We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 9 of 24
NonNull<T>, null, and dangling-but-aligned
pub fn stack_ops(script: Vec<String>) -> Vec<i64>
Implement the unfinished methods of a growable stack of i64, using
alloc, dealloc, and NonNull<i64>. Then run the supplied operations.
Return one number per operation:
| Operation | Output |
|---|---|
push N |
The new length |
pop |
The removed top value, or -1 if empty |
peek |
The top value without removing it, or -1 if empty |
len |
The current length |
reset |
0, after freeing any buffer |
| Unknown command | -2 |
For example, ["push 8", "push 3", "peek", "pop", "len", "reset", "peek"]
returns [1, 2, 3, 3, 1, 0, -1]. A stored -1 is allowed, so that
output alone does not distinguish an empty stack from a top value of -1.
Keep the supplied command parser. It splits at the first space, treats a
missing or invalid push argument as zero, and ignores arguments on other
recognized commands. The seeded tests use valid integer arguments.
Track allocation separately from length
A new stack has len = 0, cap = 0, and ptr = NonNull::dangling().
Allocate four slots on its first push and double capacity when full.
Popping the last element makes the stack empty but leaves its buffer
allocated. By contrast, reset must free any allocation and restore all
three fields to their initial values. Calling reset twice must be safe.
Maintain these invariants:
len <= cap.- If
cap == 0, there is no allocation to access or free. - If
cap > 0, the stack owns a buffer with layoutLayout::array::<i64>(cap). - The first
lenslots hold the stack's values. Read only those slots.
During growth, allocate the new buffer, copy the live elements, and free the
old buffer using its old capacity. Since the elements are i64, they need
no destructor calls. A generic collection would need additional handling.
What non-null means
NonNull<T> guarantees a non-null pointer. It does not guarantee a live
allocation, alignment, initialized data, or permission to access that data.
For sized types such as i64, Option<NonNull<T>> has the size and
alignment of a raw pointer. Null represents None, so no separate tag is
needed. This is useful for optional links in data structures.
Check your understanding Is constructing a null NonNull valid if you never dereference it? Show answer Hide answer
No. Passing null to NonNull::new_unchecked violates its safety requirement
immediately. Use NonNull::new(raw), which returns None for null.
After allocation, handle that case before using the pointer.
Why use dangling()?
NonNull::dangling() supplies an aligned, non-null placeholder. It does not
allocate storage for an i64. Never read, write, or deallocate that
placeholder in this stack.
Use cap to determine whether storage exists. Comparing the pointer's
address with the placeholder is not a valid allocation test: the address
returned by dangling() is not reserved exclusively for this purpose.
The global alloc function requires a nonzero allocation size. That is why
this implementation skips allocation when its capacity is zero. Likewise,
dealloc requires a currently allocated pointer and the matching layout.
Check your understanding After reset, why restore ptr as well as len and cap? Show answer Hide answer
It restores the representation established by empty(), making subsequent
operations easier to reason about. Merely retaining a freed raw pointer is
not itself a dereference, but it is no longer usable for accessing the old
allocation. Replacing it with dangling() also does not make unchecked
access safe: every method must still respect the length and capacity.
Related pointer rules
Unlike *mut T, NonNull<T> is covariant in T. Whether that is sound
for a wrapper depends on its API. Returning &mut T from &mut self does
not by itself require an invariant container: Vec<T> is covariant too.
Mutation through shared access needs particular care. See
variance and PhantomData.
Unsafe reference conversions such as as_ref require more than non-nullness.
The data must remain valid for the reference's lifetime, and access must
obey Rust's aliasing rules. Zero-sized types have special access rules, but
references still require non-null, aligned pointers. This exercise uses
i64 only.
A passing submission must compile, pass the tests, and pass
clippy -D warnings. These checks do not prove unsafe code sound. In
particular, undefined behavior need not produce a crash. Review the
invariants and safety comments as well as the outputs.
References: NonNull, global allocation, and pointer safety.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.