Smart Pointers
Rc<T>
enables multiple owners of the same data;Box<T>
andRefCell<T>
have single owners.Box<T>
allows immutable or mutable borrows checked at compile time;Rc<T>
allows only immutable borrows checked at compile time;RefCell<T>
allows immutable or mutable borrows checked at runtime.- Because
RefCell<T>
allows mutable borrows checked at runtime, you can mutate the value inside theRefCell<T>
even when theRefCell<T>
is immutable.
Box
All values in Rust are stack-allocated by default. Box<T>
allow you to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
Boxes provide ownership for this allocation, and drop their contents when they go out of scope. Boxes also ensure that they never allocate more than isize::MAX
bytes.
The Box<T>
type is a smart pointer, because it implements the std::ops::Deref
⮳ trait, which allows Box<T>
values to be treated like a reference. You can use the dereference operator *
or 'deref coercion' with the .
operator to retrieve its inner value.
let boxed: Box<u8> = Box::new(1); let _val: u8 = *boxed; let boxed = Box::new("example"); // Deref coercion: equivalent to (*boxed.deref()).len() let _val = boxed.len();
Use Box<T>
when
- you have a dynamically sized type, whose size can’t be known at compile time,
- you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type,
- you don't want to rely on stack space.
// Define a Node struct to represent a single element in a linked list. struct Node { value: i32, // Node is a recursive data type. next: Option<Box<Node>>, } impl Node { fn new(value: i32) -> Self { Node { value, next: None } } // Recursively traverses the list until it finds the last node // (where next is None) and sets its next field to a new Node. fn append(&mut self, value: i32) { match self.next { Some(ref mut next_node) => next_node.append(value), None => self.next = Some(Box::new(Node::new(value))), } } fn print(&self) { print!("{}", self.value); if let Some(ref next_node) = self.next { print!(" -> "); next_node.print(); } else { println!(); } } } fn main() { // The linked list has an unknown number of nodes, // thus its size is not fixed. // It could not be stored directly on the stack. // By using `Box`, which pointer to the heap has a defined size, // we can create the `head` local variable on the stack. let mut head = Node::new(1); head.append(2); head.append(3); head.append(4); head.print(); // Output: 1 -> 2 -> 3 -> 4 }
Rc
The Rc<T>
type (for "Reference Counted") enables shared ownership of a value.
Rc
maintains a reference count of the number of owners. You can create additional references to the data using theclone
method. Cloning anRc
only increments the reference count without duplicating the data. When the last owner goes out of scope, the data is automatically cleaned up (dropped).- If you need mutability, put a
Cell
orRefCell
inside theRc
. Rc
automatically dereferences toT
(via theDeref
trait), so you can callT
’s methods on a value of typeRc<T>
.Rc
is commonly used in data structures, such as graphs and linked lists, where multiple nodes might need to share ownership of certain nodes or data.
Keep in mind that Rc
is not thread-safe. For concurrent scenarios, you should use Arc
(Atomic Reference Counted), which provides similar functionality with thread safety.
use std::rc::Rc; fn main() { let a = Rc::new(vec![1.0, 2.0, 3.0]); // The two syntaxes below are equivalent. let b = a.clone(); let c = Rc::clone(&a); // `b` and `c` both point to the same memory location as `a`. // Gets the number of (Rc) pointers to this allocation. assert_eq!(3, Rc::strong_count(&a)); // Dropping one of the pointers decrements the strong reference count. drop(c); assert_eq!(2, Rc::strong_count(&a)); // `Rc` is a smart pointer, so we can dereference it. println!("{:?}", *a); b.iter().for_each(|x| print!("{} ", x)); // We can get a mutable reference to the inner value, // if there are no other `Rc` or `Weak` pointers to the same allocation. // Returns `None` ottherwise. let mut y = Rc::new(4); *Rc::get_mut(&mut y).unwrap() = 5; assert_eq!(*y, 5); // We can also consume the `Rc` to return its inner value, // if the Rc has exactly one strong reference. // See also `Rc::into_inner`. let x = Rc::new(6); assert_eq!(Rc::try_unwrap(x), Ok(6)); }
Also of note: the Weak
type, typically obtained via Rc::downgrade
, allows for non-owning (weak) references to the data. This can help prevent reference cycles that could lead to memory leaks.
RefCell
Rust memory safety allows (i) several immutable references (&T
) to an object T
; or (ii) one mutable reference (&mut T
). This is enforced at compile time. However, sometimes it is required to have multiple references to an object and yet mutate it. RefCell<T>
(and related types Cell<T>
and OnceCell<T>
) have interior mutability, a pattern that allows you to mutate data even when there are immutable references to it.
These types are used in scenarios involving shared state within a single thread, like GUI applications, or when creating complex data structures like graphs.
RefCell
keeps track of borrowing rules at runtime and ensures that only one mutable or multiple immutable borrows exist at a time. Attempts to violate borrowing rules (like having multiple mutable borrows) will cause a panic at runtime. Common methods includeborrow
,borrow_mut
, andtry_borrow
.
use std::cell::RefCell; fn main() { // Create a RefCell containing a vector of integers let data = RefCell::new(vec![1, 2, 3, 4, 5]); // Borrow the data immutably { let borrowed_data = data.borrow(); println!("Borrowed (immutable): {:?}", borrowed_data); // We can borrow the data immutably again let _borrowed_data2 = data.borrow(); } // The immutable borrow ends here (when the `Ref` guard returned by `borrow` exits scope.) // Borrow the data mutably and modify it { let mut borrowed_data = data.borrow_mut(); borrowed_data.push(6); println!("Borrowed (mutable): {:?}", borrowed_data); // We can't borrow the data again while it's borrowed mutably assert!(data.try_borrow().is_err()); assert!(data.try_borrow_mut().is_err()); } // The mutable borrow ends here // Borrow the data immutably again to check the modification { if let Ok(borrowed_data) = data.try_borrow() { println!("Borrowed (immutable again): {:?}", borrowed_data); } } // We can also consume the `RefCell`, returning the wrapped value. let _data = data.into_inner(); }
RefCell<T>
(andCell<T>
,OnceCell<T>
) do not implementSync
and are therefore single-threaded. The correspondingSync
version ofRefCell<T>
isRwLock<T>
. UseMutex<T>
,RwLock<T>
,OnceLock<T>
, or atomic types when working with multiple threads.
-
finish to rewrite Cell, OnceCell
-
example: RefCell inside of Rc
Cell
Cell<T>
is a type that provides simple, byte-wise copyable mutability. It is commonly used for types that implement the Copy
trait, like integers and booleans. Cell<T>
is used when you need to mutate a value without using a reference or a mutable reference. Common methods include set
, get
, and replace
.
use std::cell::Cell; struct MyStruct<T> { // The Cell type allows for interior mutability. value: Cell<T>, } impl<T> MyStruct<T> { fn new(value: T) -> Self { MyStruct { value: Cell::new(value), } } // Replace the interior value, dropping the replaced value. fn set_value(&self, new_value: T) { self.value.set(new_value); } } impl<T: Copy> MyStruct<T> { fn get_value(&self) -> T { // For types that implement `Copy``, the `get` method // retrieves the current interior value by duplicating it. self.value.get() } } // For types that implement `Default`, the take method replaces the current // interior value with `Default::default()` and returns the replaced value. impl<T: Default> MyStruct<T> { fn take_value(&self) -> T { self.value.take() } } fn main() { let my_int_struct = MyStruct::new(42); let my_int_ref = &my_int_struct; // Get the current value (i32 is Copy) println!("Initial value: {}", my_int_struct.get_value()); // Set a new value, while using an immutable reference to the struct my_int_ref.set_value(100); // Get the updated value println!("Updated value: {}", my_int_ref.get_value()); let my_string_struct = MyStruct::new("example".to_string()); let my_string_ref = &my_string_struct; // ERROR, `String` is not `Copy`: my_string.get_value(); // Get the value, this time using the `take_value` method println!("String: {}", my_string_ref.take_value()); assert_eq!(my_string_ref.take_value(), ""); // Replace the current interior value and returns the replaced value. assert_eq!(my_string_struct.value.replace("example2".into()), ""); // `into_inner` consumes the Cell<T> and returns the interior value. let _inner = my_string_struct.value.into_inner(); }
"Cell<T>
implements interior mutability by moving values in and out of the cell. That is, an &mut T
to the inner value can never be obtained, and the value itself cannot be directly obtained without replacing it with something else."
https://doc.rust-lang.org/nightly/core/cell/index.html
OnceCell
- The
OnceCell
type provides a way to define a value that will be initialized at most once. It's useful for scenarios where you want lazy initialization without the overhead of thread synchronization.
Link to memory management page and OnceCell section