Dereference smart Pointers through the Deref trait

Implements the Deref trait to customize the dereference operator * behavior. By implementing Deref, smart Pointers can be treated as regular references.

The use of dereferencing

Comparison with a quote:

let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, y); // error, cannot compare a reference to an integer
assert_eq!(5, *y); // Use the dereference operator to jump to the value of the reference: x
Copy the code

Change the original value with a reference:

let mut a = 1;
let b = &mut a;
*b = 2; // b is a mutable reference to a. Use the dereference operator to jump to variable a and reassign a to 2
println!("{}", a); / / 2
Copy the code

To use in loops:

let mut arr = [1;5];
for item in arr.iter_mut() {
  *item *= 2 // Use the dereference operator to jump to the value pointed to by each item of the member, multiplied by 2
}
println!("{:? }", arr); // [2, 2, 2, 2, 2]
Copy the code

Box supports dereferencing operators:

let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, y); // error, Box references cannot be compared with integers
assert_eq!(5, *y); // Use the dereference operator to jump to the value pointed to by the Box reference: x
Copy the code

Custom smart pointer

Implement a custom pointer similar to Box:

struct MyBox<T>(T); // MyBox is a tuple structure with a single member
impl<T> MyBox<T> {
  fn new(value: T) -> MyBox<T> {
    Self(value)
  }
}
Copy the code

Dereference like Box:

let x = 5;
let y = MyBox::new(x);
assert_eq!(5, y); // error, cannot compare MyBox reference with integer type
assert_eq!(5, *y); // error, the MyBox type cannot be dereferenced
Copy the code

Make types dereferenceable by implementing the Deref trait:

use std::ops::Deref;
impl<T> Deref for MyBox<T> {
  type Target = T; // Type annotations are described later, so you can ignore them for now
  fn deref(&self) -> &T { // Returns a reference to the member
    &self.0}}assert_eq!(5, *y); // In fact *y will be compiled to *(y.deref()).
Copy the code

The behavior of *

The operator contains two actions: a naive dereference + deref()

Deref returns a reference because if a value is returned, it will be removed from self. In most scenarios where dereferencing operators are used, we do not want to take ownership of the value inside MyBox. The deref function can be thought of as: Get “reference type data” for “dereferencing”

Implicit dereference conversions of functions and methods

Deref Coercion is a convenience feature Rust provides for arguments to functions and methods.

The addition of the type T implements the Deref trait, which converts “references to T” to “references to T generated by Deref operations.” The dereference conversion occurs automatically when we pass a “value reference of some type” as an argument to a function or method, but the type passed in does not match the parameter type. The compiler inserts a series of deref method calls to convert our supplied type to the required type for the parameter.


Dereferencing String:

fn hello(s: &str) {
  println!("hello {}", s)
}
let s = String::from("world");
hello(s); // error, expecting a & STR, but passing String
hello(&s); // Hello world, rust will automatically use &String deref, dereference & STR
Copy the code

Dereference MyBox:

let m = MyBox(s);
hello(m); // error, expecting a & STR, but passing MyBox(String)
hello(&m); // Hello world, &mybox (String) is dereferenced to &string, &str is dereferenced to &string
Copy the code

If there is no automatic dereference function, you can only dereference manually:

leta = &((*m)[..] );// Dereference MyBox
      
        to get String
      
// Then, pass & and [..] To get a slice of the String containing the entire String to match the signature of the Hello function
hello(a); // hello world
Copy the code

Dereference conversion and variability

The Deref trait lets you override operators for immutable references. Similarly, the DerefMut trait lets you override mutable reference operators.


Make mutable types dereferenceable by implementing the DerefMut trait:

use std::ops::DerefMut;
impl<T> DerefMut for MyBox<T> {
  fn deref_mut(&mut self) - > &mut T {
    &mut self.0}}Copy the code

To implement the DerefMut trait, you must already implement the Deref trait


Dereference with mutable types:

let s = String::from("world");
let mut m = MyBox::new(s);
fn hi(s: &mut str) {
  println!("hello {}", s)
}
hi(&mut m); // Resolve mutable references automatically with deref_mut
hi(&m); // error, it is not allowed to turn immutable references into mutable references
Copy the code

Conclusion:

Rust performs dereference conversions when types and traits meet one of three conditions:

  • Allow &t to be converted to &u when T: Deref

    .
    =u>
  • Allow &mut T to be converted to &mut U when T: DerefMut

    .
    =u>
  • Allow &mut T to be converted to &u when T: Deref

    .
    =u>

Why &mut T can be converted to &u, but not &t to &mut U:

According to the borrowing rule, if there is a mutable reference, it must be unique (otherwise the program will not compile). Converting a mutable reference to an immutable reference certainly does not break the borrowing rule, but converting an immutable reference to a mutable reference requires that the reference be unique, which the borrowing rule does not guarantee.