1. What is a smart pointer
A pointer is a variable that stores memory addresses. This address points to some other data.
Smart pointers are a type of data structures that are similar to pointers but have additional functionality. The concept of smart pointer originated in C++. The Rust standard library provides many smart pointers, such as String andVec<T>
, Although we don't call them that, these types are smart pointers.
Smart pointers are usually implemented using structures. The difference between smart pointers and conventional structures is that smart pointers implement Deref and Drop trait. Deref trait makes smart pointers behave like references, so that code can be written for both references and smart pointers. Drop trait allows us to customize the behavior of smart pointers when they leave scope.
In Rust, one difference between a reference and a smart pointer is that a reference is a class of pointers that only borrow data; a smart pointer has ownership of the data.
2. Some of the most commonly used smart pointers
1.Box<T>
, used to allocate on the heap
2.Rc<T>
, a reference count type whose data can have multiple owners
3.Ref<T>
andRefMut<T>
,passRefCell<T>
Visit (RefCell<T>
Is a type that executes borrowing rules at runtime rather than at compile time)
(I) Box pointer
Box<T>
Type is a smart pointer because it implements Deref trait and Drop trait.
The box puts the value on the heap instead of the stack. What remains on the stack is a pointer to the heap data. Box has no performance losses except that data is stored on the heap instead of the stack. But there aren't many extra features.
1. Create a Box
Create using new function
example
fn main() { let var_i32 = 5; // The default data is saved on the stack let b = Box::new(var_i32); // After using Box, the data will be stored on the heap println!("b = {}", b); }
b When it leaves scope, it will be automatically released. This release consists of b itself (on the stack) and the data it points to (on the heap).
2. Use box
Use box like you use a reference.
Use the dereference operator * Dereference box
fn main() { let x = 5; // Value type data let y = Box::new(x); // y is a smart pointer pointing to data stored on the heap 5 println!("{}",5==x); println!("{}",5==*y); // In order to access the specific data stored in y, dereference is required} The compilation and operation results are as follows true true Use directly 5 == y Will returnfalse
3. Create recursive types using Box
Rust needs to know how much space the type takes up at compile time. A type that cannot be known at compile time is a recursive type, and part of its value can be another value of its own type. This kind of nesting can be infinite, so Rust doesn't know how much space it takes for the recursive type. However, the box has a known size, so by inserting the box into the recursive type definition, you can create a recursive type. A common recursive type is linked list.
Example
enum List { Cons(i32, List), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil)));//Use this list to store 1, 2, 3}
The first Cons stores 1 and another List value. This List is a Cons value, which stores 2 and the next List value. This list is another cons, which stores 3 and lists with value Nil. This code compiles error. Because this type "has infinite size".
becauseBox<T>
is a pointer whose size is determined, so use Box as a member of Cons, so that the size of List is determined.
enum List { Cons(i32, Box<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
Three, two characteristics
(I) Deref Trait
It is a feature provided by the Rust standard library.
After implementing Deref, smart pointers can be used as references, which is equivalent to overloading the dereference operator*.
Deref contains the deref() method.
The deref() method is used to refer to a self instance and return a pointer to internal data.
example
use std::ops::Deref; struct DerefExample<T> { value: T } impl<T> Deref for DerefExample<T> { type Target = T; fn deref(&self) -> &Self::Target { & } } let x = DerefExample { value: 'a' }; assert_eq!('a', *x);
example
use std::ops::Deref; struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x:T)-> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } fn main() { let x = 5; let y = MyBox::new(x); // Call new() to return to create a structure instance println!("5==x is {}",5==x); println!("5==*y is {}",5==*y); // Decide y println!("x==*y is {}",x==*y); // Decide y} The compilation and operation results are as follows 5==x is true 5==*y is true x==*y is true
Every time you use *, the * operator is replaced by calling the deref method first and then using the * dereference operation, and it will only happen once, and the * operator will not be replaced infinitely recursively. The * operator will be dereferenced to the value of type i32 and stops.
trait is used to overload the * operators of mutable references
Implicit conversion
Deref implicit conversion converts a reference of a type that implements Deref to a reference of another type. For example, convert &String to &str, because String implements Deref so it can return &str. Deref casting is a convenient operation of Rust on function or method parameters, and can only be used to implement Deref types. The type is automatically converted when this particular type of reference is passed as an actual parameter to a function that is different from the formal parameter type. At this time, a series of deref methods will be called, converting the type we provide to the type required by the formal parameter.
Deref implicit conversion allows Rust programmers to not use too much & and * when calling functions. This function makes it convenient for us to write code that acts on references or smart pointers at the same time.
Example
//It's the MyBox<T> abovefn hello(name: &str) { println!("Hello, {name}!"); } let m = MyBox::new(String::from("Rust")); hello(&m);
Because Deref implicit conversion, useMyBox<String>
References to , as parameters are feasible.
becauseMyBox<T>
Deref is implemented, Rust can use deref to&MyBox<String>
Change to &String. String also implements Deref, and Rust calls deref again to turn &String into &str, which conforms to the definition of the hello function.
If there is no Deref cast,&MyBox<String>
Passing a value of type to the hello function, you have to write the following code
let m = MyBox::new(String::from("Rust")); hello(&(*m)[..]);
(*m)
WillMyBox<String>
Dereferenced as String, then&and[..]
Convert String to &str.
Without Deref casting, all these symbols will be difficult to read, write and understand. Deref casts will automatically perform these conversions. These transformations occur at compile time, so there is no runtime loss!
There are three cases of implicit conversion of Deref:
(1) From &T to &U when T implements Deref Target=U.
(2) When T implements DerefMut Target=U, from &mut T to &mut U.
(3) When T implements Deref Target=U, from &mut T to &U.
The first case shows that if there is a &T, and T implements a Deref that returns a U-type, you can get &U directly.
The second case shows that variable references also behave the same.
The third case strongly converts mutable references to immutable references. But the other way around is not possible. Immutable references can never be forced into mutable references.
In these three cases, T type automatically implements all methods of U type.
(II) Drop Trait
The destructor in Rust is the drop() method provided by the Drop trait.
Drop Trait has only one method drop() .
A structure that implements the Drop trait will call the drop() method when it leaves its scope.
example
use std::ops::Deref; struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x:T)->MyBox<T>{ MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -< &T { &self.0 } } impl<T> Drop for MyBox<T>{ fn drop(&mut self){ println!("dropping MyBox object from memory "); } } fn main() { let x = 50; MyBox::new(x); MyBox::new("Hello"); } The compilation and operation results are as follows dropping MyBox object from memory dropping MyBox object from memory
This is the article about the specific use of rust smart pointers. For more related rust smart pointers, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!