Rust中的智能指针是什么
智能指针(smart pointers)是一类数据结构,是拥有数据所有权和额外功能的指针。是指针的进一步发展
指针(pointer)是一个包含内存地址的变量的通用概念。这个地址引用,或 ” 指向”(points at)一些其 他数据 。引用以 & 符号为标志并借用了他们所 指向的值。除了引用数据没有任何其他特殊功能。它们也没有任何额外开销,所以在Rust中应用得最多。
智能指针是Rust中一种特殊的数据结构。它与普通指针的本质区别在于普通指针是对值的借用,而智能指针通常拥有对数据的所有权。并且可以实现很多额外的功能。
Rust智能指针有什么用,解决了什么问题
它提供了许多强大的抽象来帮助程序员管理内存和并发。其中一些抽象包括智能指针和内部可变性类型,它们可以帮助你更安全、更高效地管理内存,例如Box
用于在堆上分配值。Rc
是一种引用计数类型,可以实现数据的多重所有权。RefCell
提供内部可变性,可用于实现对同一数据的多个可变引用
它们在标准库中定义,可以用来更灵活地管理内存,智能指针的一个特点就是实现了Drop和Deref这两个trait。其中Drop trait中提供了drop方法,在析构时会去调用。Deref trait提供了自动解引用的能力,让我们在使用智能指针的时候不需要再手动解引用了
Rust有哪些常用智能指针
Box
是最简单的智能指针,它允许你在堆上分配值并在离开作用域时自动释放内存。Rc
和Arc
是引用计数类型,它们允许多个指针指向同一个值。当最后一个指针离开作用域时,值将被释放。Rc
不是线程安全的,而Arc
是线程安全的。
内部可变性类型允许你在不可变引用的情况下修改内部值。Rust中有几种内部可变性类型,包括Cell
,RefCell
和UnsafeCell
。
Cell
是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。Cell
只能用于Copy
类型,因为它通过复制值来实现内部可变性。RefCell
也是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell
不同,RefCell
可以用于非Copy
类型。它通过借用检查来确保运行时的安全性。UnsafeCell
是一个底层的内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell
和RefCell
不同,UnsafeCell
不提供任何运行时检查来确保安全性。因此,使用UnsafeCell
可能会导致未定义行为。
此外,Rust还提供了一种弱引用类型Weak
,它可以与Rc
或Arc
一起使用来创建循环引用。Weak
不会增加引用计数,因此它不会阻止值被释放。
Box
Box
是最简单的智能指针,它允许你在堆上分配值并在离开作用域时自动释放内存。
Box
通常用于以下情况:
- 当你有一个类型,但不确定它的大小时,可以使用
Box
来在堆上分配内存。例如,递归类型通常需要使用Box
来分配内存。 - 当你有一个大型数据结构并希望在栈上分配内存时,可以使用
Box
来在堆上分配内存。这样可以避免栈溢出的问题。 - 当你希望拥有一个值并只关心它的类型而不是所占用的内存时,可以使用
Box
。例如,当你需要将一个闭包传递给函数时,可以使用Box
来存储闭包。
总之,当你需要在堆上分配内存并管理其生命周期时,可以考虑使用Box
。
下面是一个简单的例子:
fn main() { let b = Box::new(5); println!("b = {}", b);}复制代码
这里定义了变量 b,其值是一个指向被分配在堆上的值 5 的 Box。这个程序会打印出 b = 5;在这个例子 中,我们可以像数据是储存在栈上的那样访问 box 中的数据。正如任何拥有数据所有权的值那样,当像 b 这样的 box 在 main 的末尾离开作用域时,它将被释放。这个释放过程作用于 box 本身(位于栈上) 和它所指向的数据(位于堆上)。
但是Box
无法同时在多个地方对同一个值进行引用
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
Rc
Reference Counted 引用计数
Rc
是一个引用计数类型,它允许多个指针指向同一个值。当最后一个指针离开作用域时,值将被释放。Rc
不是线程安全的,因此不能在多线程环境中使用。
Rc
通常用于以下情况:
- 当你希望在多个地方共享数据时,可以使用
Rc
。解决了使用Box
共享数据时出现编译错误 - 当你希望创建一个循环引用时,可以使用
Rc
和Weak
来实现。
下面是一个简单的例子,演示如何使用Rc
来共享数据:
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
实例。然后,我们使用clone
方法来创建两个新的指针,它们都指向同一个值。由于Rc
实现了引用计数,所以当最后一个指针离开作用域时,值将被释放。
但是Rc
在多线程中容易引发线程安全问题,为了解决这个问题,又有了Arc
Arc
Atomically Reference Counted 原子引用计数
Arc
是一个线程安全的引用计数类型,它允许多个指针在多个线程之间共享同一个值。当最后一个指针离开作用域时,值将被释放。
Arc
通常用于以下情况:
- 当你希望在多个线程之间共享数据时,可以使用
Arc
,是Rc
的多线程版本。 - 当你希望在线程之间传递数据时,可以使用
Arc
来实现。
下面是一个简单的例子,演示如何使用Arc
来在线程之间共享数据:
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
实例。然后,我们使用clone
方法来创建两个新的指针,它们都指向同一个值。接着,我们在线程中使用这些指针来访问共享数据。由于Arc
实现了线程安全的引用计数,所以当最后一个指针离开作用域时,值将被释放。
Weak
弱引用类型
Weak
是一个弱引用类型,它可以与Rc
或Arc
一起使用来创建循环引用。Weak
不会增加引用计数,因此它不会阻止值被释放。
当你希望创建一个循环引用时,可以使用Rc
或Arc
和Weak
来实现。
Weak
通常用于以下情况:
当你希望观察一个值而不拥有它时,可以使用
Weak
来实现。由于Weak
不会增加引用计数,所以它不会影响值的生命周期。
下面是一个简单的例子,演示如何使用Rc
和Weak
来创建一个循环引用:
use std::rc::{Rc, Weak};struct Node { value: i32, next: Option<Rc>, prev: Option<Weak>,}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
结构体,它包含一个值、一个指向下一个节点的指针和一个指向前一个节点的弱引用。然后,我们创建了两个节点first
和second
,并使用Rc::downgrade
方法来创建一个弱引用。最后,我们将两个节点连接起来,形成一个循环引用。
需要注意的是,由于Weak
不会增加引用计数,所以它不会阻止值被释放。当你需要访问弱引用指向的值时,可以使用upgrade
方法来获取一个临时的强引用。如果值已经被释放,则upgrade
方法会返回None
。
UnsafeCell
UnsafeCell
是一个底层的内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell
和RefCell
不同,UnsafeCell
不提供任何运行时检查来确保安全性。因此,使用UnsafeCell
可能会导致未定义行为。
由于UnsafeCell
是一个底层类型,它通常不直接用于应用程序代码。相反,它被用作其他内部可变性类型(如Cell
和RefCell
)的基础。
下面是一个简单的例子,演示如何使用UnsafeCell
来修改内部值:
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
实例。然后,我们创建了两个不可变引用y
和z
,它们都指向同一个值。接着,在一个unsafe
块中,我们使用get
方法来获取一个裸指针,并使用它来修改内部值。由于UnsafeCell
实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。
需要注意的是,由于UnsafeCell
不提供任何运行时检查来确保安全性,所以使用它可能会导致未定义行为。因此,在大多数情况下,你应该使用其他内部可变性类型(如Cell
和RefCell
),而不是直接使用UnsafeCell
。
Cell
Cell
是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。Cell
只能用于Copy
类型,因为它通过复制值来实现内部可变性。
Cell
通常用于以下情况:
- 当你需要在不可变引用的情况下修改内部值时,可以使用
Cell
来实现内部可变性。 - 当你需要在结构体中包含一个可变字段时,可以使用
Cell
来实现。 下面是一个简单的例子,演示如何使用Cell
来修改内部值:
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
实例。然后,我们创建了两个不可变引用y
和z
,它们都指向同一个值。接着,我们使用set
方法来修改内部值。由于Cell
实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。
需要注意的是,由于Cell
通过复制值来实现内部可变性,所以它只能用于Copy
类型。如果你需要在不可变引用的情况下修改非Copy
类型的值,可以考虑使用RefCell
。
RefCell
RefCell
是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell
不同,RefCell
可以用于非Copy
类型。
RefCell
通过借用检查来确保运行时的安全性。当你尝试获取一个可变引用时,RefCell
会检查是否已经有其他可变引用或不可变引用。如果有,则会发生运行时错误。
RefCell
通常用于以下情况:
当你需要在不可变引用的情况下修改内部值时,可以使用
RefCell
来实现内部可变性。当你需要在结构体中包含一个可变字段时,可以使用
RefCell
来实现。
下面是一个简单的例子,演示如何使用RefCell
来修改内部值:
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
实例。然后,我们创建了两个不可变引用y
和z
,它们都指向同一个值。接着,我们使用borrow_mut
方法来获取一个可变引用,并使用它来修改内部值。由于RefCell
实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。
需要注意的是,由于RefCell
通过借用检查来确保运行时的安全性,所以当你尝试获取一个可变引用时,如果已经有其他可变引用或不可变引用,则会发生运行时错误。from刘金,转载请注明原文链接。感谢!