We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 1 of 26
The stack, the heap, and `Box<T>`
Two small functions over boxed data.
pub fn heaviest(items: Vec<Box<[u8]>>) -> usize
pub fn unbox_sum(v: Vec<Box<i64>>) -> i64
heaviest returns the index of the longest slice; on a tie the lowest
index wins; an empty vector returns 0. unbox_sum adds up the boxed
integers; an empty vector sums to 0.
The arithmetic is trivial. The point is the Box.
What a Box<T> actually is
A Box<T> is one machine word on the stack holding the address of a T on
the heap, plus a promise: when the box is dropped, that heap allocation is
freed. That is the whole type. Every other smart pointer in this track —
Rc, RefCell, Cow, Weak — is a variation on the same two-part idea: a
small handle you can move around cheaply, and a Drop impl that cleans up
what the handle points at.
If you came from Java, Python or Go, there was no “who frees this” question:
the collector answered it for you, later, invisibly. If you came from C there
was a question and no answer — you had to remember. Rust’s answer is
ownership: exactly one value owns the allocation, and the compiler knows
when that owner dies. Box is the smallest possible expression of it.
The three real reasons to reach for Box
-
The size is not known at compile time.
[u8]— a slice with no length in its type — cannot sit on the stack, because the compiler cannot say how many bytes to reserve. Behind a pointer it can:Box<[u8]>is a fat pointer, two words, address plus length. Recursive types are the same problem and you meet them next. -
You want to move a large value without copying its bytes. Moving a
Boxcopies one word regardless of how big the payload is. -
You want to own a value by trait rather than by concrete type —
Box<dyn Error>, which you have already met in Track 6.
Notice what is not on that list: “because it is on the heap and heap is how
I am used to thinking”. Boxing an i64 because Java would have heap-allocated
an Integer is the single most common reflex to unlearn here.
*boxed moves the value out — and that is special
Given let b: Box<i64> = Box::new(7);, the expression *b does not merely
borrow the seven. It moves it out of the box, and the box is deallocated.
Box is the only type in the language with this power; the compiler special-
cases it. Try the same thing on an Rc in a few items’ time and you get
E0507, cannot move out of an Rc. That asymmetry surprises everyone, so
file it away now: * on Box can move; * on every other smart pointer
only borrows.
The deliberate trap in the starter
The unbox_sum starter contains a first draft that does not compile:
let owned: Box<i64> = v[i];
Indexing gives you a place, not a value, and moving a non-Copy value out
of a place inside a Vec would leave a hole the vector does not know about.
That is E0507, cannot move out of index. The compiler is not being fussy:
if it allowed this, v‘s destructor would later try to free an allocation
that the box you took away has already freed.
There are several honest fixes, and comparing them is the exercise:
borrow instead of move (*v[i] copies the i64 out, because i64 is
Copy — the box stays put), or consume the whole vector with into_iter()
so each Box really is yours. Pick the one that reads best. Both compile.
Signatures a reviewer would reject
Clippy’s default lints police reflexive boxing, and it is worth knowing which shapes they cover — because the coverage is uneven:
-
boxed_local—fn f(x: Box<u32>)where a plainu32would do. You made the caller allocate for nothing. -
borrowed_box—&Box<T>is always wrong.&Tis strictly more general and a&Box<T>autoderefs to it anyway, so the extra hop buys the caller nothing but a constraint. -
box_collectionandvec_box—Box<Vec<u8>>andVec<Box<i64>>put a pointer in front of something that is already a pointer to the heap. -
unnecessary_box_returns— returningBox<T>for aTthat has a known size just forces the caller to unwrap it.
Now the uneven part, and it is worth testing yourself: unbox_sum here takes
Vec<Box<i64>>, which is exactly the shape vec_box exists to condemn — and
the gate passes it. On 1.95 vec_box inspects struct fields and local
bindings, not pub fn parameter types. Do not read a green gate as
approval; Vec<Box<i64>> is a bad type here and it is bad on purpose, so
that unboxing it is your job.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.