Skip to content

Boxed Iterator Forwarding

Accepted

Accepted for the closed V1 boxed dynamic iterator forwarding surface; examples use the accepted V1 generic-impl syntax.

Box(dyn Iterable(Item)) provides a required prelude Iterable(Item) implementation that forwards iter_dyn to the borrowed dynamic payload:

impl Box(dyn Iterable(Item)) as Iterable(Item) {
  fn iter_dyn(self: *const Self, alloc: *dyn Allocator) Box(dyn Iterator(Item))!AllocError {
    return try self.ptr_const().iter_dyn(alloc)
  }
}

This does not make for item in boxed_iterable valid. Plain for does not call iter_dyn implicitly, and the erased payload cannot provide an honest static iter() result. The explicit allocation boundary remains:

var it = try boxed_iterable.iter_dyn(alloc)
defer it.dispose()

for item in it {
}

Box(dyn MutableIterable(Item)) provides a required prelude MutableIterable(Item) implementation that forwards iter_dyn_mut to the borrowed mutable dynamic payload:

impl Box(dyn MutableIterable(Item)) as MutableIterable(Item) {
  fn iter_dyn_mut(self: *Self, alloc: *dyn Allocator) Box(dyn Iterator(Item))!AllocError {
    return try self.ptr().iter_dyn_mut(alloc)
  }
}

This does not make for var item in boxed_mut_iterable valid. for var calls static iter_mut, not iter_dyn_mut. Use the explicit dynamic iterator:

var it = try boxed_mut_iterable.iter_dyn_mut(alloc)
defer it.dispose()

for item in it {
  item.* = value
}

These Box(dyn Iterable) and Box(dyn MutableIterable) implementations do not create general smart-pointer forwarding. They are explicit required prelude contract implementations for specific erased owner types. Ordinary payload operations still require an explicit ptr() / ptr_const() borrow.

The required V1 boxed dynamic forwarding surface is closed:

Box(dyn Iterator(Item)) as Iterator(Item)
Box(dyn Iterable(Item)) as Iterable(Item)
Box(dyn MutableIterable(Item)) as MutableIterable(Item)

No general box forwarding

There is no general rule that Box(dyn C) implements C. A broader forwarding model is deferred until receiver overloading, coherence, and smart-pointer ergonomics are designed.

In particular, Box(dyn Sequence(T)) does not implement Sequence(T) in V1, and Box(dyn MutableSequence(T)) does not implement MutableSequence(T). Use ptr_const() / ptr() for sequence operations. Borrowed dynamic pointers still expose inherited iterable operations, so seq.ptr_const().iter_dyn(alloc) is valid for seq: Box(dyn Sequence(T)). Direct owner forwarding remains available only when the boxed erased type is dyn Iterable(Item) or dyn MutableIterable(Item), or after an explicit owner upcast to that surface.

Boxed Iterator Owner

Plain for does not implicitly call iter_dyn or iter_dyn_mut. Dynamic iterator construction may allocate, can fail, and creates an owning resource, so V1 keeps that boundary visible. In V1, "dynamic iteration" means constructing and using a boxed dynamic iterator:

var it = try xs.iter_dyn(alloc)
defer it.dispose()

for item in it {
}

This is equivalent to:

var iter = it.ptr()
while const item = iter.next() {
}

Box(dyn Iterator(Item)) provides a required prelude Iterator(Item) implementation that forwards next() to the borrowed dynamic payload. This is not general smart-pointer method forwarding and not a for special case; it is a normal contract implementation for the boxed iterator owner:

impl Box(dyn Iterator(Item)) as Iterator(Item) {
  fn next(self: *Self) ?Item {
    return self.ptr().next()
  }
}

The generic form is expressible in ordinary prelude code using the accepted V1 inline impl-binder spelling. Semantically it is an impl over a concrete sized Box(dyn Iterator(Item)) type:

impl Box(dyn Iterator(comptime Item: Type)) as Iterator(Item) {
}

for item in it_box uses this implementation through ordinary visible-conformance lookup. In normal code it is visible because Box and core iterator contracts are provided by the prelude, but it is not compiler intrinsic. This implementation is part of the required V1 prelude surface because the accepted dynamic iteration pattern depends on it.

The box remains caller-owned and must still be disposed explicitly unless ownership is transferred with move. In V1, Box(dyn Iterator(Item)) must implement Iterator(Item), not Iterable(Item), because it is iterator state rather than a collection or view that creates fresh iterators. If a type provides both, normal for ambiguity applies.

ptr() remains the explicit manual way to drive the erased iterator:

var p = it_box.ptr()
while const item = p.next() {
}

Dynamic iteration is not a separate loop form; it uses Box(dyn Iterator(Item)), an owning boxed dynamic contract object:

Box(dyn Iterator(Item))

Box(dyn Iterator(Item)) is an ordinary prelude resource type, not compiler magic. It owns erased iterator state, stores the dynamic iterator dispatch and dispose metadata, provides cleanup through Box.dispose, and exposes the iterator through ptr(). APIs that create boxed dynamic iterators allocate iterator state and therefore require allocation authority:

fn iter_dyn(self: *const Self, alloc: *dyn Allocator) Box(dyn Iterator(Item))!AllocError

iter_dyn and iter_dyn_mut fail with the standard allocator error when construction cannot allocate erased iterator state. If construction fails, no initialized Box(dyn Iterator(Item)) is returned and the implementation must clean up any partial state internally. After successful construction, calling next through the borrowed dynamic iterator pointer or through for item in it_box does not require allocation authority and does not fail.

Caller-provided storage

There is no separate caller-provided-storage API for dynamic iteration in V1. Code that needs fixed-buffer, stack-backed, or arena-backed allocation should provide that policy through the erased allocator value passed to iter_dyn.

Box(dyn Iterator(Item)) is an owning resource value:

  • it must be stored in a var binding if it will be disposed
  • advancing the iterator can use ordinary for item in it_box through the visible Iterator(Item) impl, or an explicit ptr() borrow
  • dispose(self: *Self) ends the resource value's lifetime
  • dispose is not idempotent; double dispose is invalid, with debug trapping encouraged where practical
  • exhaustion does not dispose it
  • disposing before exhaustion is valid
  • after dispose, the storage may be assigned a fresh box, but the old value must not be read or used
  • copying is invalid because Box is a resource type whose declaration metadata forbids copy
  • passing *dyn Iterator(Item) borrows the iterator payload; the callee may advance it but must not dispose the owning box
  • passing owning Box(dyn Iterator(Item)) values by value requires explicit move; returning one by value transfers ownership to the caller when returned as a fresh rvalue or with return move it
  • clone support is not part of V1; code that needs two dynamic passes should call iter_dyn twice
  • it has no built-in thread-safety or sharing guarantee; future concurrency marker contracts should express send/share properties

Box(dyn Iterator(Item)) owns the erased iterator state, not necessarily the collection being iterated. The erased state may borrow from the source collection. For mutable dynamic iteration, the mutable borrow of the source lasts until the iterator box is disposed. A proven violation is a hard compiler error; suspected convention violations are lint candidates.

Iterator(Item) requires Item to be a concrete runtime type. To iterate over erased contract objects, make the item type explicit, such as Iterator(*const dyn SomeDynSafeContract). If Item is itself an owning resource type, each successful next() transfers ownership of that item to the caller. Copy prohibition and explicit move rules apply; ordinary cleanup remains explicit source convention and optional lint territory.

Box(dyn Iterator(Item)) has no public layout guarantee in V1. Public code should use Box operations, not its fields. Its implementation may store state, vtable, dispose metadata, private freeing state, or other descriptor data however the prelude or standard library chooses.

Dynamic iterators are owned boxed dynamic contract values.

box_dyn(Iterator(Item), I, alloc, value) is the V1 prelude helper for direct owned erasure. It is ordinary prelude surface, not compiler magic. (move box).into_dyn(Iterator(Item)) converts an existing concrete box without reallocating.

Direct box_dyn(Iterator(Item), I, alloc, value) rejects an already-erased Box(dyn Iterator(Item)) value. Code that needs to transfer ownership should move the existing box; code that intentionally wants a nested owner-as-payload must make that nested box explicit before using into_dyn.

Boxed dynamic iterators inherit the fused iterator law.