Skip to content

← Smart Pointers and Interior Mutability step 1 of 26

Easy Primitives

The stack, the heap, and Box<T>

Implement two functions that read values stored in boxes:

pub fn heaviest(items: Vec<Box<[u8]>>) -> usize
pub fn unbox_sum(v: Vec<Box<i64>>) -> i64

heaviest returns the zero-based index of the longest slice. If several slices have the same maximum length, return the lowest index. Return 0 for an empty vector; in that case, the result does not identify an item. Only slice lengths matter, not their byte values.

unbox_sum returns the sum of the boxed integers, or 0 for an empty vector. Inputs stay within i64's range, including intermediate sums when adding in input order. You do not need wrapping or saturating arithmetic.

For example, slice lengths [2, 4, 4] give index 1, while boxed values [10, -4] give sum 6. Both functions take ownership of their vectors.

What a box owns

Box<T> owns a value through a pointer. For a non-zero-sized payload, the box manages a heap allocation. Dropping the box normally drops its contents and releases that allocation. A zero-sized payload, such as an empty boxed byte slice, needs no allocation.

The box itself can be a local variable, a struct field or an element in a vector. It is not necessarily stored on the stack. Moving a box transfers ownership without moving its payload to a new allocation.

A box pointing to a sized type, such as Box<i64>, is represented by a single pointer. A boxed slice also carries its length: Box<[u8]> owns a sequence whose length is known at runtime. You can call .len() through the box, just as you would on a borrowed slice.

Copying, borrowing and moving the contents

Dereferencing a box does not always move out its contents. What happens depends on the expression and the payload's type. This example copies an integer and leaves the box usable:

fn main() {
    let mut boxed = Box::new(7_i64);
    let number = *boxed; // i64 implements Copy.
    *boxed = 9;
    assert_eq!(number, 7);
    assert_eq!(*boxed, 9);
}

A String is not Copy. Rust allows moving it out of an owned box:

fn main() {
    let boxed = Box::new(String::from("hello"));
    let borrowed: &str = &boxed;
    assert_eq!(borrowed, "hello");

    let text: String = *boxed;
    assert_eq!(text, "hello");
    // The contents have moved; `boxed` can no longer be read here.
}

This move-out support is special to Box. You cannot move a String out through an Rc<String> dereference, since another owner might still need it. Copying an i64 through Rc<i64> is allowed, just as with Box<i64>.

Fixing the starter

The starter tries let owned: Box<i64> = v[i];. A box is not Copy, and indexing a vector does not let you move an element out while leaving its slot uninitialized. The compiler reports E0507.

You have two straightforward options:

  • Iterate by reference and copy each i64 through its box. With for boxed in &v, use **boxed to reach the integer.
  • Consume the vector with into_iter(). Each item is then an owned Box<i64>, and *boxed copies its integer before that box is dropped.

For heaviest, borrow the slices while comparing lengths. A loop can keep the first maximum by updating only when a strictly longer slice appears. If you use max_by_key, remember that it selects the last maximum on a tie. Include an explicit tie-breaker to get the lowest index.

When boxing helps

Boxes are useful when you need ownership behind a pointer: for example, a runtime-length slice, a trait object or a link in a recursive structure. A recursive node can itself be sized because its child is stored through a pointer rather than embedded directly. Boxing can also keep a large payload at one address while its owner moves.

For a collection of ordinary integers, Vec<i64> is usually simpler than Vec<Box<i64>>: the vector already stores its elements in a heap buffer, so separate boxes add allocations and pointer accesses. Keep the boxed signature here to practice working with an API that already uses boxes.

Clippy can flag some unnecessary boxing, but a passing lint check does not establish that an API needs a box. Consider ownership and access patterns when choosing a type. Submissions must compile, pass the tests and pass clippy -D warnings.