5

Rust中的智能指针:Box<T> Rc<T> Arc<T> Cell<T> RefCell<...

 1 year ago
source link: https://www.cnblogs.com/liujin-now/p/17335275.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Rust中的智能指针是什么

智能指针(smart pointers)是一类数据结构,是拥有数据所有权和额外功能的指针。是指针的进一步发展

指针(pointer)是一个包含内存地址的变量的通用概念。这个地址引用,或 ” 指向”(points at)一些其 他数据 。引用以 & 符号为标志并借用了他们所 指向的值。除了引用数据没有任何其他特殊功能。它们也没有任何额外开销,所以在Rust中应用得最多。

智能指针是Rust中一种特殊的数据结构。它与普通指针的本质区别在于普通指针是对值的借用,而智能指针通常拥有对数据的所有权。并且可以实现很多额外的功能。

Rust智能指针有什么用,解决了什么问题

它提供了许多强大的抽象来帮助程序员管理内存和并发。其中一些抽象包括智能指针和内部可变性类型,它们可以帮助你更安全、更高效地管理内存,例如Box<T> 用于在堆上分配值。Rc<T> 是一种引用计数类型,可以实现数据的多重所有权。RefCell<T> 提供内部可变性,可用于实现对同一数据的多个可变引用

它们在标准库中定义,可以用来更灵活地管理内存,智能指针的一个特点就是实现了Drop和Deref这两个trait。其中Drop trait中提供了drop方法,在析构时会去调用。Deref trait提供了自动解引用的能力,让我们在使用智能指针的时候不需要再手动解引用了

Rust有哪些常用智能指针

  • Box<T>是最简单的智能指针,它允许你在堆上分配值并在离开作用域时自动释放内存。
  • Rc<T>Arc<T>是引用计数类型,它们允许多个指针指向同一个值。当最后一个指针离开作用域时,值将被释放。Rc<T>不是线程安全的,而Arc<T>是线程安全的。

内部可变性类型允许你在不可变引用的情况下修改内部值。Rust中有几种内部可变性类型,包括Cell<T>RefCell<T>UnsafeCell<T>

  • Cell<T>是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。Cell<T>只能用于Copy类型,因为它通过复制值来实现内部可变性。
  • RefCell<T>也是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>不同,RefCell<T>可以用于非Copy类型。它通过借用检查来确保运行时的安全性。
  • UnsafeCell<T>是一个底层的内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>RefCell<T>不同,UnsafeCell<T>不提供任何运行时检查来确保安全性。因此,使用UnsafeCell<T>可能会导致未定义行为。

此外,Rust还提供了一种弱引用类型Weak<T>,它可以与Rc<T>Arc<T>一起使用来创建循环引用。Weak<T>不会增加引用计数,因此它不会阻止值被释放。

Box<T>

Box<T>是最简单的智能指针,它允许你在堆上分配值并在离开作用域时自动释放内存。

Box<T>通常用于以下情况:

  • 当你有一个类型,但不确定它的大小时,可以使用Box<T>来在堆上分配内存。例如,递归类型通常需要使用Box<T>来分配内存。
  • 当你有一个大型数据结构并希望在栈上分配内存时,可以使用Box<T>来在堆上分配内存。这样可以避免栈溢出的问题。
  • 当你希望拥有一个值并只关心它的类型而不是所占用的内存时,可以使用Box<T>。例如,当你需要将一个闭包传递给函数时,可以使用Box<T>来存储闭包。

总之,当你需要在堆上分配内存并管理其生命周期时,可以考虑使用Box<T>

下面是一个简单的例子:

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}
复制代码

这里定义了变量 b,其值是一个指向被分配在堆上的值 5 的 Box。这个程序会打印出 b = 5;在这个例子 中,我们可以像数据是储存在栈上的那样访问 box 中的数据。正如任何拥有数据所有权的值那样,当像 b 这样的 box 在 main 的末尾离开作用域时,它将被释放。这个释放过程作用于 box 本身(位于栈上) 和它所指向的数据(位于堆上)。

但是Box<T>无法同时在多个地方对同一个值进行引用

enum List { 
Cons(i32, Box), 
Nil, 
} 
use crate::List::{Cons, Nil}; 
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); 
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a)); 
}
复制代码

编译会得出如下错误: error[E0382]: use of moved value: a,因为b和c无法同时拥有a的所有权,这个时候我们要用Rc<T>

Rc<T> Reference Counted 引用计数

Rc<T>是一个引用计数类型,它允许多个指针指向同一个值。当最后一个指针离开作用域时,值将被释放。Rc<T>不是线程安全的,因此不能在多线程环境中使用。

Rc<T>通常用于以下情况:

  • 当你希望在多个地方共享数据时,可以使用Rc<T>。解决了使用Box<T>共享数据时出现编译错误
  • 当你希望创建一个循环引用时,可以使用Rc<T>Weak<T>来实现。

下面是一个简单的例子,演示如何使用Rc<T>来共享数据:

use std::rc::Rc;

fn main() {
    let data = Rc::new(vec![1, 2, 3]);
    let data1 = data.clone();
    let data2 = data.clone();
    println!("data: {:?}", data);
    println!("data1: {:?}", data1);
    println!("data2: {:?}", data2);
}
复制代码

这个例子中,我们使用Rc::new来创建一个新的Rc<T>实例。然后,我们使用clone方法来创建两个新的指针,它们都指向同一个值。由于Rc<T>实现了引用计数,所以当最后一个指针离开作用域时,值将被释放。

但是Rc<T>在多线程中容易引发线程安全问题,为了解决这个问题,又有了Arc<T>

Arc<T> Atomically Reference Counted 原子引用计数

Arc<T>是一个线程安全的引用计数类型,它允许多个指针在多个线程之间共享同一个值。当最后一个指针离开作用域时,值将被释放。

Arc<T>通常用于以下情况:

  • 当你希望在多个线程之间共享数据时,可以使用Arc<T>,是Rc<T>的多线程版本。
  • 当你希望在线程之间传递数据时,可以使用Arc<T>来实现。

下面是一个简单的例子,演示如何使用Arc<T>来在线程之间共享数据:

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let data1 = data.clone();
    let data2 = data.clone();

    let handle1 = thread::spawn(move || {
        println!("data1: {:?}", data1);
    });

    let handle2 = thread::spawn(move || {
        println!("data2: {:?}", data2);
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}
复制代码

这个例子中,我们使用Arc::new来创建一个新的Arc<T>实例。然后,我们使用clone方法来创建两个新的指针,它们都指向同一个值。接着,我们在线程中使用这些指针来访问共享数据。由于Arc<T>实现了线程安全的引用计数,所以当最后一个指针离开作用域时,值将被释放。

Weak<T> 弱引用类型

Weak<T>是一个弱引用类型,它可以与Rc<T>Arc<T>一起使用来创建循环引用。Weak<T>不会增加引用计数,因此它不会阻止值被释放。

当你希望创建一个循环引用时,可以使用Rc<T>Arc<T>Weak<T>来实现。

Weak<T>通常用于以下情况:

  • 当你希望观察一个值而不拥有它时,可以使用Weak<T>来实现。由于Weak<T>不会增加引用计数,所以它不会影响值的生命周期。

下面是一个简单的例子,演示如何使用Rc<T>Weak<T>来创建一个循环引用:

use std::rc::{Rc, Weak};

struct Node {
    value: i32,
    next: Option<Rc<Node>>,
    prev: Option<Weak<Node>>,
}

fn main() {
    let first = Rc::new(Node { value: 1, next: None, prev: None });
    let second = Rc::new(Node { value: 2, next: None, prev: Some(Rc::downgrade(&first)) });
    first.next = Some(second.clone());
}
复制代码

这个例子中,我们定义了一个Node结构体,它包含一个值、一个指向下一个节点的指针和一个指向前一个节点的弱引用。然后,我们创建了两个节点firstsecond,并使用Rc::downgrade方法来创建一个弱引用。最后,我们将两个节点连接起来,形成一个循环引用。

需要注意的是,由于Weak<T>不会增加引用计数,所以它不会阻止值被释放。当你需要访问弱引用指向的值时,可以使用upgrade方法来获取一个临时的强引用。如果值已经被释放,则upgrade方法会返回None

UnsafeCell<T>

UnsafeCell<T>是一个底层的内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>RefCell<T>不同,UnsafeCell<T>不提供任何运行时检查来确保安全性。因此,使用UnsafeCell<T>可能会导致未定义行为。

由于UnsafeCell<T>是一个底层类型,它通常不直接用于应用程序代码。相反,它被用作其他内部可变性类型(如Cell<T>RefCell<T>)的基础。

下面是一个简单的例子,演示如何使用UnsafeCell<T>来修改内部值:

use std::cell::UnsafeCell;

fn main() {
    let x = UnsafeCell::new(1);
    let y = &x;
    let z = &x;
    unsafe {
        *x.get() = 2;
        *y.get() = 3;
        *z.get() = 4;
    }
    println!("x: {}", unsafe { *x.get() });
}
复制代码

这个例子中,我们使用UnsafeCell::new来创建一个新的UnsafeCell<T>实例。然后,我们创建了两个不可变引用yz,它们都指向同一个值。接着,在一个unsafe块中,我们使用get方法来获取一个裸指针,并使用它来修改内部值。由于UnsafeCell<T>实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。

需要注意的是,由于UnsafeCell<T>不提供任何运行时检查来确保安全性,所以使用它可能会导致未定义行为。因此,在大多数情况下,你应该使用其他内部可变性类型(如Cell<T>RefCell<T>),而不是直接使用UnsafeCell<T>

Cell<T>

Cell<T>是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。Cell<T>只能用于Copy类型,因为它通过复制值来实现内部可变性。

Cell<T>通常用于以下情况:

  • 当你需要在不可变引用的情况下修改内部值时,可以使用Cell<T>来实现内部可变性。
  • 当你需要在结构体中包含一个可变字段时,可以使用Cell<T>来实现。 下面是一个简单的例子,演示如何使用Cell<T>来修改内部值:
use std::cell::Cell;

fn main() {
    let x = Cell::new(1);
    let y = &x;
    let z = &x;
    x.set(2);
    y.set(3);
    z.set(4);
    println!("x: {}", x.get());
}
复制代码

这个例子中,我们使用Cell::new来创建一个新的Cell<T>实例。然后,我们创建了两个不可变引用yz,它们都指向同一个值。接着,我们使用set方法来修改内部值。由于Cell<T>实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。

需要注意的是,由于Cell<T>通过复制值来实现内部可变性,所以它只能用于Copy类型。如果你需要在不可变引用的情况下修改非Copy类型的值,可以考虑使用RefCell<T>

RefCell<T>

RefCell<T>是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>不同,RefCell<T>可以用于非Copy类型。

RefCell<T>通过借用检查来确保运行时的安全性。当你尝试获取一个可变引用时,RefCell<T>会检查是否已经有其他可变引用或不可变引用。如果有,则会发生运行时错误。

RefCell<T>通常用于以下情况:

  • 当你需要在不可变引用的情况下修改内部值时,可以使用RefCell<T>来实现内部可变性。

  • 当你需要在结构体中包含一个可变字段时,可以使用RefCell<T>来实现。

下面是一个简单的例子,演示如何使用RefCell<T>来修改内部值:

use std::cell::RefCell;

fn main() {
    let x = RefCell::new(vec![1, 2, 3]);
    let y = &x;
    let z = &x;
    x.borrow_mut().push(4);
    y.borrow_mut().push(5);
    z.borrow_mut().push(6);
    println!("x: {:?}", x.borrow());
}
复制代码

这个例子中,我们使用RefCell::new来创建一个新的RefCell<T>实例。然后,我们创建了两个不可变引用yz,它们都指向同一个值。接着,我们使用borrow_mut方法来获取一个可变引用,并使用它来修改内部值。由于RefCell<T>实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。

需要注意的是,由于RefCell<T>通过借用检查来确保运行时的安全性,所以当你尝试获取一个可变引用时,如果已经有其他可变引用或不可变引用,则会发生运行时错误。from刘金,转载请注明原文链接。感谢!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK