一、所有权: 单一/共享
对比单一/共享所有权
单一所有权:掌控生杀大权
从多引用开始
Rust如何解决
方案一、单一所有权
方案二、Copy
单一所有权规则整理
单一所有权借用
两种传参方式:传值/传址
只读借用/引用
借用的生命周期与约束
可变借用/引用
同一个上下文中多个可变引用是不安全的,那如果同时有一个可变引用和若干个只读引 用就可以
第一性原理理解单一所有权规则
共享内存-多个所有者:引用计数
单一所有权与多个所有者是否有冲突?
- 静态检查:单一所有权是rust的编译期默认检查内容
- 动态检查:多个所有者主要是用于共享内存,这是专门提供Rc/Arc、Box::leak()、RefCell/Mutex/RwLock等工具。
这里其实可以看出rust如何使用’二八法则’解决问题:
- 对于常用场景,用编译期静态检查来默认解决
- 对于特别场景,用专门的语法显式表达出来,提供运行期动态检查来专门解决
Rc使用说明: 只读引用计数
对一个 Rc 结构进行 clone(),不会将其内部的数据复制,只会增加引用计数
use std::rc::Rc; fn main() { let a = Rc::new(1); let b = a.clone(); let c = a.clone(); }
clone源码
fn clone(&self) -> Rc<T> { // 增加引用计数 self.inner().inc_strong(); // 通过 self.ptr 生成一个新的 Rc 结构 Self::from_inner(self.ptr) }
Rc使用Box::leak()
有了 Box::leak(),我们就可以跳出 Rust 编译器的静态检查
保证 Rc 指向的堆内存,有最大的生命周期,然后我们再通过引用计数,在合适的时机,结束这段内存的生命周期。如果你对此感兴趣,可以看 Rc::new() 的源码。
使用Rc实现DAG
不可修改版本
use std::rc::Rc; #[allow(dead_code)] #[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().cloned() } } 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); // 无法编译通过: cannot borrow as mutable // let node5 = Node::new(5); // let node3 = node1.get_downstream().unwrap(); // node3.update_downstream(Rc::new(node5)); // println!("node1: {:?}, node2: {:?}", node1, node2); }
- new():建立一个新的 Node。
- update_downstream():设置 Node 的 downstream。
- get_downstream():clone 一份 Node 里的 downstream。
RefCell: 提供内部可变性,可变引用计数
外部可变性与内部可变性
RefCell简单使用
获得 RefCell 内部数据的可变借用
use std::cell::RefCell; fn main() { let data = RefCell::new(1); { // 获得 RefCell 内部数据的可变借用 let mut v = data.borrow_mut(); *v += 1; } println!("data: {:?}", data.borrow()); }
使用RefCell实现可修改版本DAG
use std::cell::RefCell; use std::rc::Rc; #[allow(dead_code)] #[derive(Debug)] struct Node { id: usize, // 使用 Rc<RefCell<T>> 让节点可以被修改 downstream: Option<Rc<RefCell<Node>>>, } impl Node { pub fn new(id: usize) -> Self { Self { id, downstream: None, } } pub fn update_downstream(&mut self, downstream: Rc<RefCell<Node>>) { self.downstream = Some(downstream); } pub fn get_downstream(&self) -> Option<Rc<RefCell<Node>>> { self.downstream.as_ref().cloned() } } 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(RefCell::new(node4))); node1.update_downstream(Rc::new(RefCell::new(node3))); node2.update_downstream(node1.get_downstream().unwrap()); println!("node1: {:?}, node2: {:?}", node1, node2); let node5 = Node::new(5); let node3 = node1.get_downstream().unwrap(); // 获得可变引用,来修改 downstream node3.borrow_mut().downstream = Some(Rc::new(RefCell::new(node5))); println!("node1: {:?}, node2: {:?}", node1, node2); }
- 首先数据结构的 downstream 需要 Rc 内部嵌套一个 RefCell
- 这样,就可以利用 RefCell 的内部可变性,来获得数据的可变借用
- 同时 Rc 还允许值有多个所有者。
线程安全版本计数器:Arc(Rc)、Mutex/RwLock(RefCell)
Rust实现两套不同的引用计数数据结构
Arc 内部的引用计数使用了 Atomic Usize ,而非普通的 usize。 从名称上也可以感觉出来,Atomic Usize 是 usize 的原子类型,它使用了 CPU 的特殊指令,来保证多线程下的安全。 如果你对原子类型感兴趣,可以看 std::sync::atomic 的文档。
Rust 实现两套不同的引用计数数据结构,完全是为了性能考虑,从这里我们也可以感受到 Rust 对性能的极致渴求:
- 如果不用跨线程访问,可以用效率非常高的 Rc; 如果要跨线程访问,那么必须用 Arc。
- 同样的,RefCell 也不是线程安全的,如果我们要在多线程中,使用内部可变性,Rust 提供了 Mutex 和 RwLock。