智能指针与所有权
独占所有权 Box<T>
智能指针(Samrt Pointer) 是一种结构体,是对指针的封装。 智能指针区别于常规结构体的特征在于,它实现了 Deref 和 Drop。
智能指针和普通引用的区别之一是所有权的不同:智能指针拥有资源的所有权,而普通引用只是对所有权的借用。
智能指针 Box<T> 独占所有权的样例:
```
fn main() {
let x = Box::new("hello");
let y = x;
println!("x = {}", x); // value borrowed here after move
}
```
解引用智能指针 Box<T>
对于 Box<T> 类型 a,b 来说 *a 和 *b 操作相当于 *(a.defref) 和 *(b.deref)。
如果包含的类型 T 属于复制语义,则执行按位复制;如果属于移动语义,则移动所有权。
fn main() {
let a = Box::new("hello");
let b = Box::new("Rust".to_string());
let c = *a;
let d = *b; // String 是移动语义,解引用 Box 会导致 String 的转移,转移后 b 不再可用。
println!("a = {:?}", a);
// println!("b = {:?}", b); // borrow of moved value: `b`
println!("d = {:?}", d);
}
这种对 Box<T>使用操作符 (*) 进行解引用而转移所有权的行为,被称作 解引用移动,目前支持此行为的智能指针只有 Box<T>。 (Rc<T> 和 Arc<T> 不支持解引用移动)
use std::rc::Rc;
fn main() {
let x = Rc::new(45);
let x1 = *x; // 解引用复制,允许
let a = Rc::new("aa".to_string());
// let b = *a; // 解引用移动,不允许
let b = &*a; // Rc只能 解引用只读借用
}
共享所有权 Rc<T> 和 Weak<T>
引用计数 (reference counting) 是最简单的 GC 算法之一。 Rust 中提供了 Rc<T> 智能指针来支持引用计数,但不同于 GC,Rust是确定性的析构,开发者(编译器)知道资源什么时候会被析构。
Rust 中只有拥有所有权才能释放资源, Rc<T> 可以将多个所有权共享给多个变量。每当共享一个所有权时,计数就会增加一次;当共享变量离开作用域时,计数就减少一次。当计数为零时,该值才会被析构。
Rc<T> 是单线程引用计数指针,不是线程安全的类型,Rust 也不允许它被传递或共享给别的线程。
use std::rc::Rc;
fn main() {
let x = Rc::new(45);
let y1 = x.clone(); // 增加引用计数
let y2 = x.clone(); // 增加引用计数
println!("x_strong_count = {:?}", Rc::strong_count(&x));
let w = Rc::downgrade(&x); // 增加弱引用计数
println!("{}", Rc::weak_count(&x));
let y3 = &*x; // 不增加引用计数。45i32是复制语义,也可以 *x 。
println!("100 - *x = {}", 100 - y3);
let x = Rc::new("hello".to_string());
let y = &*x;
println!("x = {:?}", y);
}
通过 clone 方法共享的引用所有权被称为 强引用。 通过 downgrade 方法创建了另一种智能指针类型 Weak<T>,也是引用计数指针。它共享的指针没有所有权,所以被称为 弱引用。
内部可变性 Cell<T> 和 RefCell<T>
Rust 中的可变或不可变主要是针对一个变量绑定而言的,比如对于结构体来说,可变或不可变只能对其实例进行设置,而不能设置单个成员的可变性。
实际的开发应用中,某个字段是可变而其他字段不可变的情况确实存在。 Rust 提供了 Cell<T> 和 RefCell<T> 来对应这种情况。
它们本质上不属于智能指针,只是可以提供内部可变性的容器。
Cell<T>
内部可变性实际上是 Rust 中的一种设计模式。内部可变性容器是对 Struct 的一种封装,表面不可变,但内部可以通过某种方法来改变里面的值。
use std::{cell::Cell};
fn main() {
let mut foo1 = Foo {
x: 3,
y: Cell::new(7),
};
println!("foo1 = {:?}", foo1);
foo1.x = 5;
foo1.y = Cell::new(9);
println!("foo1 = {:?}", foo1);
let foo2 = Foo {
x: 5,
y: Cell::new(9),
};
println!("foo2.y = {:?}", foo2.y.get());
foo2.y.set(10);
println!("foo2.y = {:?}", foo2.y.get());
}
#[derive(Debug)]
struct Foo {
x: u32,
y: Cell<u32>,
}
Cell<T> 提供的 set/get 方法像极了 OOP的 setter/getter 方法。实际上,Cell<T> 包裹的 T 本身合法的避开了借用检查。
RefCell<T>
RefCell<T> 适用的范围个更广,对类型 T 并没有 Copy 的限制。
use std::{borrow::Borrow, cell::RefCell};
fn main() {
let x = RefCell::new(vec![1, 2, 3, 4]);
println!("x.borrow() = {:?}", x.borrow());
x.borrow_mut().push(5);
println!("x.borrow() = {:?}", x.borrow());
}
Cell<T> 和 RefCell<T> 使用最多的场景就是配合只读引用来使用,如 Rc<RefCell<T>>。
Cell<T> 和 RefCell<T> 的区别
-
Cell<T> 使用 set/get 方法直接操作包裹的值,RefCell<T> 通过 borrow/borrow_mut 返回包装过的引用 Ref<T> 和 RefMut<T> 来操作包裹的值。
-
Cell<T> 一般适合复制语义类型(实现了 Copy),RefCell<T> 一般适合移动语义类型(未实现 Copy)。
-
Cell<T> 无运行时开销,并且永远不会在运行时引发 panic 错误。RefCell<T>需要在运行时执行借用检查,发现违反借用的情况,会引发现场 panic 而退出当前线程。
写时复制 Cow<T>
写时复制 (Copy on Write) 技术是一种程序中的优化策略,应用于多种场景。比如 Linux 中父进程创建子进程时,此种 “拖延” 策略减少了开销。
Rust 根据这种思维,提供了 Cow<T> 容器。Cow<T> 是一个枚举体的智能指针,包括两个可选值:
- Borrowed,用于包裹引用。
- Owned,用于包裹所有者。
Cow<T> 提供的功能是,以不可变的方式访问借用内容,以及在需要可变借用或所有权的时候再 克隆(clone)一份数据。
- Cow<T> 实现了 Deref,这意味着可以直接调用其包含数据的不可变的方法。
- Cow<T> 旨在减少复制操作,提高性能,一般用于读多写少的场景。
use std::borrow::Cow;
fn main() {
// 没有可变需求,也不会克隆
let s1 = [1, 2, 3];
let mut cow1 = Cow::from(&s1[..]);
abs_all(&mut cow1);
println!("in_01 = {:?}", s1);
println!("ot_01 = {:?}", cow1);
// 有可变需求,所以会克隆。
// 注意:借用数据被克隆成了新的对象
// s2 != cow2 ,s2 不可变,也不会被改变
let s2 = [1, 2, 3, -45, 5];
let mut cow2 = Cow::from(&s2[..]);
abs_all(&mut cow2);
println!("in_02 = {:?}", s2);
println!("ot_02 = {:?}", cow2);
// 这里不会克隆,因为数据本身拥有所有权
// 注意: v1 本身就是可变的
let mut v1 = Cow::from(vec![1, 2, -3, 4]);
abs_all(&mut v1);
println!("v1 = {:?}", v1);
// 没有可变需求,所以没有克隆
let s3 = [1, 3, 5, 6];
let sum1 = abs_sum(&s3[..]);
println!("sum1 = {}", sum1);
// 这里有可变需求,因此发生了克隆
let s4 = [1, -3, 5, -6];
let sum2 = abs_sum(&s4[..]);
println!("sum2 = {}", sum2);
}
fn abs_all(input: &mut Cow<[i32]>) {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
input.to_mut()[i] = v * (-1);
}
}
}
fn abs_sum(slice: &[i32]) -> i32 {
let mut cow = Cow::from(slice);
abs_all(&mut cow);
let sum = cow.iter().fold(0, |acc, &n| acc + n);
return sum;
}
在使用Cow的时候,注意一下几个要点:
-
Cow<T> 实现了 Deref,所以可以直接调用 T 的不可变方法。
-
在需要修改 T 时,可以使用 to_mut 方法来获取可变借用。改方法会产生克隆,但仅克隆一次,如果多次调用,则只会使用第一次的克隆对象。如果 T 本身拥有所有权,则此时调用 to_mut 不会发生克隆。
-
在需求修改 T 时,也可以使用 into_owned 方法来获取一个拥有所有权的对象。如果 T 是借用类型,这个过程会发生克隆,并创建新的所有权对象。如果 T 是所有权对象,则会将所有权转移到新的克隆对象。
Cow<T> 的另一个用处是统一实现规范。// 比如?