Deref and DerefMut

pull/48/head
Dhghomon 4 years ago committed by GitHub
parent 984002eec4
commit 961e71f081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -89,6 +89,7 @@ It is now late July, and *Easy Rust* is about 200 pages long. I am still writing
- [Box](#box)
- [Box around traits](#box-around-traits)
- [Default and the builder pattern](#default-and-the-builder-pattern)
- [Deref and DerefMut](#deref-and-derefmut)
## Rust Playground
@ -8646,3 +8647,301 @@ Could not create character. Characters must have:
Character { name: "Billybrobby", age: 15, height: 180, weight: 100, lifestate: Alive, can_use: true }
```
## Deref and DerefMut
`Deref` is the trait that lets you use `*` to deference something. We know that a reference is not the same as a value:
```rust
// ⚠️
fn main() {
let value = 7; // This is an i32
let reference = &7; // This is a &i32
println!("{}", value == reference);
}
```
And Rust won't even give a `false` because it won't even compare the two.
```text
error[E0277]: can't compare `{integer}` with `&{integer}`
--> src\main.rs:4:26
|
4 | println!("{}", value == reference);
| ^^ no implementation for `{integer} == &{integer}`
```
Of course, the answer is `*`. So this will print `true`:
```rust
fn main() {
let value = 7;
let reference = &7;
println!("{}", value == *reference);
}
```
Now let's imagine a simple type that just holds a number. It will be like a `Box`, and we have some ideas for some extra functions for it. But if we just give it a number, it won't be able to do much with it.
We can't use `*` like we can with `Box`:
```rust
// ⚠️
struct HoldsANumber(u8);
fn main() {
let my_number = HoldsANumber(20);
println!("{}", *my_number + 20);
}
```
The error is:
```text
error[E0614]: type `HoldsANumber` cannot be dereferenced
--> src\main.rs:24:22
|
24 | println!("{:?}", *my_number + 20);
```
We can of course do this: `println!("{:?}", my_number.0 + 20);`. But then we are just adding a separate `u8` to the 20. It would be nice if we could just add them together. The message `cannot be dereferenced` gives us a clue: we need to implement `Deref`. Something simple that implements `Deref` is sometimes called a "smart pointer". It can point to the item, has information about it, and can use its methods. Because right now we can add `my_number.0`, which is a `u8`, but we can't add a `HoldsANumber`: all it has so far is `Debug`.
By the way, `String` is actually a smart pointer to `&str` and `Vec` is a smart pointer to array (or other types). So we have actually been using smart pointers since the beginning.
Implementing `Deref` is not too hard and the examples in the standard library are easy. So we follow them and now our `Deref` looks like this:
```rust
// 🚧
impl Deref for HoldsANumber {
type Target = u8; // Remember, this is the "associated type": the type that goes together.
// You have to right type Target = (the type you want to return)
fn deref(&self) -> &Self::Target { // Rust calls .deref() when you use *. We just defined Target as a u8 so this is easy to understand
&self.0 // We chose &self.0 because it's a tuple struct. In a named struct it would be something like "&self.number"
}
}
```
So now we can do this with `*`:
```rust
use std::ops::Deref;
#[derive(Debug)]
struct HoldsANumber(u8);
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_number = HoldsANumber(20);
println!("{:?}", *my_number + 20);
}
```
So that will print `40` and we didn't need to write `my_number.0`. That means we get the methods of `u8` and we can write our own methods for `HoldsANumber`. We will add our own simple method and use another method we get from `u8` called `.checked_sub()`. That method tries to do subtraction, and if it can't do it, it gives a `None`. Remember, a `u8` can't be negative so it's safer to do `.checked_sub()` so we don't panic.
```rust
use std::ops::Deref;
struct HoldsANumber(u8);
impl HoldsANumber {
fn prints_the_number_times_two(&self) {
println!("{}", self.0 * 2);
}
}
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_number = HoldsANumber(20);
println!("{:?}", my_number.checked_sub(100)); // This method comes from u8
my_number.prints_the_number_times_two(); // This is our own method
}
```
This prints:
```text
40
None
```
We can also implement `DerefMut` so we can change the values through `*`. It looks almost the same. You need `Deref` before you can implement `DerefMut`.
```rust
use std::ops::{Deref, DerefMut};
struct HoldsANumber(u8);
impl HoldsANumber {
fn prints_the_number_times_two(&self) {
println!("{}", self.0 * 2);
}
}
impl Deref for HoldsANumber {
type Target = u8;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for HoldsANumber { // You don't need type Target = u8; here because it already knows thanks to Deref
fn deref_mut(&mut self) -> &mut Self::Target { // Everything else is the same except it says mut everywhere
&mut self.0
}
}
fn main() {
let mut my_number = HoldsANumber(20);
*my_number = 30; // DerefMut lets us do this
println!("{:?}", my_number.checked_sub(100));
my_number.prints_the_number_times_two();
}
```
So you can see that `Deref` gives your type a lot of power. This is why the standard library says: `Deref should only be implemented for smart pointers to avoid confusion.`. That is because you can do some strange things with `Deref` for a complicated type. Let's imagine a `Character` struct for a game. A new `Character` needs some stats like intelligence and strength. So here is our first character:
```rust
struct Character {
name: String,
strength: u8,
dexterity: u8,
health: u8,
intelligence: u8,
wisdom: u8,
charm: u8,
hit_points: i8,
alignment: Alignment,
}
impl Character {
fn new(
name: String,
strength: u8,
dexterity: u8,
health: u8,
intelligence: u8,
wisdom: u8,
charm: u8,
hit_points: i8,
alignment: Alignment,
) -> Self {
Self {
name,
strength,
dexterity,
health,
intelligence,
wisdom,
charm,
hit_points,
alignment,
}
}
}
enum Alignment {
Good,
Neutral,
Evil,
}
fn main() {
let billy = Character::new("Billy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good);
}
```
Now let's imagine that we want to keep character hit points in a big vec. Maybe we'll put monster data in there too, and keep it all together. Since `hit_points` is an `i8`, we implement `Deref` so we can do all sorts of math on it. But look at how strange it looks in our `.main()` function now:
```rust
use std::ops::Deref;
// All the other code is the same until after the enum Alignment
struct Character {
name: String,
strength: u8,
dexterity: u8,
health: u8,
intelligence: u8,
wisdom: u8,
charm: u8,
hit_points: i8,
alignment: Alignment,
}
impl Character {
fn new(
name: String,
strength: u8,
dexterity: u8,
health: u8,
intelligence: u8,
wisdom: u8,
charm: u8,
hit_points: i8,
alignment: Alignment,
) -> Self {
Self {
name,
strength,
dexterity,
health,
intelligence,
wisdom,
charm,
hit_points,
alignment,
}
}
}
enum Alignment {
Good,
Neutral,
Evil,
}
impl Deref for Character { // impl Deref for Character. Now we can do any integer math we want!
type Target = i8;
fn deref(&self) -> &Self::Target {
&self.hit_points
}
}
fn main() {
let billy = Character::new("Billy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good); // Create two characters, billy and brandy
let brandy = Character::new("Brandy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good);
let mut hit_points_vec = vec![]; // Put our hit points data in here
hit_points_vec.push(*billy); // Push *billy?
hit_points_vec.push(*brandy); // Push *brandy?
println!("{:?}", hit_points_vec);
}
```
This just prints `[5, 5]`. Our code is now very strange for someone to read. We can read `Deref` just above `.main()` and figure out that `*billy` means `i8`, but what if there was a lot of code? Maybe our code is 2000 lines long, and suddenly we have to figure out why we are `.push()`ing `*billy`. `Character` is certainly more than just a smart pointer for `i8`.
Of course, it is not illegal to write `hit_points_vec.push(*billy)`, but it makes the code look very strange. Probably a simple `.get_hp()` method would be much better, or another struct that holds the characters. Then you could iterate through and push the `hit_points` for each one. `Deref` gives a lot of power but it's good to make sure that the code is logical.

Loading…
Cancel
Save