Smart Pointers
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
- 智能指针 - Anatomy In First Rust Programming Class 🦀
- ✨Smart Pointers: Heap、Deref、Drop、Rc、RefCell、Reference Cycle - The Rust Programming Language
- Heap: Using Box
to Point to Data on the Heap - The Rust Programming Language - Rc
(Reference Counting): single-threaded scenarios, immutable references of multiple owners, the Reference Counted Smart Pointer - The Rust Programming Language - Shared-State Concurrency - The Rust Programming Language
- Cow Documentation
rustlings-solutions-5/standard_library_types at main · gaveen/rustlings-solutions-5
Rustlings
Arc
- 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_numberswhere the first TODO comment is - and create an initial binding for
child_numberswhere the second TODO comment is.
Try not to create any copies of the
numbersVec!
arc1: Using Arc to keep thread-safe
// 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(); } }
Hint
- Make
shared_numbersbe anArcfrom the numbers vector. - Then, in order
to avoid creating a copy of
numbers, you’ll need to createchild_numbersinside the loop but still in the main thread. child_numbersshould be a clone of the Arc of the numbers instead of a thread-local copy of the numbers.
This is a simple exercise if you understand the underlying concepts, but if this is too much of a struggle, consider reading through all of Concurrency Chapter in the book: Fearless Concurrency - The Rust Programming Language
solution: Arc::new()
#![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 = Arc::new(numbers); // filling in a value for `shared_numbers` let mut joinhandles = Vec::new(); for offset in 0..8 { let child_numbers = shared_numbers.clone(); // create an initial binding for `child_numbers` 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(); } }
Box
At compile time, Rust needs to know
how much space a type takes up. This becomes problematic forrecursive 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
Boxin the enum definition to make the code compile - Step 2: create both empty and non-empty cons lists by replacing
todo!()
box1
// 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() ); // convert unit tests to main fn test_create_empty_list() { assert_eq!(List::Nil, create_empty_list()) } fn test_create_non_empty_list() { assert_ne!(create_empty_list(), create_non_empty_list()) } test_create_empty_list(); test_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()) } }
Hint
Step 1
The compiler’s message should help: since we cannot store the value of the actual type
when working with recursive types, we need to store a reference (pointer) to its value.
We should, therefore, place our List inside a Box.
More details in the book here: Heap: Using Box
to Point to Data on the Heap - The Rust Programming Language
Step 2
Creating an empty list should be fairly straightforward (hint: peek at the assertions). For a non-empty list keep in mind that we want to use our Cons “list builder”. Although the current list is one of integers (i32), feel free to change the definition and try other types!
solution: Box::new()
#[derive(PartialEq, Debug)] pub enum List { Cons(i32, Box<List>), Nil, } pub fn create_empty_list() -> List { List::Nil } pub fn create_non_empty_list() -> List { List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil)))))) }
Cow: Clone-On-Write
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.
cow1
// 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 } // convert unit tests to main fn main() { 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"), } } 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 } } 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 } } 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 } } reference_mutation(); reference_no_mutation(); owned_no_mutation(); owned_mutation(); } #[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 } } }
Hint
Since the vector is already owned, the Cow type doesn’t need to clone it.
Checkout Cow in std::borrow - Rust for documentation
on the Cow type.
solution: Cow::Borrowed()/Cow::Owned()
// 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. 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 } fn main() { // 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(_) => println!("I borrowed the slice!"), _ => panic!("expected borrowed value"), } // 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(_) => println!("I modified the slice and now own it!"), _ => panic!("expected owned value"), } // No clone occurs because `input` is already owned. let slice = vec![-1, 0, 1]; let mut input = Cow::from(slice); match abs_all(&mut input) { Cow::Owned(_) => println!("I own this slice!"), _ => panic!("expected borrowed value"), } }
Rc
In this exercise, we want to express the concept of multiple owners via the Rc
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.
rc1
// 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); }
Hint
This is a straightforward exercise to use the Rc
- Each Planet has ownership of the Sun, and uses Rc::clone() to increment the reference count of the Sun.
- After using drop() to move the Planets out of scope individually, the reference count goes down.
- In the end the sun only has one reference again, to itself.
- Unfortunately Pluto is no longer considered a planet :(
solution: Arc::new()
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(); let saturn = Planet::Saturn(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); let uranus = Planet::Uranus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); let neptune = Planet::Neptune(Rc::clone(&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 drop(earth); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references drop(venus); println!("reference count = {}", Rc::strong_count(&sun)); // 2 references drop(mercury); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); }
Memory Leak
memory_leak1
use std::rc::Rc; struct Node { value: i32, parent: Option<Rc<Node>>, children: Vec<Rc<Node>>, } impl Node { fn new(value: i32) -> Rc<Self> { Rc::new(Self { value, parent: None, children: Vec::new(), }) } fn add_child(&mut self, child: Rc<Self>) { self.children.push(child); child.parent = Some(Rc::clone(&self)); } } fn main() { let root = Node::new(0); let child1 = Node::new(1); let child2 = Node::new(2); child1.add_child(Rc::clone(&child2)); child2.add_child(Rc::clone(&child1)); root.add_child(child1); root.add_child(child2); println!("{}", root.value); }