• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Rust- 智能指针

武飞扬头像
青衫客36
帮助1

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<T> 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<T> 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<T>, Rc<T>, and RefCell<T> are some of the commonly used smart pointers in Rust.

  1. Box<T>: This is the simplest kind of smart pointer. It allows you to put data on the heap rather than the stack. Box<T> 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<T>: “Rc” stands for reference counting. The Rc<T> 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<T>: 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<T> 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<T>, Mutex<T>, 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<T> 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<T> 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<T> can be used in this case, as the size of Box<T> itself is known regardless of the size of the data on the heap it points to.

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

Here is a simple example of Box<T>:

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<T> 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<T> 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<T> provides.

Note1: The borrow_mut() function is a method of RefCell<T> 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<T> 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<T>), while borrow_mut() gives you a mutable reference (RefMut<T>).

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.

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfifjjk
系列文章
更多 icon
同类精品
更多 icon
继续加载