Skip to content

← Unsafe and Soundness step 19 of 24

Hard Framework

PhantomData, variance and drop-check

Implement shared and mutable iterators for the supplied MyVec<T>. The vector stores initialized elements in an allocated buffer and deliberately rejects zero-sized element types.

Add the iterator markers, implement iter() and iter_mut(), and implement Iterator for both iterator types. Keep the supplied script behavior and the assert_variance signature, whose body must remain x.

pub fn iter_script(ops: Vec<String>) -> Vec<i64>

Script behavior

The script operates on a MyVec<i64> and emits one integer per command.

Command Effect and output
push N Append N and emit the new length.
sum Sum elements through iter(); emit 0 when empty.
double Double each element through iter_mut(), then emit their sum.
max Emit the largest element, or -1 when empty.
count Count elements by consuming a shared iterator.
len Emit the current length.
Unknown command Emit -2 without changing the vector.

Preserve the supplied parser: split at the first ASCII space, without trimming. Commands are case-sensitive. push parses the entire remainder as an i64, using 0 for missing or invalid values. Other recognized commands ignore the remainder. Inputs keep all sums, doubled values and reported lengths within i64 range.

commands = ["sum", "push 3", "push -7", "sum", "double", "max", "count"]
output   = [0, 1, 2, -4, -8, 6, 2]

The harness also runs a shared iterator over [4, 9] and expects a sum of 13. A result of -1 from max can mean either an empty vector or a largest value of -1; the output does not distinguish them.

Describe the borrow with PhantomData

A raw pointer has no lifetime parameter. The starter's iterator structs declare 'a but do not use it in any field, producing E0392.

Add PhantomData<&'a T> to MyIter and PhantomData<&'a mut T> to MyIterMut. These zero-sized markers describe the intended borrow to the type system without storing an actual reference.

The marker alone does not validate the pointer or choose the correct lifetime. The constructors must tie the returned iterator to their receiver:

pub fn iter(&self) -> MyIter<'_, T>
pub fn iter_mut(&mut self) -> MyIterMut<'_, T>

Together with the iterator definitions, these signatures keep the vector borrowed while references yielded by the iterator remain in use. Do not manufacture a longer lifetime or expose unchecked iterator construction.

Separate the borrowing lifetime from the element type

Covariance permits a type containing a longer-lived reference to be used where the corresponding shorter-lived reference type is required. Invariance disallows that subtype conversion. The two parameters of these iterators must be considered separately:

Type or field In the borrowing lifetime 'a In element type T
&'a T Covariant Covariant
&'a mut T Covariant Invariant
*const T No lifetime parameter Covariant
*mut T No lifetime parameter Invariant
MyIter<'a, T> with the requested fields Covariant Covariant
MyIterMut<'a, T> with the requested fields Covariant Invariant

Mutable access to a slot must not permit changing its element type from a long-lived reference to a shorter-lived one and storing that shorter borrow in the slot. This is why &mut T is invariant in T.

That does not make every container with a method returning &mut T invariant. For example, an owning vector can be covariant while its mutable borrow supplies the necessary restriction. Analyze the whole API and its fields.

The supplied mutable iterator already contains *mut T, which makes it invariant in T. Replacing only its marker with a shared-reference marker would not remove that existing invariance. Use the requested mutable marker to describe the intended exclusive borrow, rather than treating the marker as the only field that affects variance.

Know what the compile check establishes

pub fn assert_variance<'long: 'short, 'short>(
    x: MyIter<'long, i32>,
) -> MyIter<'short, i32> {
    x
}

Keeping this body checks that the shared iterator can shorten its borrowing lifetime. It does not check variance in T, since T is fixed to i32, or establish the mutable iterator's safety. A mutable-reference marker is also covariant in 'a, so this check cannot distinguish every possible marker.

Separate compile-pass and compile-fail examples can test other coercions. Output tests cannot establish these properties on their own, and passing a coercion check does not validate the unsafe implementation.

Yield each initialized element at most once

Construct the end pointer from the buffer start and len. For an empty unallocated vector, this is a zero offset from its aligned dangling pointer; never dereference it. For a nonempty vector, stop at the one-past-end pointer.

In next, first check for exhaustion. Otherwise obtain the reference and advance the iterator pointer before a subsequent call can yield the same slot. The shared iterator returns &'a T; the mutable iterator returns &'a mut T. Those lifetimes come from the original vector borrow, not the short borrow of the iterator during this one next call.

A caller can retain one mutable item while requesting another. Soundness therefore requires distinct yielded elements, valid initialized storage, correct alignment and bounds, and an exclusive vector borrow that prevents reallocation or conflicting access. Advancing a pointer is only one part of that argument. State the relevant invariants beside each unsafe block.

Ownership and automatic traits

The supplied owning vector has a Drop implementation. Its destructor already affects drop checking for its generic parameter; adding PhantomData<T> is not required just to make that destructor count as a possible user of T. Other representations can need ownership markers, and marker choices also affect variance and automatic traits.

Raw-pointer fields already prevent these iterators from automatically being Send or Sync. A PhantomData field does not remove that restriction. Do not add unsafe implementations of those traits for this exercise.

A passing submission must compile, pass the tests, and pass clippy -D warnings. The checks exercise behavior and the supplied coercion; they are not a complete proof of memory safety.