Rust 中 Rc、Arc 与内部可变性总结
在 Rust 中,所有权默认是唯一的,也就是说,一个数据只有一个所有者。当所有者离开作用域时,数据会被自动释放。
但在某些场景中,我们希望让多个地方共享对同一数据的所有权,这就需要使用共享所有权。
怎么理解呢? 比如在使用Config对象的场景下使用Rc:
struct Config { }
fn main() {
let config = Rc::new(Config { });
let module_a_config = Rc::clone(&config);
let module_b_config = Rc::clone(&config);
let module_c_config = Rc::clone(&config);
}
这样做的好处:
- Config只创建了一次,避免重复分配内存。
- 每个module使用Config不用担心所有权或者生命周期的问题。
- module模块都不再需要配置时,数据会自动释放。
如果使用&Config,很容易出现生命周期的报错或者出现悬挂指针的问题。
Rc和Arc实际上是打破了Rust对所有权的限制。
Rc和Arc
-
Rc 和 Arc 都是智能指针,它们通过内部的引用计数机制来管理数据的所有权。每当有一个新的引用计数指针创建时,它的引用计数就会增加;当引用计数降为零时,数据会被自动销毁。
-
共享所有权:打破了一个数据只有一个所有者。允许多个所有者共享对同一个数。 据的所有权,确保当最后一个所有者离开时,数据被释放。
-
Rc是单线程场景下共享所有权
-
Arc是多线程场景下共享所有权
只读使用方式
struct Config {
name: String,
version: String,
}
impl Config {
pub fn new(name: &str, version: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
}
}
fn print(&self) {
println!("name = {}, verison = {}", self.name, self.version);
}
fn print_msg(&self, msg: &str) {
println!("name = {}, version = {}, msg = {}", self.name, self.version, msg);
}
}
fn main() {
let config = Rc::new(Config::new("app", "v0.0.1"));
let module_config = Rc::clone(&config);
println!("name = {}", config.name);
println!("version = {}", config.version);
module_config.print();
module_config.print_msg("Hello, world!");
// 也可以正常借用
let module_config_ref = &module_config;
println!("module_config_ref.name = {}", module_config_ref.name);
}
只读取, Rc和Config 使用上几乎一样。
fn main() {
let arc_config = Arc::new(Config::new("arc app", "v0.0.2"));
let tasks: Vec<_> = (1..5).map(move |i| {
let module_arc_config_ref = Arc::clone(&arc_config);
thread::spawn(move || {
module_arc_config_ref.print_msg(format!("thread {}", i).as_str());
})
})
.collect();
for task in tasks {
task.join().unwrap();
}
}
Arc使用上类似。
修改内部值
这里要提到rust另一个规则:
-
不可变借用(&T):可以同时拥有多个,不会改变数据。
-
可变借用(&mut T):只能有一个,并且可以修改数据,但在此期间不能有不可变借用。
当使用Rc创建了多个所有者之后,如果要修改值的话,也就违反了 可变借用(&mut T)规则。
需要用Refell或者Mutex来破坏这一规则。
-
RefCell 是在单线程中使用的,可以提供内部可变性,但只适用于单线程场景。
-
Mutex 是在多线程环境中使用的,确保同一时间只能有一个线程访问数据,相当于锁。
所以要修改多所有者的数据需要使用Rc<RefCell> 或者 Arc<Mutex>
- Rc<RefCell>
struct Config {
name: String,
version: String,
}
impl Config {
pub fn new(name: &str, version: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
}
}
fn print(&self) {
println!("name = {}, version = {}", self.name, self.version);
}
fn print_msg(&self, msg: &str) {
println!("name = {}, version = {}, msg = {}", self.name, self.version, msg);
}
fn set_version(&mut self, new_version: &str) {
self.version = new_version.to_string();
}
}
fn main() {
let config = Rc::new(RefCell::new(Config::new("app", "v0.0.1")));
let module_config = Rc::clone(&config);
{
// 可变借用修改 version
let mut cfg_mut = config.borrow_mut();
cfg_mut.set_version("v0.0.2");
}
{
// 使用不可变借用打印
let cfg = module_config.borrow();
cfg.print();
cfg.print_msg("Hello, Rc<RefCell>!");
}
{
// 再修改
// 使用不可变借用打印
let mut module_cfg_mut = module_config.borrow_mut();
module_cfg_mut.set_version("v1.0.2");
}
// 再借用看看字段
println!("version from main: {}", config.borrow().version);
}
这里需要使用borrow(), borrow_mut()获取Ref 再操作。
- Arc<Mutex>
fn main() {
let arc_config = Arc::new(Mutex::new(Config::new("arc app", "v0.0.2")));
let tasks: Vec<_> = (1..5).map(|i| {
let module_arc_config_ref = Arc::clone(&arc_config);
thread::spawn(move || {
let mut config = module_arc_config_ref.lock().unwrap();
config.set_version(&format!("-t{}", i));
config.print_msg(&format!("from thread {}", i));
})
}).collect();
for task in tasks {
task.join().unwrap();
}
// 主线程查看最终版本
let final_config = arc_config.lock().unwrap();
println!("Final config: name = {}, version = {}", final_config.name, final_config.version);
}
Mutex使用时需要先lock() 之后操作,会自动解锁。