大家好,我是砸锅。一个摸鱼八年的后端开发。熟悉 Go、Lua。第十八天还是继续和大家一起学习 Rust😊
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
Rust 对可变引用的限制:
- 在一个作用域里面,仅允许一个活跃的可变引用。所谓活跃是指真正被用来修改数据的可变引用,只是定义没有使用或者当作可读引用来使用都不算
- 在一个作用域里面,活跃的可变引用(写)和可读引用(读)是互斥的,不能同时存在。和别的语言里面的 RwLock 类似,并发下读写访问规则
Rust 处理很多问题的思路是:编译时,处理大部份使用场景,保证安全性和效率;运行时处理无法编译时处理的场景,虽然会牺牲一定的效率,但是提高了灵活性
Rust 在运行时做动态检查是利用了引用计数的智能指针:Rc(Reference counter) 和 Arc (Atomic reference counter)
Rc
对某个数据结构 T,可以创建引用计数 Rc,让它拥有更多的所有者。Rc 会把对应的数据结构创建在堆上,如果要对数据创建更多的所有者,可以使用 clone(), clone 操作不会将其内部数据复制,只会增加引用计数
当一个 Rc 结构离开作用域被 drop() 时,就会减少引用计数,直到引用计数为零,就会真正清除对应的内存
use std::rc::Rc;
fn main() {
let a = Rc::new(1);
let b = a.clone();
let c = a.clone();
// a: 0x6000034111b0, b: 0x6000034111b0, c:0x6000034111b0
println!("a: {:p}, b: {:p}, c:{:p}", a, b, c);
}
Box::leak() 机制
Box 是 Rust 下的智能指针,可以强制将任何数据结构创建在堆上,然后在栈上放一个指针指向这个数据结构
Box::leak() 创建的对象,从堆内存里面泄漏出去,不受栈内存控制,生命周期可以大到和整个进程的生命周期一致的对象
实现一个 DAG
use std::rc::Rc;
#[derive(Debug)]
struct Node {
id: usize,
downstream: Option<Rc<Node>>,
}
impl Node {
pub fn new(id: usize) -> Self {
Self {
id,
downstream: None,
}
}
pub fn update_downstream(&mut self, downstream: Rc<Node>) {
self.downstream = Some(downstream);
}
pub fn get_downstream(&self) -> Option<Rc<Node>> {
self.downstream.as_ref().map(|v| v.clone())
}
}
fn main() {
let mut node1 = Node::new(1);
let mut node2 = Node::new(2);
let mut node3 = Node::new(3);
let node4 = Node::new(4);
node3.update_downstream(Rc::new(node4));
node1.update_downstream(Rc::new(node3));
node2.update_downstream(node1.get_downstream().unwrap());
println!("node1: {:?}, node2: {:?}", node1, node2);
}
内部可变性
例如 let mut 声明一个可变的值,或者是 &mut 声明一个可变引用时,编译器可以在编译时进行严格的检查,确保只有可变的值和可变的引用才可以修改值内部的数据,这个被称为外部可变性
如果想在编译器检查时这个值是只读的,然后在运行时可以变成可变借用,修改内部的数据,这时候就需要用到 RefCell
use std::cell::RefCell;
fn main() {
let data = RefCell::new(1);
{ // 单独加花括号是为了分开两个不同的作用域,因为在所有权规则里,同一个作用域下不能同时有活跃的可变借用和不可变借用
let mut v = data.borrow_mut();
*v += 1;
}
println!("data: {:?}", data.borrow());
}
Arc
如果是多个线程访问同一块内存的时候,就不能用 Rc了,因为 Rc 为了性能并没有使用线程安全的引用计数器。这时候需要使用 Arc,因为它实现了线程安全的引用计数器
内部引用计数使用了 Atomic Usize,利用 CPU 的特殊指令,来保证多线程下安全
RefCell 也不是线程安全的,如果在多线程的场景下,需要使用 Mutex 和 RwLock。Mutex 是互斥量,获取互斥量的线程对数据独占访问。RwLock 是读写锁,获得写锁的线程对数据独占访问,同时没有写锁的时候,允许有多个读锁