Каков правильный интеллектуальный указатель, чтобы иметь несколько сильных ссылок и допускать изменяемость?

Я хочу иметь структуру в куче с двумя ссылками; один для меня, а другой - от закрытия. Обратите внимание, что код предназначен для однопоточного случая:

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 сохраняет обратный вызов в какое-то место, но перед этим может вызвать cb, как это делает need_callback.

Код показывает, что std::rc::Rc не подходит для этой задачи, потому что foo.set_val(x) никогда не вызывается; У меня есть две сильные ссылки, и Rc::get_mut дает None в этом случае.

Какой умный указатель с подсчетом ссылок я должен использовать вместо std::rc::Rc, чтобы можно было вызвать foo.set_val? Может быть, можно исправить мой код и по-прежнему использовать std::rc::Rc?

Поразмыслив, мне нужно что-то вроде std::rc::Rc, но слабые ссылки должны предотвратить падение. Я могу иметь две слабые ссылки и обновлять их до сильных, когда мне нужна изменчивость.

Поскольку это однопоточная программа, у меня будут только сильные ссылки за раз, поэтому все будет работать так, как ожидалось.


person user1244932    schedule 21.08.2016    source источник


Ответы (1)


Rc (и его многопоточный аналог Arc) заботятся только о владении. Вместо одного владельца теперь существует совместное владение, отслеживаемое во время выполнения.

Изменчивость - это другое понятие, хотя оно тесно связано с владением: если вы владеете ценностью, у вас есть возможность ее видоизменять. Вот почему Rc::get_mut работает только тогда, когда есть одна сильная ссылка - это то же самое, что сказать, что есть единственный владелец.

Если вам нужна возможность разделения изменяемости способом, который не соответствует структуре программы, вы можете использовать такие инструменты, как _ 4_ или 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, _ 8_ или атомарный тип в многопоточном контексте:

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
}

Все эти инструменты откладывают проверку наличия только одной изменяемой ссылки на время выполнения, а не на время компиляции.

person Shepmaster    schedule 21.08.2016