1. 什么是智能指针?
智能指针(smart pointers)是一类数据结构,它们的表现类似指针,但是也拥有额外的元数据和功能。
在 Rust 中因为引用和借用,普通引用和智能指针的一个额外的区别是引用是一类只借用数据的指针;相反,在大部分情况下,智能指针 拥有 它们指向的数据,这是智能指针所独有的特点。
智能指针 就是具有引用行为的能够管理数据的数据结构。
2. Rust 中的智能指针
2.1 Box<T>
允许在堆上存储数据;可读可写;对数据的所有权只能有一个,无法通过 clone 实现多所有权;
use std::thread;
#[derive(Debug)]
struct User {
name: String,
age: u8,
}
fn main() {
let mut x = Box::new(User{ name: String::from("Mike"), age: 20 });
println!("{:?}", x); //可读 User { name: "Mike", age: 20 }
let y = &mut x;
y.age = 30;
y.name = String::from("Jack");
println!("{:?}", x); //可写 User { name: "Jack", age: 30 }
let t = thread::spawn(move || {
println!("{:?}", x);
});
t.join().unwrap();
println!("{:?}", x); //所有权唯一 value borrowed here after move
}
2.2 Rc<T>
可读;可以通过 clone 实现多个所有权,但仅限单线程,无法 move;
fn main() {
let mut x = Rc::new(User{ name: String::from("Mike"), age: 20 });
println!("{:?}", x); //可读 User { name: "Mike", age: 20 }
let y = &mut x;
y.age = 30;
y.name = String::from("Jack");
println!("{:?}", x); //不可写 trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<User>`
let t = thread::spawn(move || {
println!("{:?}", x);
});
t.join().unwrap();
println!("{:?}", x); //线程不安全 cannot be sent between threads safely
}
和 Box 对比
fn main() {
let x = Box::new(User{ name: String::from("Mike"), age: 20 });
let y = Rc::new(User{ name: String::from("Jack"), age: 30 });
let x1 = x;
let x2 = x;
println!("{:?}", x1);
println!("{:?}", x2); //error[E0382]: value used here after move
let y1 = y.clone();
let y2 = y;
println!("{:?}", y1);
println!("{:?}", y2); // User { name: "Jack", age: 30 }
}
可能有人会想 let x1 = x.clone() 不就行了?但实际使用会发现 Box 的实例是没有 clone() 方法的。
2.3 Arc<T>
可读;虽然 Rc 通过 clone 能够实现多所有权,但是 Rc 无法多线程共享,所以便有了 Arc。
fn main() {
let x = Rc::new(User{ name: String::from("Mike"), age: 20 });
let y = Arc::new(User{ name: String::from("Jack"), age: 30 });
let t = thread::spawn(move || {
println!("{:?}", x); // error[E0277] `Rc<User>` cannot be sent between threads safely
println!("{:?}", y); // User { name: "Jack", age: 30 }
});
t.join().unwrap();
}
2.3 Mutex<T>
此时我们已经能够通过 Arc 配合 clone 实现多线程访问同一份数据了,但我们还无法去修改这个数据。
多线程怎么修改同一份数据?很明显需要锁, Mutex 则是 Rust 中多线程加锁的方式。
fn main() {
let y = Arc::new(Mutex::new(User{ name: String::from("Jack"), age: 30 }));
let y1 = y.clone();
let t = thread::spawn(move || {
println!("{:?} y", y); // Mutex { data: User { name: "Jack", age: 30 }, poisoned: false, .. } y
let mut yy = y.lock().unwrap();
yy.name = String::from("Mike");
yy.age = 10;
println!("{:?} yy", yy); // User { name: "Mike", age: 10 } yy
});
let t1 = thread::spawn(move || {
sleep(Duration::new(1,0));
println!("{:?} y1", y1); // Mutex { data: User { name: "Mike", age: 10 }, poisoned: false, .. } y1
});
t.join().unwrap();
t1.join().unwrap();
}
可以看到,通过 Mutex 和 Arc 的组合方式,数据可以在多线程读和写了。
PS: 至于线程之间传递数据,Rust 类似 Go,"不要通过共享内存去传递数据,而是通过传递数据去共享内存" 的理念,感兴趣的可以去看看。
2.4 Cell<T>
Cell 和 RefCell 和上面的智能指针有所不同,对于 Rust 来说,它们两个有点 "unsafe" 的意思,因为
RefCell 是在编译时检查借用规则,而 Box 是在编译时强制代码遵守借用规则。
先简单介绍下 Cell
fn main() {
let x = Cell::new(User{ name: String::from("Jack"), age: 30 });
// 此处 y 并没有加 mut,但也可以通过 set 更改值
let y = Cell::new(10);
// Cell<T>用于提供一个内部可变的容器,使你可以在不可变上下文中修改其内部数据。然而,Cell<T>要求其内部存储的类型T必须实现Copy trait。
// 这是因为Cell的读取和写入操作都是通过按值传递完成的,而不是通过引用。
// User Struct 默认是没有实现 copy trait 的,所以此处报错
println!("{:?}", x); // the trait `Copy` is not implemented for `User`, which is required by `Cell<User>: Debug`
println!("{:?}", y); // Cell { value: 10 }
y.set(20);
println!("{:?}", y); // Cell { value: 20 }
println!("{:?}", y.get()); // 20
}
2.5 RefCell<T>
Cell 只能接收实现了 Copy trait 的值。要想更改没有实现的值该怎么办呢?比如更改自定义的 struct.
RefCell 是通过 borrow_mut() 和 borrow() 来实现的。
fn main() {
let x = RefCell::new(User{ name: String::from("Jack"), age: 30 });
println!("{:?}", x); // RefCell { value: User { name: "Jack", age: 30 } }
// 1. 通过 borrow_mut() 借用内部的指针
let mut inner = x.borrow_mut();
// 2. 修改值
inner.name = String::from("Mike");
inner.age = 10;
// 注意,此时需要显式释放可变借用,以便下面的不可变借用可以成功
drop(inner);
// 3. 通过 borrow() 获取修改后的值
println!("{:?}", x.borrow()); // User { name: "Mike", age: 10 }
}
总结:
本文主要介绍了对智能指针的理解以及一些常用智能指针的特性和使用方式。
智能指针其实就是一种结构体,即 struct ,但是它们实现的 Deref 和 Drop 让它们既能管理数据的同时也拥有了指针的“解引用”和“释放”的能力,并且通过实现各种 trait 来拥有更多其它的能力,比如 Arc 实现了 Sync 这个 trait 因此可在多线程之间共享,而 Rc 则没有实现。
总之:智能指针的实现就是基于实现了各种 trait 的结构体。