拥有多个强引用并允许可变性的正确智能指针是什么?
What is the right smart pointer to have multiple strong references and allow mutability?
我想在堆上有一个有两个引用的结构;一个给我,另一个来自关闭。请注意,代码是针对单线程情况的:
use std::rc::Rc;
#[derive(Debug)]
struct Foo {
val: u32,
}
impl Foo {
fn set_val(&mut self, val: u32) {
self.val = val;
}
}
impl Drop for Foo {
fn drop(&mut self) {
println!("we drop {:?}", self);
}
}
fn need_callback(mut cb: Box<FnMut(u32)>) {
cb(17);
}
fn create() -> Rc<Foo> {
let rc = Rc::new(Foo { val: 5 });
let weak_rc = Rc::downgrade(&rc);
need_callback(Box::new(move |x| {
if let Some(mut rc) = weak_rc.upgrade() {
if let Some(foo) = Rc::get_mut(&mut rc) {
foo.set_val(x);
}
}
}));
rc
}
fn main() {
create();
}
在实际代码中,need_callback
将回调保存到某个地方,但在此之前可能会像need_callback
那样调用cb
。
代码显示 std::rc::Rc
不适合此任务,因为从未调用过 foo.set_val(x)
;我有两个强引用,在这种情况下 Rc::get_mut
给出 None
。
我应该使用什么带有引用计数的智能指针来代替 std::rc::Rc
才能调用 foo.set_val
?也许可以修复我的代码并仍然使用 std::rc::Rc
?
经过一番思考,我需要类似 std::rc::Rc
的东西,但是弱引用应该可以防止丢失。我可以有两个弱引用,并在需要可变性时将它们升级为强引用。
因为是单线程程序,我一次只会有强引用,所以一切都会按预期运行。
Rc
(及其对应的多线程 Arc
)只关心所有权。现在不再是单一所有者,而是在运行时跟踪的共同所有权。
可变性是一个不同的概念,尽管它与所有权密切相关:如果你拥有一个值,那么你就有能力改变它。这就是为什么 Rc::get_mut
仅在存在单个强引用时才有效——这等同于说只有一个所有者。
如果您需要以不匹配程序结构的方式划分可变性的能力,您可以对单线程程序使用 Cell
or RefCell
等工具:
use std::cell::RefCell;
fn create() -> Rc<RefCell<Foo>> {
let rc = Rc::new(RefCell::new(Foo { val: 5 }));
let weak_rc = Rc::downgrade(&rc);
need_callback(move |x| {
if let Some(rc) = weak_rc.upgrade() {
rc.borrow_mut().set_val(x);
}
});
rc
}
或 Mutex
, RwLock
, or an atomic type 在多线程上下文中:
use std::sync::Mutex;
fn create() -> Rc<Mutex<Foo>> {
let rc = Rc::new(Mutex::new(Foo { val: 5 }));
let weak_rc = Rc::downgrade(&rc);
need_callback(move |x| {
if let Some(rc) = weak_rc.upgrade() {
if let Ok(mut foo) = rc.try_lock() {
foo.set_val(x);
}
}
});
rc
}
这些工具都推迟检查是否只有一个可变的运行时引用,而不是编译时。
我想在堆上有一个有两个引用的结构;一个给我,另一个来自关闭。请注意,代码是针对单线程情况的:
use std::rc::Rc;
#[derive(Debug)]
struct Foo {
val: u32,
}
impl Foo {
fn set_val(&mut self, val: u32) {
self.val = val;
}
}
impl Drop for Foo {
fn drop(&mut self) {
println!("we drop {:?}", self);
}
}
fn need_callback(mut cb: Box<FnMut(u32)>) {
cb(17);
}
fn create() -> Rc<Foo> {
let rc = Rc::new(Foo { val: 5 });
let weak_rc = Rc::downgrade(&rc);
need_callback(Box::new(move |x| {
if let Some(mut rc) = weak_rc.upgrade() {
if let Some(foo) = Rc::get_mut(&mut rc) {
foo.set_val(x);
}
}
}));
rc
}
fn main() {
create();
}
在实际代码中,need_callback
将回调保存到某个地方,但在此之前可能会像need_callback
那样调用cb
。
代码显示 std::rc::Rc
不适合此任务,因为从未调用过 foo.set_val(x)
;我有两个强引用,在这种情况下 Rc::get_mut
给出 None
。
我应该使用什么带有引用计数的智能指针来代替 std::rc::Rc
才能调用 foo.set_val
?也许可以修复我的代码并仍然使用 std::rc::Rc
?
经过一番思考,我需要类似 std::rc::Rc
的东西,但是弱引用应该可以防止丢失。我可以有两个弱引用,并在需要可变性时将它们升级为强引用。
因为是单线程程序,我一次只会有强引用,所以一切都会按预期运行。
Rc
(及其对应的多线程 Arc
)只关心所有权。现在不再是单一所有者,而是在运行时跟踪的共同所有权。
可变性是一个不同的概念,尽管它与所有权密切相关:如果你拥有一个值,那么你就有能力改变它。这就是为什么 Rc::get_mut
仅在存在单个强引用时才有效——这等同于说只有一个所有者。
如果您需要以不匹配程序结构的方式划分可变性的能力,您可以对单线程程序使用 Cell
or RefCell
等工具:
use std::cell::RefCell;
fn create() -> Rc<RefCell<Foo>> {
let rc = Rc::new(RefCell::new(Foo { val: 5 }));
let weak_rc = Rc::downgrade(&rc);
need_callback(move |x| {
if let Some(rc) = weak_rc.upgrade() {
rc.borrow_mut().set_val(x);
}
});
rc
}
或 Mutex
, RwLock
, or an atomic type 在多线程上下文中:
use std::sync::Mutex;
fn create() -> Rc<Mutex<Foo>> {
let rc = Rc::new(Mutex::new(Foo { val: 5 }));
let weak_rc = Rc::downgrade(&rc);
need_callback(move |x| {
if let Some(rc) = weak_rc.upgrade() {
if let Ok(mut foo) = rc.try_lock() {
foo.set_val(x);
}
}
});
rc
}
这些工具都推迟检查是否只有一个可变的运行时引用,而不是编译时。