Dynamic Iteration¶
Accepted
Accepted for V1 explicit dynamic iterator construction through iter_dyn and iter_dyn_mut.
iter_dyn and iter_dyn_mut are the standard default dynamic paths for iterable receivers. They belong to Iterable and MutableIterable, not to Iterator, because they create owning boxed iterator state from a receiver and require allocation authority.
The names iter_dyn and iter_dyn_mut are intentional: dyn describes the dispatch mode of the produced iterator. The owning storage strategy is already visible in the return type Box(dyn Iterator(Item)).
iter_dyn and iter_dyn_mut are part of the dynamic contract surface. A borrowed dynamic iterable such as *const dyn Iterable(Item) can dispatch iter_dyn dynamically to create a Box(dyn Iterator(Item))!AllocError. Static-only iter and iter_mut are guarded out of the dynamic surface with ContractSurface.current().is_static().
Because iter_dyn and iter_dyn_mut are dynamic operations, their allocator parameter is an explicit erased allocator boundary:
alloc: *dyn Allocator
The ordinary Box constructors use static allocator dispatch. Dynamic iteration is the V1 exception because the operation itself must be callable through a dynamic vtable. Box(dyn C).create_from_dyn(I, alloc, value) is the erased-allocator boxed-construction path for these defaults and for custom iter_dyn overrides.
The erased allocator authority passed to iter_dyn or iter_dyn_mut must outlive every boxed iterator allocated from it unless the concrete allocator documents a stronger free-independent policy. V1 treats this as an ownership convention and ownership/allocator-authority-escape candidate, not a hard lifetime proof.
The default iter_dyn boxes the static iterator returned by iter. Implementations inherit this default and may override it when they can build a better boxed dynamic iterator. The same rule applies to iter_dyn_mut and iter_mut.
This lets dynamic iterable objects create dynamic iterators:
fn drain(alloc: *dyn Allocator, xs: *const dyn Iterable(u8)) void!AllocError {
var it = try xs.iter_dyn(alloc)
defer it.dispose()
for item in it {
}
}
iter is static-only because it returns opaque static comptime Iterator(Item). iter_dyn is dyn-safe because it returns the concrete sized type Box(dyn Iterator(Item))!AllocError.
Likewise, iter_dyn_mut is part of the dynamic surface of dyn MutableIterable(Item) and requires a mutable dynamic receiver:
fn drain(alloc: *dyn Allocator, xs: *dyn MutableIterable(*u8)) void!AllocError {
var it = try xs.iter_dyn_mut(alloc)
defer it.dispose()
for item in it {
item.* = 0
}
}
*const dyn MutableIterable(Item) cannot call iter_dyn_mut because the operation receiver is self: *Self.
Plain for item in xs never chooses iter_dyn or iter_dyn_mut automatically, even when xs is a dynamic iterable pointer. If xs: *const dyn Iterable(Item), the allocation boundary must be explicit:
var it = try xs.iter_dyn(alloc)
defer it.dispose()
for item in it {
}
for item in xs is rejected when xs is only a dynamic iterable pointer and not itself an iterator. This differs from *dyn Iterator(Item), which is already a borrowed iterator and may be looped directly:
fn drain(xs: *dyn Iterator(u8)) void {
for item in xs {
}
}
*const dyn Iterator(Item) remains rejected because next mutates.
Dynamic Iterator Construction¶
Dynamic iterator construction may allocate, can fail, and creates an owning resource, so V1 keeps that boundary visible:
var xs = make_items()
var it = try xs.iter_dyn(alloc)
defer it.dispose()
for item in it {
}
Readonly iter_dyn(alloc) also requires a receiver lifetime that outlives the returned boxed iterator when the iterator may borrow from the source. In V1, calling iter_dyn on an rvalue receiver is rejected for the normal borrowed-iterator path:
var it = try make_items().iter_dyn(alloc) // error for borrowed iterator implementations
Use a named binding:
var xs = make_items()
var it = try xs.iter_dyn(alloc)
Owned dynamic iterator construction from moved or rvalue receivers is deferred. V1 does not include an iter_dyn_owned-style API:
var it = try (move xs).iter_dyn_owned(alloc) // deferred
Static for item in make_items() remains the ergonomic path for rvalue collection loops.
Mutable dynamic iteration uses the same pattern. It returns a boxed iterator whose item type is usually a mutable pointer:
var xs = make_items()
var it = try xs.iter_dyn_mut(alloc)
defer it.dispose()
for item in it {
item.* = value
}
The mutable borrow of the source lasts until the iterator box is disposed.
iter_dyn_mut(alloc) requires a mutable source place, just like iter_mut():
var xs = make_items()
var it = try xs.iter_dyn_mut(alloc) // ok
Const source bindings are rejected:
const ys = make_items()
var bad = try ys.iter_dyn_mut(alloc) // error
Calling iter_dyn_mut on an rvalue receiver is rejected in V1:
var it = try make_items().iter_dyn_mut(alloc) // error
The boxed mutable iterator may borrow from the source after the call expression. V1 does not create an implicit hidden receiver lifetime for this method-call form; use a named mutable binding instead.