Smart pointers in rust

We know that smart pointers in C++ include shared_ptr, unique_ptr, weak_ptr, etc. In rust, you will further understand the meaning of smart pointers.
Let's review the pointer first: its value is a memory address. To access the memory address it points to, you need to dereference it. In theory, you can dereference to any data type.
Smart Pointers
Smart pointers, in addition to pointers to data, also have source data to provide additional processing power.
The difference between smart pointers and fat pointers : A smart pointer must be a fat pointer, but a fat pointer is not necessarily a smart pointer.

Insert image description hereString (smart pointer) has ownership of the value on the heap, while &str (fat pointer) has no ownership. This is the difference between smart pointers and ordinary fat pointers in Rust.

pub struct String { vec: Vec, } Judging from the definition of String, it is obviously a structure. How come it has become a smart pointer? This is because String implements the two traits of Deref and DerefMut that we just learned yesterday, which causes it to get &str when dereferencing.


impl ops::Deref for String {
type Target = str;

fn deref(&self) -> &str {
    unsafe { str::from_utf8_unchecked(&self.vec) }
}

}
Since data is allocated on the heap, String also needs to be recycled accordingly for its allocated resources. String uses Vec internally, so it can rely on Vec's ability to release heap memory.
Now we find that in Rust, any data structure that needs to recycle resources and implements the three traits of Deref/DerefMut/Drop is a smart pointer.

With that said, we find that the following are smart pointers

Box and Vec for allocating memory on the heap,
Rc and Arc for reference counting
, and many other data structures PathBuf, Cow <'a B>, MutexGuard, etc.

BOX

It is the most basic way to allocate memory on the heap in Rust . Most other data types that include heap memory allocation are internally completed through Box, such as Vec.
Let's first review how C allocates heap memory. C provides malloc/calloc/realloc/free to handle memory allocation, but in the end, in order to safely release the memory, it puts a relatively large mental burden on us programmers.

C++ discovered this problem, so it created a smart pointer unique that can release the heap memory when the pointer exits the scope, thus ensuring single ownership of the heap memory. This is the predecessor of Box.
We see that in the definition of Box, there is Unique, which represents the C++ reference.

Insert image description here
The Box that allocates memory on the heap actually has a default generic parameter A, which needs to satisfy the Allocator trait. This actually specifies a memory allocator, and the default is Global. Of course, it can also be replaced with your own memory allocator.
The Allocator trait provides many methods:
allocate is the main method, used to allocate memory, corresponding to C's malloc/calloc;
deallocate, used to release memory, corresponding to C's free;
and grow/shrink, used to expand or shrink the allocated heap memory, corresponding to C's realloc.
Let’s take a look at Box’s Drop implementation:

#[stable(feature = “rust1”, since = “1.0.0”)]
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> { fn drop(&mut self) { // FIXME: Do nothing, drop is currently performed by compiler. } } You can see that this is an empty implementation. Like our usual development, we first determine the interface, and then each implements their own logic in their own services. Is this the idea of ​​interface-oriented programming we have been talking about?




Cow/MutexGuard

Cow<'a, B>
This is a smart pointer used to provide Clone-on-Write, which is very similar to the copy-on-write of virtual memory management. **Mostly used in scenarios with more reading and less writing.
The code definition is as follows:
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized { Borrowed(&'a B), Owned( ::Owned), } Contains a read-only borrow. If the caller needs ownership to make modification operations, he will clone the borrowed data. I don’t quite understand, so I won’t go into details.




MutexGuard is another interesting type of smart pointer: it not only provides a good user experience through Deref, but also uses the Drop trait to ensure that resources other than memory used are released when exiting.

The MutexGuard structure is generated when calling Mutex::lock:
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> { unsafe { self.inner.raw_lock(); MutexGuard::new(self) } } Get the lock first, if you can't get it, wait. Once you get it, give the Mutex reference to MutexGuard.






Smart pointers like MutexGuard have many uses. For example, to create a connection pool, you can recycle checkout connections in the Drop trait and put them back into the connection pool.

The difference between Box, Rc, arc CELL RefCell in Rust

Box is an exclusive ownership smart pointer , similar to C++'s unique_ptr. Resources are allocated on the heap, relying on Deref and Drop to manage resources on the heap, with zero runtime overhead.
Rc is a shared ownership smart pointer , similar to C++'s shared_ptr resource. Allocated on the heap, relying on Deref and Drop to manage resources on the heap, using the reference counting algorithm
Arc is a thread-safe shared ownership smart pointer , similar to C++'s shared_ptr + mutex resources are allocated on the heap, relying on Deref and Drop to manage Resources on the heap, using reference counting algorithms

Cell and RefCell can achieve internal mutability :
1.Cell wraps the Copy value, and has no borrow check;
2.RefCell wraps any type of value, has runtime borrow check, and needs to be locked with borrow or borrow_mut, respectively providing an immutable or Mutable references;
3. Neither Cell nor RefCell are thread-safe.
Compared with Cell, RefCell internally maintains a reference count of the packaging object. When an immutable reference is obtained through RefCell's borrow, the internal reference count is increased by one. When the obtained reference leaves the scope, the internal reference count is decreased by one; since
Cell does not introduce a reference count, Cell needs to satisfy T: Copy.
impl<T: Copy> Cell {}
For Cell, what is obtained through get is a copy of the original object, while set replaces the old object with a new object.

use std::cell::Cell;

#[derive(Debug)]
struct Person {
    
    
    name: String,
    age: Cell<usize>,
}

fn main() {
    
    
    let person = Person {
    
    
        name: "XiaoMing".to_string(),
        age: Cell::new(18),
    };

    let p = &person;
    p.age.set(20);
    // Person {
    
     name: "XiaoMing", age: Cell {
    
     value: 20 } }
    println!("{:?}", p);
}
use std::cell::RefCell;

#[derive(Debug)]
struct Person {
    
    
    name: String,
    age: RefCell<usize>,
}

fn main() {
    
    
    let person = Person {
    
    
        name: "XiaoMing".to_string(),
        age: RefCell::new(18),
    };

    let p = &person;
    *p.age.borrow_mut() = 20;
    // Person {
    
     name: "XiaoMing", age: Cell {
    
     value: 20 } }
    println!("{:?}", p);
}

You can see that the role of cell and refcell is that although p is an immutable borrow, you can use cell and refcell to modify the internal value of p. The difference is that the cell obtains a copy of the original object through get, which is suitable for types that implement Copy, or small structs, because the get method directly copies bit by bit.
refcell returns a mutable reference through borrow checking. It is suitable for types that do not implement Copy, or large structs. This is used if Cell is not easy to use.

Guess you like

Origin blog.csdn.net/weixin_53344209/article/details/130053246