By default, Zith uses static dispatch — the compiler knows the exact implementation at compile time. Zero overhead.
Use dyn for dynamic dispatch. At the call site you get polymorphism; the compiler and LLVM can often optimize away the indirection, making it zero-cost in practice.
dyn as a Type HintLike union (see §3.6), dyn works as a type hint. When the compiler can't deduce the concrete type, you write dyn to tell it you want dynamic behavior:
let x: dyn = 5;
if (x is i32) x += 32; // smart-cast inside the branch
// dyn []T — dynamic Trait slice
let items: dyn []Drawable = shapes;
// dyn slice
let dynList: dyn [];
// In many cases LLVM strips the dyn overhead entirely
// even if not, compare the type is a simple int comparison
// in terms of performance, is union + ptr indirection, still fast
Inside a smart-cast branch (is), the type narrows to the concrete type. Mutations inside the branch affect the inner value; outside, assigning to the variable changes what the dyn points to (if var).
dyn Traitsdyn Trait is a view by default — a read-only, non-owning reference with a vtable. That means view dyn is redundant.
All other memory modifiers work with dyn:
| Keyword | dyn behavior |
|---|---|
view dyn |
Redundant — dyn is already a view |
share dyn |
Multiple names, same dynamic value |
lend dyn |
Exclusive mutable borrow of a dynamic value |
unique dyn |
Single-owner dynamic value |
fn draw_all(items: dyn []Drawable) {
for (item in items) { item.render(); }
}
//specific verbose, you could use an interface or alias to simplify
fn modify(shape: lend dyn Drawable) {
shape.scale(2.0);
}
dyn does not work with interface (see §4.3) — only traits. Interfaces are structural and don't carry a vtable.
When you write a type that could be dyn or opaque, prefer dyn — it's short and clearer. Reserve opaque for cases where you specifically need raw opaque (untagged void*, C interop).
dyn vs Generics| Generics | dyn |
|
|---|---|---|
| Dispatch | Static — one copy per type | Dynamic — single code path |
| Code size | Larger (N copies) | Smaller (one copy) |
| Performance | No indirection | Vtable indirection (often elided by LLVM) |
| When to use | Hot loops, known types at compile time | Heterogeneous collections, plugins |
// Generic — compiler monomorphizes
fn log<T: Printable>(val: T) { val.print(); }
// dyn — vtable dispatch, LLVM may inline away the overhead
fn logDyn(val: dyn Printable) { val.print(); }
A trait is object-safe if all its methods meet these rules:
Self in parameter or return types (except self, other)Self: Sized requirementsIf you try to use a non-object-safe trait with dyn, the compiler rejects it.
Zith Language Specification — Draft v0.9