23 Rustlings Smart Pointers Solution

In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. Smart pointers in Rust often own the data they point to, while references only borrow data.

Further Information

Box1.rs

// box1.rs
//
// At compile time, Rust needs to know how much space a type takes up. This becomes problematic
// for recursive types, where a value can have as part of itself another value of the same type.
// To get around the issue, we can use a `Box` - a smart pointer used to store data on the heap,
// which also allows us to wrap a recursive type.
//
// The recursive type we're implementing in this exercise is the `cons list` - a data structure
// frequently found in functional programming languages. Each item in a cons list contains two
// elements: the value of the current item and the next item. The last item is a value called `Nil`.
//
// Step 1: use a `Box` in the enum definition to make the code compile
// Step 2: create both empty and non-empty cons lists by replacing `todo!()`
//
// Note: the tests should not be changed
//
// Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

#[derive(PartialEq, Debug)]
pub enum List {
    Cons(i32, List),
    Nil,
}

fn main() {
    println!("This is an empty cons list: {:?}", create_empty_list());
    println!(
        "This is a non-empty cons list: {:?}",
        create_non_empty_list()
    );
}

pub fn create_empty_list() -> List {
    todo!()
}

pub fn create_non_empty_list() -> List {
    todo!()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_empty_list() {
        assert_eq!(List::Nil, create_empty_list())
    }

    #[test]
    fn test_create_non_empty_list() {
        assert_ne!(create_empty_list(), create_non_empty_list())
    }
}

Our instructions are pretty clear, we have to first, make our code compile and then complete a couple of functions and make the program pass the tests.

  • Step 1: use a Box in the enum definition to make the code compile
  • Step 2: create both empty and non-empty cons lists by replacing todo!()

Box1.rs Errors

⚠️  Compiling of exercises/smart_pointers/box1.rs failed! Please try again. Here's the output:
error[E0072]: recursive type `List` has infinite size
  --> exercises/smart_pointers/box1.rs:22:1
   |
22 | pub enum List {
   | ^^^^^^^^^^^^^
23 |     Cons(i32, List),
   |               ---- recursive without indirection
   |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
   |
23 |     Cons(i32, Box<List>),
   |               ++++    +

error: aborting due to previous error

Here we see the Rust compiler is suggesting to use a Box<> in our enum this seems like a reasonable suggestion and we should use that and see

Box1.rs Solution

Let's take what the Rust compiler suggests and see what happens.


  #[derive(PartialEq, Debug)]
pub enum List {
    Cons(i32, Box<List>),  // Adding `Box<>` here
    Nil,
}

By adding the Box<List> to our Cons variant, our code now compiles but our tests are not passing, but that should be expected as we know we have todo! macros that we need to complete.

Empty List todo!

Let's take a look at our first todo!

pub fn create_empty_list() -> List {
    todo!()
}

by also looking at the test we see that we should trying to get to pass

#[test]
fn test_create_empty_list() {
    assert_eq!(List::Nil, create_empty_list())
}

Simply put, we need our function to return List::Nil so let's update our function to match:

pub fn create_empty_list() -> List {
    List::Nil
}

Now we are passing our first test:

running 2 tests
test tests::test_create_empty_list ... ok
test tests::test_create_non_empty_list ... FAILED

Non-Empty Listtodo!

Our instructions for our second todo! are Step 2: create both empty and non-empty cons lists by replacing todo!()

pub fn create_non_empty_list() -> List {
    todo!()
}

So let's see how we can do that.

If we look at our List enum we see that Cons variant has both an i32 and the Box<List> that we just added to get the code to compile. So to make our function create_non_empty_list() compile we have to pass in both and i32 and create a new List right? let's try.

pub fn create_non_empty_list() -> List {
    List::Cons(3, Box::new(List::Nil))
}

And with this our code compiles, let's have a look at the full block for reference


  #[derive(PartialEq, Debug)]
pub enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    println!("This is an empty cons list: {:?}", create_empty_list());
    println!(
        "This is a non-empty cons list: {:?}",
        create_non_empty_list()
    );
}

pub fn create_empty_list() -> List {
    List::Nil
}

pub fn create_non_empty_list() -> List {

    List::Cons(3,Box::new(List::Nil))
}

Let's move on to the next exercise.

Rc1.rs

// rc1.rs
// In this exercise, we want to express the concept of multiple owners via the Rc<T> type.
// This is a model of our solar system - there is a Sun type and multiple Planets.
// The Planets take ownership of the sun, indicating that they revolve around the sun.

// Make this code compile by using the proper Rc primitives to express that the sun has multiple owners.

// I AM NOT DONE

use std::rc::Rc;

#[derive(Debug)]
struct Sun {}

#[derive(Debug)]
enum Planet {
    Mercury(Rc<Sun>),
    Venus(Rc<Sun>),
    Earth(Rc<Sun>),
    Mars(Rc<Sun>),
    Jupiter(Rc<Sun>),
    Saturn(Rc<Sun>),
    Uranus(Rc<Sun>),
    Neptune(Rc<Sun>),
}

impl Planet {
    fn details(&self) {
        println!("Hi from {:?}!", self)
    }
}

fn main() {
    let sun = Rc::new(Sun {});
    println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

    let mercury = Planet::Mercury(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
    mercury.details();

    let venus = Planet::Venus(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
    venus.details();

    let earth = Planet::Earth(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
    earth.details();

    let mars = Planet::Mars(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
    mars.details();

    let jupiter = Planet::Jupiter(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
    jupiter.details();

    // TODO
    let saturn = Planet::Saturn(Rc::new(Sun {}));
    println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
    saturn.details();

    // TODO
    let uranus = Planet::Uranus(Rc::new(Sun {}));
    println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
    uranus.details();

    // TODO
    let neptune = Planet::Neptune(Rc::new(Sun {}));
    println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
    neptune.details();

    assert_eq!(Rc::strong_count(&sun), 9);

    drop(neptune);
    println!("reference count = {}", Rc::strong_count(&sun)); // 8 references

    drop(uranus);
    println!("reference count = {}", Rc::strong_count(&sun)); // 7 references

    drop(saturn);
    println!("reference count = {}", Rc::strong_count(&sun)); // 6 references

    drop(jupiter);
    println!("reference count = {}", Rc::strong_count(&sun)); // 5 references

    drop(mars);
    println!("reference count = {}", Rc::strong_count(&sun)); // 4 references

    // TODO
    println!("reference count = {}", Rc::strong_count(&sun)); // 3 references

    // TODO
    println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

    // TODO
    println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

    assert_eq!(Rc::strong_count(&sun), 1);
}

okay, we have a lot of TODO's here, with the instructions of making the code compile by using the proper Rc primitives. Let's take a look at what the Rust compiler says.

Rc1.rs Errors

⚠️  Ran exercises/smart_pointers/rc1.rs with errors
reference count = 1
reference count = 2
Hi from Mercury(Sun)!
reference count = 3
Hi from Venus(Sun)!
reference count = 4
Hi from Earth(Sun)!
reference count = 5
Hi from Mars(Sun)!
reference count = 6
Hi from Jupiter(Sun)!
reference count = 6
Hi from Saturn(Sun)!
reference count = 6
Hi from Uranus(Sun)!
reference count = 6
Hi from Neptune(Sun)!

thread 'main' panicked at exercises/smart_pointers/rc1.rs:72:5:
assertion `left == right` failed
  left: 6
 right: 9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.

We see that our code is panicking and that our asserting at line 72: assert_eq!(Rc::strong_count(&sun), 9); because we're "stuck" on reference count 6. So let's go ahead and try and fix that

Rc1.rs Solution

Let's break this down into two different parts as there seems to be a thread (no pun intended 😄) between the different TODOs

Fixing Saturn, Uranus and Neptune

First 3 TODO's are these:

    // TODO
    let saturn = Planet::Saturn(Rc::new(Sun {}));
    println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
    saturn.details();

    // TODO
    let uranus = Planet::Uranus(Rc::new(Sun {}));
    println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
    uranus.details();

    // TODO
    let neptune = Planet::Neptune(Rc::new(Sun {}));
    println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
    neptune.details();

and if you look at the variable assignment of each it differs from the first 3, instead of cloning our planet we use the Rc::new() syntax, here's what the jupiter variable looks like:

let jupiter = Planet::Jupiter(Rc::clone(&sun));

Here we are taking our Jupiter planet and cloning it using Rc::clone which creates a new reference to sun. This working for our first 3 planets, So let's fix saturn, uranus and neptune to match and see what happens.

Reference Count Output

⚠️  Ran exercises/smart_pointers/rc1.rs with errors
reference count = 1
reference count = 2
Hi from Mercury(Sun)!
reference count = 3
Hi from Venus(Sun)!
reference count = 4
Hi from Earth(Sun)!
reference count = 5
Hi from Mars(Sun)!
reference count = 6
Hi from Jupiter(Sun)!
reference count = 7
Hi from Saturn(Sun)!
reference count = 8
Hi from Uranus(Sun)!
reference count = 9
Hi from Neptune(Sun)!
reference count = 8
reference count = 7
reference count = 6
reference count = 5
reference count = 4
reference count = 4
reference count = 4
reference count = 4

thread 'main' panicked at exercises/smart_pointers/rc1.rs:101:5:
assertion `left == right` failed
  left: 4
 right: 1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Okay, we are counting all the way to 9 now, BUT, we're still not fully working because as you can see, the second set of TODO's are still not updated, let's take a closer look:

    // TODO
    println!("reference count = {}", Rc::strong_count(&sun)); // 3 references

    // TODO
    println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

    // TODO
    println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

Again, we seem to have a natural grouping of these three, and we see that we should be going down to 3, 2, and finally 1 reference. If we look at the previous reference output. We'll notice that there's a drop(mars) before the println! statement:

drop(mars);
println!("reference count = {}", Rc::strong_count(&sun)); // 4 references

Seems simple enough let's add a drop for each of the corresponding planets which would be earth, venus, and mercury.

// TODO
drop(earth);
println!("reference count = {}", Rc::strong_count(&sun)); // 3 references

// TODO
drop(venus);
println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

// TODO
drop(mercury);
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

Final Output

With those updates we are now compiling!

🎉 🎉  The code is compiling! 🎉 🎉

Output:
====================
reference count = 1
reference count = 2
Hi from Mercury(Sun)!
reference count = 3
Hi from Venus(Sun)!
reference count = 4
Hi from Earth(Sun)!
reference count = 5
Hi from Mars(Sun)!
reference count = 6
Hi from Jupiter(Sun)!
reference count = 7
Hi from Saturn(Sun)!
reference count = 8
Hi from Uranus(Sun)!
reference count = 9
Hi from Neptune(Sun)!
reference count = 8
reference count = 7
reference count = 6
reference count = 5
reference count = 4
reference count = 3
reference count = 2
reference count = 1

====================

Arc1.rs

// arc1.rs
// In this exercise, we are given a Vec of u32 called "numbers" with values ranging
// from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ]
// We would like to use this set of numbers within 8 different threads simultaneously.
// Each thread is going to get the sum of every eighth value, with an offset.
// The first thread (offset 0), will sum 0, 8, 16, ...
// The second thread (offset 1), will sum 1, 9, 17, ...
// The third thread (offset 2), will sum 2, 10, 18, ...
// ...
// The eighth thread (offset 7), will sum 7, 15, 23, ...

// Because we are using threads, our values need to be thread-safe.  Therefore,
// we are using Arc.  We need to make a change in each of the two TODOs.


// Make this code compile by filling in a value for `shared_numbers` where the
// first TODO comment is, and create an initial binding for `child_numbers`
// where the second TODO comment is. Try not to create any copies of the `numbers` Vec!
// Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

#![forbid(unused_imports)] // Do not change this, (or the next) line.
use std::sync::Arc;
use std::thread;

fn main() {
    let numbers: Vec<_> = (0..100u32).collect();
    let shared_numbers = // TODO
    let mut joinhandles = Vec::new();

    for offset in 0..8 {
        let child_numbers = // TODO
        joinhandles.push(thread::spawn(move || {
            let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
            println!("Sum of offset {} is {}", offset, sum);
        }));
    }
    for handle in joinhandles.into_iter() {
        handle.join().unwrap();
    }
}

We continue our exercises with threads this time our exercise focuses on the use of Arc we have 2 TODO's to complete

  • let shared_numbers = // TODO
  • let child_numbers = // TODO

Let's take a look at the Rust compiler errors and see if we can get some hints from there.

Arc1.rs Errors

⚠️  Compiling of exercises/smart_pointers/arc1.rs failed! Please try again. Here's the output:
error: expected expression, found `let` statement
  --> exercises/smart_pointers/arc1.rs:30:5
   |
30 |     let mut joinhandles = Vec::new();
   |     ^^^

error: expected expression, found statement (`let`)
  --> exercises/smart_pointers/arc1.rs:30:5
   |
30 |     let mut joinhandles = Vec::new();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: variable declaration using `let` is a statement

error[E0425]: cannot find value `child_numbers` in this scope
  --> exercises/smart_pointers/arc1.rs:35:28
   |
35 |             let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
   |                            ^^^^^^^^^^^^^ help: a local variable with a similar name exists: `shared_numbers`

Unfortunately it does't say much, just let's us know that we have missing information where our TODO's are located so that's not a surprise.

Arc1.rs Solution


  #![forbid(unused_imports)] // Do not change this, (or the next) line.
use std::sync::Arc;
use std::thread;

fn main() {
    let numbers: Vec<_> = (0..100u32).collect();
    let shared_numbers = // TODO
    let mut joinhandles = Vec::new();

    for offset in 0..8 {
        let child_numbers = // TODO
        joinhandles.push(thread::spawn(move || {
            let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
            println!("Sum of offset {} is {}", offset, sum);
        }));
    }
    for handle in joinhandles.into_iter() {
        handle.join().unwrap();
    }
}

So we know that we need to use Arc in this exercise, so let's create a new Arc for shared number by completing the first TODO with:

let shared_numbers = Arc::new(numbers); // TODO

This allows us to wrap the numbers vector in an Arc to share it safely among threads without making any copies.

Our second TODO needs a reference to the shared numbers so we need to create a reference to the shared Arc data for each thread. Let's use clone as we have done before in previous exercises

let child_numbers = Arc::clone(&shared_numbers); // TODO

and with this our Rust code is compiling!

🎉 🎉  The code is compiling! 🎉 🎉

Output:
====================
Sum of offset 0 is 624
Sum of offset 1 is 637
Sum of offset 2 is 650
Sum of offset 5 is 588
Sum of offset 3 is 663
Sum of offset 4 is 576
Sum of offset 6 is 600
Sum of offset 7 is 612

====================

Let's move on to our final exercise for this category.

Cow1.rs

// cow1.rs

// This exercise explores the Cow, or Clone-On-Write type.
// Cow is a clone-on-write smart pointer.
// It can enclose and provide immutable access to borrowed data, and clone the data lazily when mutation or ownership is required.
// The type is designed to work with general borrowed data via the Borrow trait.
//
// This exercise is meant to show you what to expect when passing data to Cow.
// Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the TODO markers.

// I AM NOT DONE

use std::borrow::Cow;

fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
    for i in 0..input.len() {
        let v = input[i];
        if v < 0 {
            // Clones into a vector if not already owned.
            input.to_mut()[i] = -v;
        }
    }
    input
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reference_mutation() -> Result<(), &'static str> {
        // Clone occurs because `input` needs to be mutated.
        let slice = [-1, 0, 1];
        let mut input = Cow::from(&slice[..]);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }

    #[test]
    fn reference_no_mutation() -> Result<(), &'static str> {
        // No clone occurs because `input` doesn't need to be mutated.
        let slice = [0, 1, 2];
        let mut input = Cow::from(&slice[..]);
        match abs_all(&mut input) {
            // TODO
        }
    }

    #[test]
    fn owned_no_mutation() -> Result<(), &'static str> {
        // We can also pass `slice` without `&` so Cow owns it directly.
        // In this case no mutation occurs and thus also no clone,
        // but the result is still owned because it always was.
        let slice = vec![0, 1, 2];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            // TODO
        }
    }

    #[test]
    fn owned_mutation() -> Result<(), &'static str> {
        // Of course this is also the case if a mutation does occur.
        // In this case the call to `to_mut()` returns a reference to
        // the same data as before.
        let slice = vec![-1, 0, 1];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            // TODO
        }
    }
}

Our instructions are to fix the unit tests to get them to pass by using Cow::Owned(_) and Cow::Borrowed(_), let's take a look at what the compiler is saying.

Cow1.rs Errors

⚠️  Compiling of exercises/smart_pointers/cow1.rs failed! Please try again. Here's the output:
error[E0004]: non-exhaustive patterns: type `&mut std::borrow::Cow<'_, [i32]>` is non-empty
   --> exercises/smart_pointers/cow1.rs:46:15
    |
46  |         match abs_all(&mut input) {
    |               ^^^^^^^^^^^^^^^^^^^
    |
note: `std::borrow::Cow<'_, [i32]>` defined here
   --> /Users/desmo/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/borrow.rs:179:1
    |
179 | pub enum Cow<'a, B: ?Sized + 'a>
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: the matched value is of type `&mut std::borrow::Cow<'_, [i32]>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
    |
46  ~         match abs_all(&mut input) {
47  +             _ => todo!(),
48  +         }
    |

error[E0004]: non-exhaustive patterns: type `&mut std::borrow::Cow<'_, [i32]>` is non-empty
   --> exercises/smart_pointers/cow1.rs:58:15
    |
58  |         match abs_all(&mut input) {
    |               ^^^^^^^^^^^^^^^^^^^
    |
note: `std::borrow::Cow<'_, [i32]>` defined here
   --> /Users/desmo/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/borrow.rs:179:1
    |
179 | pub enum Cow<'a, B: ?Sized + 'a>
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: the matched value is of type `&mut std::borrow::Cow<'_, [i32]>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
    |
58  ~         match abs_all(&mut input) {
59  +             _ => todo!(),
60  +         }
    |

error[E0004]: non-exhaustive patterns: type `&mut std::borrow::Cow<'_, [i32]>` is non-empty
   --> exercises/smart_pointers/cow1.rs:70:15
    |
70  |         match abs_all(&mut input) {
    |               ^^^^^^^^^^^^^^^^^^^
    |
note: `std::borrow::Cow<'_, [i32]>` defined here
   --> /Users/desmo/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/borrow.rs:179:1
    |
179 | pub enum Cow<'a, B: ?Sized + 'a>
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = note: the matched value is of type `&mut std::borrow::Cow<'_, [i32]>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
    |
70  ~         match abs_all(&mut input) {
71  +             _ => todo!(),
72  +         }
    |

error: aborting due to 3 previous errors

Okay, our messages are essentially telling us we have some code missing in our TODO's fair enough. Let's try and work on a solution.

Cow1.rs Solution

If we look at the first test reference_mutation we can get and idea of what is missing in our other tests:

    fn reference_mutation() -> Result<(), &'static str> {
        // Clone occurs because `input` needs to be mutated.
        let slice = [-1, 0, 1];
        let mut input = Cow::from(&slice[..]);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }

In the other tests, we are missing the match abs_all match arms. So I would expect to fill those match arms with either Cow::Owned(_) or Cow::Borrowed(_) we get additional comment notes at each function. Looking at the comment in reference_mutation, it tells us that a clone occurs because input needs to be mutated, so it makes sense that they use the Cow::Owned() smart pointer.

In our next function reference_no_mutation we see the comment: No clone occurs because input doesn't need to be mutated. So naturally, since it doesn't need to be owned we should use the Cow::Borrowed smart pointer, right?

#[test]
fn reference_no_mutation() -> Result<(), &'static str> {
    // No clone occurs because `input` doesn't need to be mutated.
    let slice = [0, 1, 2];
    let mut input = Cow::from(&slice[..]);
    match abs_all(&mut input) {
        Cow::Borrowed(_) => Ok(()),
        _ => Err("Expected owned value"),
    }
}

Our third function tells us we don't need to mutate and no clone is created but the result is still owned so that makes it clear to use Cow::Owned

    #[test]
    fn owned_no_mutation() -> Result<(), &'static str> {
        // We can also pass `slice` without `&` so Cow owns it directly.
        // In this case no mutation occurs and thus also no clone,
        // but the result is still owned because it always was.
        let slice = vec![0, 1, 2];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }

Our final test tells us that that calling input.to_mut() in our abs_all function, in case we need to mutate the data we need to make sure it's owned.

    fn owned_mutation() -> Result<(), &'static str> {
        // Of course this is also the case if a mutation does occur.
        // In this case the call to `to_mut()` returns a reference to
        // the same data as before.
        let slice = vec![-1, 0, 1];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }

and with that we are compiling and our tests have all passed!

✅ Successfully tested exercises/smart_pointers/cow1.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

Conclusion

Exploring Rust's smart pointers - Box, Rc, Arc, and Cow - has taken us through a variety of scenarios, each illustrating the unique strengths and use cases of these powerful tools in the Rust ecosystem. From handling recursive data structures with Box to managing shared ownership with Rc and Arc, and leveraging the efficiency of Cow for clone-on-write scenarios, we've seen how Rust smart pointers enable more complex data management while maintaining the language's strict guarantees on safety and concurrency.

What stands out is how Rust's design encourages developers to think critically about ownership, lifetime, and memory usage. These smart pointers are not just tools but also guidelines that shape the way we structure our programs for optimal performance and safety. They embody Rust's philosophy of making systems programming both accessible and reliable, preventing common pitfalls such as data races and memory leaks.

As you continue your journey with Rust, remember that mastering these concepts opens doors to advanced patterns and techniques in Rust programming. The versatility of smart pointers in Rust makes them indispensable for a wide range of applications, from web servers and operating systems to game development and embedded systems.

Keep experimenting, keep building, and most importantly, keep enjoying the process of learning and growing with Rust. The Rust community is an invaluable resource, always ready to help and inspire. With these tools in your arsenal, you're well-equipped to tackle the challenges of systems programming with confidence and creativity.