Smart pointers are data structures that not only act like a pointer
but also have additional metadata and capabilities.
In Rust, which uses the concept of ownership and borrowing, and
additional difference between references and smart pointers is that
references are pointers that only borrow data; in contrast, in many
cases, smart pointers own the data they point to.
Some common smart pointers in the standard library:
Box<T> for allocating values on the heap
Rc<T>, a reference counting type that enables multiple ownership
Ref<T> and RefMut<T>, accessed through RefCell<T>, a type that enforces the borrowing rules at runtime instead of compile time
Using Box<T> to point to data on the heap
Use Box to store data on the heap rather than the stack.
What remains on the stack is the pointer to the heap data.
Mostly used in these situations:
When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
When 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
When a box goes out of scope, as b does at the end of main, it
will be deallocated. The deallocation happens for the box (stored
on the stack) and the data it points (stored on the heap).
Enabling recursive types with boxes
Deref trait
Implementing the Deref trait allows you to customize the behavior
of the deference operator, *. By implementing Deref in such a
way that a smart pointer can be treated like a regular reference,
you can write code that operates on references and use that code
with smart pointers too.
Defining our own smart pointer
Deref coercion
Deref coercion is a convenience that Rust performs on arguments to
functions and methods.
Deref coercion works only on types that implement the Deref
trait.
Deref coercion with mutability
You can use the DerefMut trait to override the * operator on
mutable references.
Rust does deref coercion when it finds types and trait
implementations in three cases:
From &T to &U when T: Deref<Target=U>
From &mut T to &mut U when T: DerefMut<Target=U>
From &mut T to &U when T: Deref<Target=U>
/!\ Immutable references will never coerce to mutable references.
Running code on cleanup with the Drop trait
In Rust, you can specify that a particular bit of code be run
whenever a value goes out of scope, and the compiler will insert
this code automatically.
/!\ Variables are dropped in the reverse order of their creation!
Dropping a value early with std::mem::drop
Occasionally, you might want to clean up a value early, e.g.
release a lock.
We are not allowed to explicitly call drop.
Rc<T> the reference counted smart pointer
There are cases when a single value might have multiple owners,
e.g. in graph data structures, multiple edges might point to the
same node, and that node is conceptually owned by all of the edges
that point to it.
To enable multiple ownership, Rust has a type called Rc<T>, i.e.
Reference Counting. It keeps track of the number of references
to a value to determine whether or not the value is still in use.
If there are zero references to a value, the value can be cleaned
up without any references becoming invalid.
RefCell<T> and the interior mutability pattern
With references and Box<T>, the borrowing rules’ invariants are
enforced at compile time. With RefCell<T>, these invariants are
enforced at runtime. With references, if you break these rules,
you’ll get a compiler error. With RefCell<T>, if you break these
rules, your program will panic and exit.
The RefCell<T> type is useful when you’re sure your code follows
the borrowing rules but the compiler is unable to understand and
guarantee that.
There are situations in which it would be useful for a value to
mutate itself in its methods but appear immutable to other code.
Some example: mock objects.
Keeping track of borrows at runtime with RefCell<T>
When creating immutable and mutable references, we use the & and
&mut syntax, respectively. With RefCell<T>, we use the borrow
and borrow_mut methods, which are part of the safe API that belongs
to RefCell<T>. The borrow method returns the smart pointer type
Ref<T>, and borrow_mut returns the smart pointer type
RefMut<T>. Both types implement Deref, so we can treat them
like regular references.
The code panicked at runtime.
Having multiple owners of mutable data
A common way to use RefCell<T> is in combination with Rc<T>.
You can get a value that can have multiple owners and that you can
mutate.
Reference cycles can leak memory
Preventing reference cycles: turning a Rc<T> into a Weak<T>
Rc::clone increases the strong_count of a Rc<T> instance.
A Rc<T> instance is only cleaned up if its strong_count is 0.
You can create a weak reference to the value within a Rc<T>
instance by calling Rc::downgrade and passing a reference to the
Rc<T>. When you call Rc::downgrade, you get a smart pointer of
type Weak<T>. Instead of increasing the strong_count in the
Rc<T> instance by 1, it will increase the weak_count by 1.
The difference with strong_count is that it doesn’t need to be 0
for the Rc<T> instance to be cleaned up.
Hence, weak references don’t express an ownership relationship.
Because the value that Weak<T> references might have been
dropped, you must make sure the value still exists.
You can do this by calling the upgrade method on a Weak<T>
instance, which will return an Option<Rc<T>>.