什么指针?什么是智能指针?
指针,是保存内存地址的变量。Rust中最常见的指针是引用。引用没有值的所有权,它只能借用数据。 在栈上的值,当值离开作用域,这个值就会被清理掉,引用就会失效。
Rust是一个安全的语言,开发者一般不会使用裸指针,而是使用智能指针。 智能指针是一些数据结构,它们的表现像是指针,但是提供了附加的元信息和能力。智能指针有它指向的值的所有权。
接下来我们来看一些常见的智能指针。(我对Rust的智能指针也不是特别熟悉,所以本文只讲一部分智能指针,等我学会了更多,再来写新的文章介绍)
Box
这种智能指针是最简单的。 使用示例
fn main() {
let a = Box::new(1);
print!("{}", a); // 1
}
可以用它来实现递归类型,比如
enum List {
Cons(i32, Box<List>),
Nil,
}
这里举的例子是使用Box实现递归类型,其实任何指针都能实现递归类型的。但不用指针是无法实现递归类型的,原因是这样的递归类型的内存占用大小是无限的。
通过以下方式可以修改Box指向的结构体的字段,或者直接替换掉结构体的值。
#[derive(Debug)]
struct Point(i32, i32);
fn main() {
let mut a = Box::new(Point(1, 2));
a.as_mut().1 = 3;
dbg!(&a);
*a = Point(4, 5);
dbg!(&a);
}
注意,Box没有实现Copy,也没实现Clone。把它赋值给其它的变量时,会发生移动。这看起来是合理的,这样可以保证它指向的值的所有者只有一个。当一个Box离开作用域时,它指向的值就会被释放。
但有时需要有多个指针指向同一个值,就得使用其它的智能指针了,比如Rc
Rc
它是有引用计数的智能指针。它可以任意clone。还可以获取引用计数。
let mut v = vec![];
let a = Rc::new(Point(1, 2));
v.push(a.clone());
dbg!(Rc::strong_count(&a));
当一个Rc指针离开作用域时,如果这个值没有引用了,就要把这个值清理调用。
这里有个问题:Rc不允许直接修改一个值。否则,就可能有多个可变引用,就可能违反借用规则。
为了解决这个问题,需要用使用RefCell。
RefCell
这种智能指针的关键的用途是获取它指向的值的可变引用。 “不能通过不可变引用修改数据”这个规则,使得一些本来安全的代码无法编译。 使用RefCell,可以绕开这个规则。但实际上,它在运行期间是遵守借用规则的。在运行时,如果同时存在多个可变引用,就会报错。所以,它仍然可以保证不让多个流程同时修改同一个值。 以下代码在运行时不会报错
let p = RefCell::new(Point(0, 0));
for _ in 0..10 {
p.borrow_mut().0 += 1;
println!("{:?}",&p);
}
以下代码在运行时会报错
let p = RefCell::new(Point(0, 0));
let a = p.borrow_mut();
let b = p.borrow_mut();
Rc结合RefCell
Rc和RefCell常常结合起来使用。
在实现数据结构时,常常会 遇到多个指针指向同一个值,而且需要这些指针都能修改这个值的情况。 这种需求,Rc和RefCell结合起来可以实现。
let mut p = Rc::new(RefCell::new(Point(0, 0)));
let mut p1 = p.clone();
p.borrow_mut().0 = 1;
p1.borrow_mut().1 = 2;
println!("{:?}", p); // RefCell { value: Point(1, 2) }
如何自定义智能指针?
开发者可以自己实现智能指针,只要实现Deref
和Drop
这两个trait。
其中,Deref::deref用来定义这个指针解引用时得到的是哪个值的引用,也就是定义了这个指针指向的是哪个值。
Drop::drop
用来定义这个指针离开定义域时执行什么操作,这是用来清理它指向的值的。
如何表示空值?
Rust是安全的,智能指针本身无法表示空值。要表示可空的智能指针类型,给智能指针套上Option<>
即可
总结
以上就是Rust里常见的几种智能指针了。
在非并发场景下,Option<Rc<RefCell<T>>>
这种使用方式,支持多个引用指向同一个值,而且都支持对值进行修改,也支持空值。它的灵活程度接近裸指针了,但它远比裸指针安全。
这种方式,应该足够在非并发场景下,实现任何数据结构了。
当然,只学会原理是不够的,还得多练习,才能熟练地使用它们。