Add notes on Smart Pointers, Box<T>, Deref & Drop

This commit is contained in:
Marcello 2021-11-20 18:51:51 +01:00
parent bf4d947520
commit 1cb17e0c0d

View file

@ -1019,6 +1019,118 @@ impl Iterator for Counter {
}
```
## Smart Pointers
A **pointer** is a general concept for a variable that contains an address in memory. This address refers to, or "points at" some other data.
The most common kind of pointer in Rust is a *reference*, which you learned about in Chapter 4. References are indicated by the `&` symbol and borrow the value they point to.
They don't have any special capabilities other than referring to data. Also, they don`t have any overhead and are the kind of pointer used most often.
**Smart pointers**, on the other hand, are *data structures* that not only act like a pointer but also have additional metadata and capabilities.
The different smart pointers defined in the standard library provide functionality beyond that provided by references.
In Rust, which uses the concept of ownership and borrowing, an 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.
Smart pointers are usually implemented using structs. The characteristic distinguishing a smart pointer from a struct is that smart pointers implement the `Deref` and `Drop` traits.
The `Deref` trait allows an instance of the smart pointer struct to behave like a reference so it's possible to write code that works with either references or smart pointers.
The `Drop` trait allows to customize the code that is run when an instance of the smart pointer goes out of scope.
The most common smart pointers in the standard library are:
- `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
The most straightforward smart pointer is `Box<T>`. Boxes allow to store data on the heap rather than the stack. What remains on the stack is the pointer to the heap data.
Boxes don't have performance overhead, other than storing their data on the heap instead of on the stack. But they don't have many extra capabilities either.
`Box<T>` use cases:
- Using a type whose size can't be known at compile time in a context that requires an exact size
- Transferring ownership of a large amount of data but ensuring the data won't be copied when you do so
- Owning a value and which implements a particular trait rather than being of a specific type
```rs
let _box = Box::new(pointed_value);
```
### `Deref` Trait & Deref Coercion
Implementing the `Deref` trait allows to customize the behavior of the dereference operator, `*`.
By implementing `Deref` in such a way that a smart pointer can be treated like a regular reference.
```rs
struct CustomSmartPointer<T>(T);
impl<T> CustomSmartPointer<T> {
fn new(x: T) {
CustomSmartPointer(x)
}
}
impl<T> Deref for CustomSmartPointer<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
// return reference to value
}
}
let s = CustomSmartPointer::new(value);
let v = *s;
// same as
let v = *(s.deref());
```
*Deref coercion* is a convenience that Rust performs on arguments to functions and methods.
It works only on types that implement the `Deref` trait and converts such a type into a reference to another type.
Deref coercion was added to Rust so that programmers writing function and method calls don't need to add as many explicit references and dereferences with `&` and `*`.
```rs
fn hello(name: &str) {
println!("Hello {}", name);
}
fn main() {
let name = Box::new(String::from("Rust"));
hello(&name); // Box<string> coerced to &str (Box -> String -> &str)
}
```
When the `Deref` trait is defined for the types involved, Rust will analyze the types and use `Deref::deref` as many times as necessary to get a reference to match the parameter's type.
Similar to the `Deref` trait to override the `*` operator on *immutable references*, it's possible to 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>`
### `Drop` Trait
`Drop` allows to customize what happens when a value is about to go out of scope. It-s possible to provide an implementation for the `Drop` trait on any type.
```rs
struct CustomSmartPointer<T>(T);
impl<T> Drop for CustomSmartPointer<T> {
fn drop(&mut self) {
// clean up memory
}
}
fn main() {
let var1 = CCustomSmartPointer(value); // dropped when var1 goes out of scope
let var2 = CCustomSmartPointer(value);
drop(var2); // dropped early by using std::mem::drop
}
```
Rust automatically calls `drop` when the instances went go of scope. Variables are dropped in the reverse order of their creation.
## Files
### Reading Files