Rust- 智能指针

Smart pointers

A smart pointer is a data structure that not only acts like a pointer but provides additional functionality. This “smartness” comes from the fact that smart pointers encapsulate additional logical or semantic rules, which are automatically applied to simplify memory or resource management tasks.

While different programming languages implement smart pointers in various ways, they all share a common goal: to manage the life cycle of the resources they point to.

Here are some key features of smart pointers:

  1. Ownership: Smart pointers keep track of the memory they point to, and can automatically reclaim (or “free”) that memory when it’s no longer needed. This can greatly simplify memory management, as the programmer doesn’t need to manually release memory, reducing the risk of memory leaks.

  2. Reference Counting: Some smart pointers, like the Rc in Rust or shared_ptr in C++, keep a count of the number of references (i.e., pointers) to an object. This object is only deallocated when there are no more references to it.

  3. Dereferencing: Like regular pointers, smart pointers implement the dereferencing operation, allowing access to the data they point to.

  4. Polymorphism: In object-oriented languages, smart pointers can be used to enable polymorphism when pointing to base and derived class objects.

  5. Thread Safety: Some smart pointers, like Arc in Rust or std::atomic_shared_ptr in C++20, are thread-safe, meaning they can be safely used in concurrent programming.

In Rust, smart pointers are essential components due to the language’s focus on safety and zero-cost abstractions. Box, Rc, and RefCell are some of the commonly used smart pointers in Rust.

  1. Box: This is the simplest kind of smart pointer. It allows you to put data on the heap rather than the stack. Box is often used in Rust in two scenarios: when you have a type of known size but need to store a value of a type that might have an unknown size at compile time, or when you have a large data item and want to transfer ownership to a function without copying the data to prevent stack overflow.

  2. Rc: “Rc” stands for reference counting. The Rc smart pointer allows your program to have multiple read-only references to the same data item on the heap. It keeps track of the number of references to the data on the heap, and once the reference count goes to zero, meaning there are no more references to the data, it cleans up the data.

  3. RefCell: Rust performs borrow checking at compile time which ensures that references to data obey the rules of either one write or multiple reads. However, sometimes you may need to do such checking at runtime. RefCell provides this functionality. This means that while you can borrow mutable references at runtime, your program will panic if you violate the rules of one write or multiple reads.

These are some of the basic smart pointers in Rust. There are several other smart pointers, like Arc, Mutex, etc., which are all designed to deal with ownership and concurrency issues in Rust.

It’s worth noting that while these types are called “smart pointers”, they’re not limited to mimicking pointer-like behavior. They’re actually more general constructs often used to add structure to a value, such as tracking reference counts, thread-safe access, and more.

Box< T >

Box is a very important smart pointer in Rust. It allows you to store data on the heap rather than the stack. This is mainly used in Rust for the following two scenarios:

  1. Moving data to the heap: By default, Rust stores data on the stack. However, stack space is limited and the data on the stack is immediately removed when it goes out of scope. If you need to store a large amount of data in memory or need to pass data between scopes without losing ownership, you can use Box to move the data to the heap.

  2. Storing dynamically sized types: In Rust, the size of all types must be known at compile time. However, there might be cases where you want to store a type whose size is not known at compile time. Box can be used in this case, as the size of Box itself is known regardless of the size of the data on the heap it points to.

When using Box, Rust will automatically clean up the data on the heap when the Box goes out of scope, meaning you do not need to manually manage memory.

Here is a simple example of Box:

fn main() {
    let b = Box::new(5); // b is a pointer to a box in the heap
    println!("b = {}", b);
}

In this example, the integer 5 is stored on the heap, rather than on the stack. The variable b is a pointer to the value stored on the heap. When b goes out of scope, Rust’s automatic memory management system cleans up the data on the heap, so you do not need to manually free the memory.

use std::ops::Deref;
fn main() {
    /*
        如果一个结构体实现了deref和drop的Trait,那他们就不是普通结构体了。
        Rust提供了堆上存储数据的能力并把这个能力封装到了Box中。
        把栈上的数据搬到堆上的能力,就叫做装箱。

        Box可以把数据存储到堆上,而不是栈上,box 装箱,栈还是包含指向堆上数据的指针。
     */
    let a = 6;
    let b = Box::new(a);
    println!("b = {}", b); // b = 6

    let price1 = 158;
    let price2 = Box::new(price1);
    println!("{}", 158 == price1);  // true
    println!("{}", 158 == *price2); // true

    let x = 666;
    let y = CustomBox::new(x);

    println!("666 == x is {}", 666 == x);   // 666 == x is true
    println!("666 == *y is {}", 666 == *y); // 666 == *y is true
    println!("x == *y is {}", x == *y);     // x == *y is true
}

struct CustomBox<T> {
    value: T
}

impl <T> CustomBox<T> {
    fn new(v: T) -> CustomBox<T> {
        CustomBox { value: v }
    }
}

impl <T> Deref for CustomBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.value
    }
}

impl <T> Drop for CustomBox<T> {
    fn drop(&mut self) {
        println!("drop CustomBox 对象!")
    }
}

Rc < T >

Rc stands for ‘Reference Counting’. It’s a smart pointer that allows you to have multiple owners of the same data. When each owner goes out of scope, the reference count is decreased. When the count reaches zero, the data is cleaned up.

Here’s a simple example:

use std::rc::Rc;

fn main() {
    let data = Rc::new("Hello, World!"); // data now has a reference count of 1

    {
        let _clone = Rc::clone(&data); // data now has a reference count of 2
        println!("Inside the scope, data is: {}", *_clone);
    } // _clone goes out of scope and data's reference count decreases to 1

    println!("Outside the scope, data is: {}", *data);
} // data goes out of scope and its reference count decreases to 0, the data is cleaned up

RefCell < T >

RefCell provides ‘interior mutability’, a design pattern in Rust that allows you to mutate data even when there are immutable references to that data. It enforces the borrowing rules at runtime instead of compile time.

Here’s a simple example:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    {
        let mut mutable_reference = data.borrow_mut();
        *mutable_reference += 1;
        println!("Inside the scope, data is: {}", *mutable_reference);
    } // mutable_reference goes out of scope, the lock is released

    println!("Outside the scope, data is: {}", *data.borrow());
}

In this example, you can see that we borrowed a mutable reference to the data inside RefCell using borrow_mut, modified it, and then the lock was automatically released when the mutable reference went out of scope. This demonstrates the dynamic checking that RefCell provides.

Note1: The borrow_mut() function is a method of RefCell in Rust. This function is used to gain mutable access to the underlying value that the RefCell wraps around.

The RefCell struct in Rust enforces the borrow rules at runtime, allowing you to have multiple immutable references or one mutable reference at any point in time. If you attempt to violate these rules, your program will panic at runtime.

When you call borrow_mut() on a RefCell, you get a RefMut smart pointer that allows mutable access to the underlying data. Once this RefMut is dropped (which happens automatically when it goes out of scope), others can then borrow the RefCell again.

Here’s an example:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    {
        let mut mutable_reference = data.borrow_mut();
        *mutable_reference += 1;
        println!("Inside the scope, data is: {}", *mutable_reference);
    } // mutable_reference goes out of scope, the lock is released

    println!("Outside the scope, data is: {}", *data.borrow());
}

In this example, mutable_reference is a mutable reference to the integer wrapped in data, which is a RefCell. We increment the integer by 1, and after mutable_reference goes out of scope, we can borrow data again.

Note2: The borrow() function is a method of RefCell in Rust. This function is used to gain immutable access to the underlying value that the RefCell wraps around.

Similar to borrow_mut(), it provides a way to access the data inside the RefCell. The difference is that borrow() gives you an immutable reference (Ref), while borrow_mut() gives you a mutable reference (RefMut).

The RefCell checks at runtime to make sure the borrowing rules are not violated, which are:

  1. You may have either one mutable reference or any number of immutable references at the same time, but not both.
  2. References must always be valid.

If you try to call borrow() when the value is already mutably borrowed (through borrow_mut()), or try to call borrow_mut() when the value is immutably borrowed (through borrow()), your program will panic at runtime.

Here’s how you might use borrow():

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    {
        let mutable_reference = data.borrow_mut();
        *mutable_reference += 1;
    } // mutable_reference goes out of scope, the lock is released

    let immutable_reference = data.borrow();
    println!("Data is: {}", *immutable_reference);
} 

In this example, we first use borrow_mut() to mutate the value inside data, then we use borrow() to get an immutable reference to the data.

你可能感兴趣的:(Rust,rust)