文章目录
- 1v1 所有权
- 深拷贝(clone)
- 引用计数 (reference count)
- 写时复制(copy on write)
- 循环引用
- 弱引用
- 线程安全
1v1 所有权
Rust
中所有权约束了值只能有一个所有者,当值离开作用域时,它将被销毁。
像如下代码,字符串a
如果直接移动给b
后就没法后边再去打印,因为它的所有权已经转移给了b
。
let a = String::from("hello");
let b = a;
println!("a = {}, b = {}", a, b);
// got error:
// error[E0382]: borrow of moved value: `a`
// 3 | let a = String::from("hello");
// | - move occurs because `a` has type `String`, which does not implement the `Copy` trait
// 4 | let b = a;
// | - value moved here
// 5 | println!("a = {}, b = {}", a, b);
// | ^ value borrowed here after move
深拷贝(clone)
如果clone
的话可以复制一份,但是这样的话就需要开辟一块新的内存,不是很高效。
let a = String::from("hello");
// let b = a;
let b = a.clone();
println!("a = {}, b = {}", a, b);
// output: a = hello, b = hello
引用计数 (reference count)
想要实现多个所有者,又开销小,可以用引用计数,对应的类型是Rc
。
Rc
只会在复制时增加引用计数,当引用计数为 0 时,会自动调用drop
方法,释放内存。
let a = Rc::new(String::from("hello"));
let _b = Rc::clone(&a);
{
let _b = Rc::clone(&a);
println!("reference count {}", Rc::strong_count(&a));
// 3, will be 2 after this block out of scope
}
println!("reference count {}", Rc::strong_count(&a)); // 2
写时复制(copy on write)
Rc
引用的值是不可变的,如果想要修改,可以使用Rc::make_mut
方法,它会检查引用计数,在有别的有效引用(strong
)时,会复制一份,然后修改。否则就直接修改原来的值。这也是写时复制,只有在需要修改时才会复制。
let mut a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);
// allocate a new string (copy on write)
(*Rc::make_mut(&mut a)).push_str( " world");
println!("{} {}", a, b);
// hello world hello
let c = Rc::clone(&a);
println!("{} {} {}", a, b, c);
// hello world hello hello world
所以这么用有一个好处,如果有修改,修改是独立于之前的引用的,不用担心修改会影响之前引用的值。
当然,如果想保持值修改的同步,可以使用之前提到的
Cell
和RefCell
,这两个类型可以实现内部可变性,可以在不可变引用的情况下修改值。
循环引用
Rc
是不允许循环引用的,因为它的引用计数是在编译时就确定的,如果有循环引用,那么引用计数永远不会为 0,也就永远不会调用drop
方法,导致内存泄漏。
这里用官方的一个例子说明:下边代码用来描述工具(gadget)和工具所有者(owner)的关系,一个工具可以有一个个所有者,一个所有者可以有多个工具。
如果用Rc
来实现的话,会出现循环引用,工具和工具所有者互相引用,导致谁都无法对引用计数减一,也就无法释放对应的内存。
use std::{rc::Rc, cell::RefCell};
struct Owner {
name: String,
gadgets: RefCell<Vec<Rc<Gadget>>>
}
struct Gadget {
id: i32,
owner: Rc<Owner>
}
fn main() {
let gadget_owner : Rc<Owner> = Rc::new(
Owner { name: String::from("Gadget Man"), gadgets: RefCell::new(vec![]) }
);
// 两个工具,都有同一个所有者
let gadget1 = Rc::new(Gadget { id: 1, owner: gadget_owner.clone() });
let gadget2 =Rc::new(Gadget { id: 2, owner: gadget_owner.clone() });
gadget_owner.gadgets.borrow_mut().push(gadget1.clone());
gadget_owner.gadgets.borrow_mut().push(gadget2.clone());
// 释放gadget_owner的引用计数,保留工具的owner引用计数
drop(gadget_owner);
println!("strong count of gadget1: {}", Rc::strong_count(&gadget1));
// strong count of gadget1: 2
println!("strong count of gadget1.owner: {}", Rc::strong_count(&gadget1.owner));
// strong count of gadget1.owner: 2
// 释放gadget1的引用计数,正常没有引用循环的话,owner对应的引用计数也需要释放
// 但是gadget1的owner的引用计数不会减一,导致内存泄漏
drop(gadget1);
println!("strong count of gadget2.owner: {}", Rc::strong_count(&gadget2.owner));
// strong count of gadget2.owner: 2
}
循环引用如下图所示
代码语言:javascript复制gadgets和owner的引用形成了一个环,谁也没法释放,对应的引用计数无法减到0,也就没法释放
----------- -----------
| Owner |<------| Gadget |
| | | |
| Rc | | Rc |
| | | |
| gadgets --|------>| owner ----
----------- -----------
弱引用
这个时候就是弱引用的用武之地了,弱引用不会增加引用计数,所以不会导致循环引用。
但是它也不能保证引用的值一定存在,因为它的引用计数可能为 0,所以用时,需要用upgrade
方法来获取Option
类型的引用。
也就是说引用的值释放与否只取决于强引用的引用计数。
代码语言:javascript复制use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;
struct Owner {
name: String,
gadgets: RefCell<Vec<Weak<Gadget>>>
}
struct Gadget {
id: i32,
owner: Rc<Owner>
}
fn main() {
let gadget_owner : Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
gadgets: RefCell::new(Vec::new())
}
);
let gadget1 = Rc::new(Gadget{id: 1, owner: gadget_owner.clone()});
let gadget2 = Rc::new(Gadget{id: 2, owner: gadget_owner.clone()});
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget1.clone()));
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget2.clone()));
for gadget_opt in gadget_owner.gadgets.borrow().iter() {
let gadget = gadget_opt.upgrade().unwrap();
println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}
drop(gadget_owner);
println!("strong count of gadget1: {}", Rc::strong_count(&gadget1));
// strong count of gadget1: 1
println!("strong count of gadget1.owner: {}", Rc::strong_count(&gadget1.owner));
// strong count of gadget1.owner: 2
drop(gadget1);
println!("strong count of gadget2.owner: {}", Rc::strong_count(&gadget2.owner));
// strong count of gadget2.owner: 1
}
线程安全
Rc
是线程不安全的,如果想要在多线程中使用,可以使用Arc
,它是Rc
的线程安全版本。(A
代表atomic
)
use std::sync::Arc;
use std::thread;
fn main() {
let val = Arc::new(5);
for _ in 0..3 {
let val = Arc::clone(&val);
thread::spawn(move || {
let v = *val.as_ref() 1;
println!("{v:?}");
});
}
thread::sleep(std::time::Duration::from_secs(1));
}
而如果想要在多线程中修改值,可以使用Mutex
和RwLock
,它们都是线程安全的。如Arc<Mutex<T>>
。
最后还有一点想提下,Rc<T>
和Arc<T>
都实现了自动解引用Deref
到T
,所以可以直接在Rc<T>
和Arc<T>
上调用T
的方法。而为了防止方法名冲突,一般习惯用全限定语法调用方法来调用Rc<T>
和Arc<T>
的方法,如Rc::clone
。
推荐阅读
- 掌握Rust:从零开始的所有权之旅
- 聊聊Rust的Cell和RefCell
如果有用,点个 在看 ,让更多人看到
外链不能跳转,戳 阅读原文 查看参考资料