started ch15.2
Some checks failed
Test Gitea Actions / first (push) Successful in 17s
Test Gitea Actions / check-code (push) Failing after 14s
Test Gitea Actions / test (push) Has been skipped
Test Gitea Actions / documentation-check (push) Has been skipped

This commit is contained in:
darkicewolf50 2025-03-04 11:59:38 -07:00
parent ad92fedb9c
commit 856e0e992a
3 changed files with 225 additions and 3 deletions

View File

@ -1 +1,223 @@
# Treating Smart Pointers Like Regular References with the `Deref` Trait
Implmenting the `Deref` trait allows you to customize the behavior of the *dereference operator* `*`.
This is not the multiplication or glob operator
Implementing `Deref` in a way such that a smart pointer can be treated like a regular reference.
You can wirte code that operators on references and use that code with smart pointers as well.
Now lets look at the dereference operator works with regualr references.
Next we will try to define a custom type that behaves like `Box<T>`.
Then we will see why the dereference operator doesnt work like a reference on our newly behaves lik `Box<T>`.
We will explore how implementing the `Deref` trait makes it possible for smart pointers to work in a way that is simialr to references.
Finally we will lokk at Rust's *deref coercion* feature and how it let us work with either references or smart pointers.
Note: The big difference between the `MyBox<T>` type, the custom type we are about to build and the real `Box<T>`.
Our version will not store its data on the heap.
We are focusing on the `Deref` trait, so where the data is actualy stored is less improtant than the pointer-like behavior.
## Following the Pointer to the Value
A regular reference is a type of pointer.
One way to think about a pointer is, an arrow to a value stored somewhere else.
In this example we create a reference to an `i32` value.
We then use the dereference operator to follwo the reference to the value.
```rust
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
```
The variable `x` holds a `5` (`i32`) value.
We then set `y` equal to a refernce to `x`.
We assert that `x` is equal to `5`.
If we want to make an assertion about the value in `y`, we have touse `*y` to follow the reference to the value it's pointing to.
Hence the need for a *dereference*.
Once we dereference `y` we havce access to the integer value `y` is pointing to so that we can compare with `5`
If we tired to use `assert_eq!(5, t);` instead we woul get the compiler error
```
$ cargo run
Compiling deref-example v0.1.0 (file:///projects/deref-example)
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src/main.rs:6:5
|
6 | assert_eq!(5, y);
| ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
|
= help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider dereferencing here
--> file:///home/.rustup/toolchains/1.82/lib/rustlib/src/rust/library/core/src/macros/mod.rs:46:35
|
46| if !(*left_val == **right_val) {
| +
For more information about this error, try `rustc --explain E0277`.
error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
```
Comparing a number to a reference is not allowed because they are differnect types.
We must ue the dereference operator to follow the reference to the value they are pointing at.
## Using `Box<T>` Like Reference
We can rewrite the code from the example to use a `Box<T>` instead of a reference.
Here dereference operator using on the `Box<T>` functions the same way as the dereference operator used on the reference (from before).
```rust
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
```
The main difference between the two eamples is that we ext `y` to be an instance of a `Box<T>` pointing to a copied value of `x` rather than a reference pointing to the value of `x`
In the last assertion we can use the dereference operator to following the pointer of the `Box<T>` in the way as we deferenced `y` when it was a reference.
## Defining Our Own Smart Pointer
Here we will bouild a smart pointer simialr to the `Box<T>` type provided by the std library.
This is in order to explain fully how smart pointers behave differently from references by defualt.
Then we will look into how to add the ability to use the dereference operator.
The `Box<T>` type is defined as a tuple struct with one element.
In this example we defined `MyBox<T>` type in the same way.
We will aslo define a `new` function to match the `new` function defined in `Box<T>`
```rust
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
```
Here we defined a struct named `MyBox` and declarce a generic paramter `T` beucause we want our type to hold values of any type.
The `MyBox` type is a tuple struct with one element of type `T`.
The `MyBox::new` function takes one parameter of type `T` and returns a `MyBox` instance that holds the value passed in.
If we try adding the `main` function from above to our preivous examples.
We will change it to use `MyBox<T>` type we defined instead of `Box<T>`.
The code will not compie because Rust doesn't know how to dereference `MyBox`
```rust
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
```
Here is the resulting compilation error
```
$ cargo run
Compiling deref-example v0.1.0 (file:///projects/deref-example)
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
--> src/main.rs:14:19
|
14 | assert_eq!(5, *y);
| ^^
For more information about this error, try `rustc --explain E0614`.
error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
```
The `MyBox<T>` type cant be dereferenced because we havn't implemented the ability to on our type.
To enable dereferencing with the `*` operator, we need to implemented the `Deref` trait.
## Treating a Type Like a Reference by Implementing the `Deref` Trait
As discussed before to implement a trait we need to provide implementations for the trait's required methods.
Here the `Deref` trait, provided by the std library, requires us to implement one method named `deref` that borrows `self` and returns a refernce to the inner data.
Here is th implementation of the `Deref` to add to the definition of `MyBox`
```rust
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
```
The `type Target = T;` syntax defines an associated type for the `Deref` trait to use.
Associated types are a slightly different way of declaring a generic paramter, we will cover them later in ch20.
We fill in the body of the `deref` method with `&self.0` so `deref` returns a reference to the vbalue we want to access with the `*` operator.
Recall that using a "Tuple Struct without a Named Filed to Create Different Types" section.
We use `.0` accesses the first value in a tuple struct.
The `main` function from the example before will now compile and the assertions will pass now with thuis implementation.
Without the `Deref` trait, the compiler can only dereference `&` references.
The `deref` method gives the compiler the ability to take a value of any type that implements `Deref` and call the `deref` method to get a `&` reference that it knows how to deference.
When we write `*y`.
Behind the scenes Rust actually ran this code
```rust
*(y.deref())
```
Rust substitues the `*` operator with a call to `deref` method.
Then a plain derefence so we dont have to think about whether or not we need to call the `deref` method.
This Rust feature lets us write code that functions identically regardless of if we have a regular reference or a type that implements `Deref`
The reason that the `deref` method reutnrs a reference to a value, and that the plain dereferncee outside the parentheses in `*(y.deref())` is still necessary, is to do with the ownership system.
If the `deref` method retuned the value directly instead of a refernece to the value, the value would be moved out of `self`.
We dont want to take ownership of the inner value insid `MyBox<T>` in this case or in most cases where we use the dereference oerator.
Note: the `*` operator is replaced with a call to the `deref` method and then a call to trhe `*` operator just once.
Each time we use a `*` in our code.
Due to the substituation of the `*` operator doesn't recurse infinitely we end up with data of type `i32`.
This then matches the `5` in `assert_eq!`
## Implicit Deref Coercions iwth Functions and Methods
*Deref coercion* converts a reference to a type that implements the `Deref` trait into a reference to another type.
For example a deref coercion can convert `&String` to `&str` because `String` implemented the `Deref` trait such that it returns `&str`.

View File

@ -1 +1 @@
{"rustc_fingerprint":4305361489769004817,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""},"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":4305361489769004817,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""},"2063776225603076451":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}

View File

@ -1 +1 @@
{"rustc_fingerprint":4305361489769004817,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""},"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":4305361489769004817,"outputs":{"2063776225603076451":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""}},"successes":{}}