diff --git a/Deref Trait.md b/Deref Trait.md index 4c359a5..8c7181f 100644 --- a/Deref Trait.md +++ b/Deref Trait.md @@ -1 +1,223 @@ -# Treating Smart Pointers Like Regular References with the `Deref` Trait \ No newline at end of file +# 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`. + +Then we will see why the dereference operator doesnt work like a reference on our newly behaves lik `Box`. + +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` type, the custom type we are about to build and the real `Box`. + +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` Like Reference +We can rewrite the code from the example to use a `Box` instead of a reference. + +Here dereference operator using on the `Box` 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` 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` 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` 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` type is defined as a tuple struct with one element. + +In this example we defined `MyBox` type in the same way. + +We will aslo define a `new` function to match the `new` function defined in `Box` +```rust +struct MyBox(T); + +impl MyBox { + fn new(x: T) -> MyBox { + 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` type we defined instead of `Box`. + +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` 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 Deref for MyBox { + 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` 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`. + diff --git a/guessing_game/target/.rustc_info.json b/guessing_game/target/.rustc_info.json index 0d44089..6aa56cc 100644 --- a/guessing_game/target/.rustc_info.json +++ b/guessing_game/target/.rustc_info.json @@ -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":{}} \ No newline at end of file +{"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":{}} \ No newline at end of file diff --git a/hello_cargo/target/.rustc_info.json b/hello_cargo/target/.rustc_info.json index 0d44089..8f3f4b3 100644 --- a/hello_cargo/target/.rustc_info.json +++ b/hello_cargo/target/.rustc_info.json @@ -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":{}} \ No newline at end of file +{"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":{}} \ No newline at end of file