Bonus chapter

Smart Pointers

👋 Anyone can read and edit this exercise. Sign up to save your progress.

A pointer can own a value, not just borrow it. A reference such as &T borrows a value whose owner must keep it alive. A Box<T> owns its value on the heap, so you can move the box or return it from a function without borrowing a local variable. Dropping the box drops the value and releases its allocation. No separate free or delete is needed.

Why Own a Value through a Pointer?

Most values need no box. Two situations make the extra indirection useful.

A recursive type cannot contain another whole value of itself inline. The compiler would need an infinite amount of space for it. A Box<Expr> gives an expression-tree node a fixed-size pointer to an owned child instead.

A collection of different command types needs a common element type. Box<dyn Command> owns any concrete value that implements Command and calls its methods through dynamic dispatch. By contrast, &dyn Command only borrows a command owned elsewhere. Returning newly created commands is a reason to choose the owned form.

You'll start with a small dereferencing warmup, then construct and evaluate a tree, and finally build and borrow a command pipeline. The exercises focus on Box; other smart pointers get a short recognition guide at the end.

Reading a Boxed Value

Box::new(value) moves a value into a heap allocation and returns its owner, a Box<T>. When the box is dropped, Rust drops the inner value and frees the allocation.

The * operator dereferences a box. For a Copy type such as i32, reading this way copies the inner value. Most method calls work through automatic dereferencing instead.

let boxed: Box<i32> = Box::new(7);
let n: i32 = *boxed;
assert_eq!(n + 1, 8);

Implement boxed_sum to take ownership of two boxed integers and return their sum as an i32. The tests supply the boxes. The function does not need to allocate any new ones.

You usually wouldn't box a tiny integer. This warmup lets you practice reading through a box before using one to own recursive data or a trait object.

Exercise 1 of 3
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Stuck? Show a hint No spoilers, just a nudge
    1. *a reads the i32 inside the first box.
    2. i32 is Copy, so you can read the two integers through their boxes and add them.
    Reveal the full solution Spoiler: the complete answer
    /// Sums two boxed integers, taking ownership of both boxes.
    ///
    /// The boxes are dropped when the function returns, freeing their allocations.
    fn boxed_sum(a: Box<i32>, b: Box<i32>) -> i32 {
        *a + *b
    }
    
    #[test]
    fn test_boxed_sum() {
        assert_eq!(boxed_sum(Box::new(2), Box::new(3)), 5);
        assert_eq!(boxed_sum(Box::new(-10), Box::new(40)), 30);
        assert_eq!(boxed_sum(Box::new(0), Box::new(0)), 0);
    }
    

    A Recursive Type That Needs `Box`

    This enum cannot compile:

    // Each recursive field would contain another whole Expr inline.
    enum Expr {
        Num(i32),
        Add(Expr, Expr),
        Mul(Expr, Expr),
    }
    

    The compiler must know how many bytes one Expr occupies. Each Add would contain two complete Expr values, which could themselves contain more Expr values with no fixed limit. There is no finite layout for this type.

    Box<Expr> fixes the layout by owning each child through a pointer. A Box<Expr> is one pointer wide, regardless of how large the child's tree becomes. The enum also needs space to distinguish its variants.

    enum Expr {
        Num(i32),
        Add(Box<Expr>, Box<Expr>),
        Mul(Box<Expr>, Box<Expr>),
    }
    

    Each parent owns its children, rather than borrowing nodes kept alive elsewhere. Dropping the root drops the owned tree.

    Build and Evaluate a Tree

    The supplied Expr represents a literal number, a sum, or a product. For example, an interpreter might represent (1 + 2) * 4 as a multiplication node with an addition node on the left and a number on the right.

    Implement both methods:

    The construction test checks the shape without calling eval. The evaluation tests build their own trees, so you can work on either method independently. These trees are small; recursive evaluation and dropping are not a strategy for arbitrarily deep input.

    Exercise 2 of 3
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Stuck? Show a hint No spoilers, just a nudge

      Expr::add

      1. The arguments are owned Expr values, but the Add fields require Box<Expr>.
      2. Use Box::new on each argument and put the resulting boxes in Self::Add, preserving their order.

      Expr::eval

      1. Match on self with arms for Self::Num, Self::Add, and Self::Mul.
      2. Because self is borrowed, the pattern bindings borrow the fields too. Dereference the integer in the Num arm to return its value.
      3. Evaluate both children recursively and combine their results with the appropriate operator. Method calls auto-deref through &Box<Expr>, so left.eval() works without (*left).eval().
      Reveal the full solution Spoiler: the complete answer
      /// An expression tree whose recursive children are owned through boxes. Each
      /// box gives the enum a fixed-size field instead of an inline child tree.
      #[derive(Debug, PartialEq)]
      enum Expr {
          Num(i32),
          Add(Box<Self>, Box<Self>),
          Mul(Box<Self>, Box<Self>),
      }
      
      impl Expr {
          /// Own both child expressions in an Add node, preserving their order and
          /// shape.
          fn add(left: Self, right: Self) -> Self {
              Self::Add(Box::new(left), Box::new(right))
          }
      
          /// Compute the tree's numeric value without consuming or changing it.
          fn eval(&self) -> i32 {
              match self {
                  Self::Num(value) => *value,
                  Self::Add(left, right) => left.eval() + right.eval(),
                  Self::Mul(left, right) => left.eval() * right.eval(),
              }
          }
      }
      
      #[test]
      fn add_preserves_child_order_and_structure() {
          let left = Expr::Mul(Box::new(Expr::Num(2)), Box::new(Expr::Num(3)));
          let right = Expr::Add(Box::new(Expr::Num(4)), Box::new(Expr::Num(5)));
          let tree = Expr::add(left, right);
      
          // Check construction without relying on evaluation, which would hide
          // swapped children.
          let expected = Expr::Add(
              Box::new(Expr::Mul(Box::new(Expr::Num(2)), Box::new(Expr::Num(3)))),
              Box::new(Expr::Add(Box::new(Expr::Num(4)), Box::new(Expr::Num(5)))),
          );
          assert_eq!(tree, expected);
      }
      
      #[test]
      fn leaf_evaluates_to_its_value() {
          assert_eq!(Expr::Num(7).eval(), 7);
          assert_eq!(Expr::Num(-3).eval(), -3);
      }
      
      #[test]
      fn add_two_leaves() {
          let tree = Expr::Add(Box::new(Expr::Num(2)), Box::new(Expr::Num(3)));
          assert_eq!(tree.eval(), 5);
      }
      
      #[test]
      fn mul_two_leaves() {
          let tree = Expr::Mul(Box::new(Expr::Num(4)), Box::new(Expr::Num(5)));
          assert_eq!(tree.eval(), 20);
      }
      
      #[test]
      fn nested_mixed_ops() {
          // Evaluate (1 + 2) * 4, with the nested operation on the left.
          let tree = Expr::Mul(
              Box::new(Expr::Add(Box::new(Expr::Num(1)), Box::new(Expr::Num(2)))),
              Box::new(Expr::Num(4)),
          );
          assert_eq!(tree.eval(), 12);
      }
      
      #[test]
      fn asymmetric_tree_can_be_evaluated_again() {
          // Evaluate 2 + (3 * (4 + 5)), with the deeper branch on the right.
          let tree = Expr::Add(
              Box::new(Expr::Num(2)),
              Box::new(Expr::Mul(
                  Box::new(Expr::Num(3)),
                  Box::new(Expr::Add(Box::new(Expr::Num(4)), Box::new(Expr::Num(5)))),
              )),
          );
          assert_eq!(tree.eval(), 29);
          assert_eq!(tree.eval(), 29);
      }
      

      Owning Different Command Types

      A factory cannot return references to commands it creates locally; those commands would be dropped when the factory returns. Box<dyn Command> lets it return ownership instead. Each box owns a concrete command, while the caller uses the shared Command interface.

      The supplied trait has one method:

      trait Command {
          fn run(&self, input: &str) -> String;
      }
      

      Three implementations are provided:

      A Vec<C> with C: Command has just one concrete element type. A Vec<Box<dyn Command>> can own different command types in the same collection. Each element has the same size, holding a data pointer and a vtable pointer for dispatching method calls to the concrete implementation. The unsized dyn Command lives behind the pointer, not directly in the vector. Dropping the vector drops its boxes and their commands, including any owned strings.

      Build and Run a Pipeline

      Implement both functions:

      For a suffix of "x", the factory's pipeline transforms "hi" into "HIx", not "HIX". The factory tests call the returned commands directly, independently of apply_pipeline. The runner tests supply their own pipelines.

      Owning Is Different from Borrowing

      Box<dyn Command> owns a command; &dyn Command borrows one whose owner lives elsewhere. Here apply_pipeline borrows a slice of owned boxes rather than taking the vector away from its caller. Command::run also borrows its command through &self. The caller can therefore run the same pipeline again with another input.

      Exercise 3 of 3
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    
        Stuck? Show a hint No spoilers, just a nudge

        make_pipeline

        1. Wrap each concrete command in Box::new. Construct Append by moving suffix into its field.
        2. Return a vec! with the uppercase command first and the append command second. The return type tells Rust to convert both boxes to Box<dyn Command>. If you use a local vector, annotate it as Vec<Box<dyn Command>> so it does not infer a single concrete command type.

        apply_pipeline

        1. Start with an owned string, let mut current = input.to_string();.
        2. A for loop over commands borrows each box. Replace current with command.run(&current) on each iteration. Method calls auto-deref through the reference and the box.
        3. Return current after the loop. An empty pipeline leaves that starting string unchanged.
        Reveal the full solution Spoiler: the complete answer
        /// A text transformation that borrows its command and returns an owned string.
        trait Command {
            fn run(&self, input: &str) -> String;
        }
        
        /// Uppercase the input.
        struct Uppercase;
        
        impl Command for Uppercase {
            fn run(&self, input: &str) -> String {
                input.to_uppercase()
            }
        }
        
        /// Reverse the input by Unicode scalar value.
        struct Reverse;
        
        impl Command for Reverse {
            fn run(&self, input: &str) -> String {
                input.chars().rev().collect()
            }
        }
        
        /// Append an owned suffix.
        struct Append {
            suffix: String,
        }
        
        impl Command for Append {
            fn run(&self, input: &str) -> String {
                format!("{input}{}", self.suffix)
            }
        }
        
        /// Return exactly two owned commands: Uppercase, then Append with the supplied
        /// suffix.
        fn make_pipeline(suffix: String) -> Vec<Box<dyn Command>> {
            vec![Box::new(Uppercase), Box::new(Append { suffix })]
        }
        
        /// Pass input through every command in slice order and return the final output.
        /// An empty pipeline returns the input unchanged. Borrow the pipeline so it can
        /// be reused, and support any Command implementation.
        fn apply_pipeline(commands: &[Box<dyn Command>], input: &str) -> String {
            let mut current = input.to_string();
            for command in commands {
                current = command.run(&current);
            }
            current
        }
        
        #[test]
        fn factory_returns_uppercase_then_append() {
            let pipeline = make_pipeline(String::from("x"));
            assert_eq!(pipeline.len(), 2);
        
            // Inspect each stage independently of apply_pipeline.
            assert_eq!(pipeline[0].run("hi"), "HI");
            assert_eq!(pipeline[1].run("hi"), "hix");
            assert_eq!(pipeline[1].run(&pipeline[0].run("hi")), "HIx");
        }
        
        #[test]
        fn factory_owns_the_supplied_suffix() {
            let pipeline = {
                let suffix = String::from(" fin");
                make_pipeline(suffix)
            };
            assert_eq!(pipeline.len(), 2);
            assert_eq!(pipeline[1].run("one"), "one fin");
            assert_eq!(pipeline[1].run("two"), "two fin");
        
            let empty_suffix = make_pipeline(String::new());
            assert_eq!(empty_suffix.len(), 2);
            assert_eq!(empty_suffix[1].run("hi"), "hi");
        }
        
        #[test]
        fn empty_pipeline_returns_input_unchanged() {
            let pipeline: Vec<Box<dyn Command>> = Vec::new();
            assert_eq!(apply_pipeline(&pipeline, "hello"), "hello");
            assert_eq!(apply_pipeline(&pipeline, ""), "");
        }
        
        #[test]
        fn single_command_uppercase() {
            let pipeline: Vec<Box<dyn Command>> = vec![Box::new(Uppercase)];
            assert_eq!(apply_pipeline(&pipeline, "hello"), "HELLO");
        }
        
        #[test]
        fn order_matters() {
            let append_then_uppercase: Vec<Box<dyn Command>> = vec![
                Box::new(Append {
                    suffix: "x".to_string(),
                }),
                Box::new(Uppercase),
            ];
            let uppercase_then_append: Vec<Box<dyn Command>> = vec![
                Box::new(Uppercase),
                Box::new(Append {
                    suffix: "x".to_string(),
                }),
            ];
            assert_eq!(apply_pipeline(&append_then_uppercase, "hi"), "HIX");
            assert_eq!(apply_pipeline(&uppercase_then_append, "hi"), "HIx");
        }
        
        #[test]
        fn mixed_pipeline_with_append() {
            let pipeline: Vec<Box<dyn Command>> = vec![
                Box::new(Append {
                    suffix: "!".to_string(),
                }),
                Box::new(Uppercase),
                Box::new(Reverse),
            ];
            assert_eq!(apply_pipeline(&pipeline, "hi"), "!IH");
        }
        
        #[test]
        fn borrowed_pipeline_supports_custom_commands_and_reuse() {
            // This implementation exists only in the test, outside the supplied command
            // set.
            struct Bracket;
        
            impl Command for Bracket {
                fn run(&self, input: &str) -> String {
                    format!("[{input}]")
                }
            }
        
            let pipeline: Vec<Box<dyn Command>> = vec![
                Box::new(Bracket),
                Box::new(Append {
                    suffix: "x".to_string(),
                }),
            ];
            assert_eq!(apply_pipeline(&pipeline, "hi"), "[hi]x");
            assert_eq!(apply_pipeline(&pipeline, "bye"), "[bye]x");
            assert_eq!(apply_pipeline(&pipeline, "hi"), "[hi]x");
        }
        

        Wrapping Up Smart Pointers

        Boxing an integer was practice. The tree and pipeline gave you reasons to own values through pointers.

        What We Learned

        • Box<T> owns a heap value and drops it when the box is dropped. Box::new constructs a box, * dereferences it, and method calls usually auto-deref.
        • Recursive types need indirection for a finite layout. Expr::add moves child expressions into boxes; eval borrows the resulting tree without consuming it.
        • Box<dyn Command> lets a factory return ownership of different concrete command types behind one interface. A borrowed &dyn Command instead depends on an owner elsewhere.
        • Owning commands does not mean consuming them on every run. apply_pipeline borrows the slice and dispatches through the trait, leaving the pipeline available for reuse.

        Recognizing Other Smart Pointers

        These types are for recognition only here, not additional exercise requirements.

        Next chapter 23Rust Fundamentals Quiz

        Optional Chapters

        Extra practice to explore at your own pace. These chapters do not count toward course progress.