Закрытие может пережить текущую функцию

Я только начинаю изучать Rust. Для этого я переписываю свой проект C ++ на Rust, но самые большие проблемы - это время жизни замыканий и тому подобное.

Я создал абсолютно минимальный сценарий моей проблемы, который можно увидеть здесь и ниже:

use std::sync::Arc;
use std::cell::{RefCell, Cell};

struct Context {
    handler: RefCell<Option<Arc<Handler>>>,
}

impl Context {
    pub fn new() -> Arc<Context> {
        let context = Arc::new(Context{
            handler: RefCell::new(None),
        });

        let handler = Handler::new(context.clone());

        (*context.handler.borrow_mut()) = Some(handler);

        context
    }

    pub fn get_handler(&self) -> Arc<Handler> {
        self.handler.borrow().as_ref().unwrap().clone()
    }
}

struct Handler {
    context: Arc<Context>,

    clickables: RefCell<Vec<Arc<Clickable>>>,
}

impl Handler {
    pub fn new(context: Arc<Context>) -> Arc<Handler> {
        Arc::new(Handler{
            context: context,

            clickables: RefCell::new(Vec::new()),
        })
    }

    pub fn add_clickable(&self, clickable: Arc<Clickable>) {
        self.clickables.borrow_mut().push(clickable);
    }

    pub fn remove_clickable(&self, clickable: Arc<Clickable>) {
        // remove stuff ...
    }
}

struct Clickable {
    context: Arc<Context>,

    callback: RefCell<Option<Box<Fn()>>>,
}

impl Clickable {
    pub fn new(context: Arc<Context>) -> Arc<Clickable> {
        let clickable = Arc::new(Clickable{
            context: context.clone(),

            callback: RefCell::new(None),
        });

        context.get_handler().add_clickable(clickable.clone());

        clickable
    }

    pub fn remove(clickable: Arc<Clickable>) {
        clickable.context.get_handler().remove_clickable(clickable);
    }

    pub fn set_callback(&self, callback: Option<Box<Fn()>>) {
        (*self.callback.borrow_mut()) = callback;
    }

    pub fn click(&self) {
        match *self.callback.borrow() {
            Some(ref callback) => (callback)(),
            None => (),
        }
    }
}

struct Button {
    context: Arc<Context>,

    clickable: Arc<Clickable>,
}

impl Button {
    pub fn new(context: Arc<Context>) -> Arc<Button> {
        let clickable = Clickable::new(context.clone());

        let button = Arc::new(Button{
            context: context,

            clickable: clickable.clone(),
        });

        let tmp_callback = Box::new(|| {
            button.do_stuff();
        });
        clickable.set_callback(Some(tmp_callback));

        button
    }

    pub fn do_stuff(&self) {
        // doing crazy stuff
        let mut i = 0;

        for j in 0..100 {
            i = j*i;
        }
    }

    pub fn click(&self) {
        self.clickable.click();
    }
}

impl Drop for Button {
    fn drop(&mut self) {
        Clickable::remove(self.clickable.clone());
    }
}

fn main() {
    let context = Context::new();

    let button = Button::new(context.clone());

    button.click();
}

Я просто не знаю, как передавать ссылки в замыканиях.

Еще одна неприятность в том, что мой Handler и мой Context нужны друг другу. Есть ли лучший способ создать эту зависимость?


person hodasemi    schedule 17.08.2017    source источник


Ответы (1)


Отказ от исходного кода

pub fn new(context: Arc<Context>) -> Arc<Button> {
    let clickable = Clickable::new(context.clone());

    let button = Arc::new(Button{
        context: context,

        clickable: clickable.clone(),
    });

    let tmp_callback = Box::new(|| {
        button.do_stuff();
    });
    clickable.set_callback(Some(tmp_callback));

    button
}

Прежде всего, обратите внимание на ошибку, которую вы получаете

    error[E0373]: closure may outlive the current function, but it borrows `button`, which is owned by the current function
   --> src/main.rs:101:37
    |
101 |         let tmp_callback = Box::new(|| {
    |                                     ^^ may outlive borrowed value `button`
102 |             button.do_stuff();
    |             ------ `button` is borrowed here
    |
help: to force the closure to take ownership of `button` (and any other referenced variables), use the `move` keyword, as shown:
    |         let tmp_callback = Box::new(move || {

Обратите внимание на блок help внизу, вам нужно использовать закрытие move, потому что, когда функция new завершается, переменная button в стеке выйдет за пределы области видимости. Единственный способ избежать этого - передать право собственности на сам обратный вызов. Таким образом, вы измените

let tmp_callback = Box::new(|| {

to

let tmp_callback = Box::new(move || {

Теперь вы получите вторую ошибку:

    error[E0382]: use of moved value: `button`
   --> src/main.rs:107:9
    |
102 |         let tmp_callback = Box::new(move || {
    |                                     ------- value moved (into closure) here
...
107 |         button
    |         ^^^^^^ value used here after move
    |
    = note: move occurs because `button` has type `std::sync::Arc<Button>`, which does not implement the `Copy` trait

И здесь ошибка может быть немного яснее. Вы пытаетесь передать владение значением button в закрытие обратного вызова, но вы также используете его внутри тела функции new, когда вы возвращаете его, и у вас не может быть двух разных попыток владеть ценностью.

Надеюсь, вы догадались, как решить эту проблему. Вы должны сделать копию, которой вы можете стать владельцем. Вы захотите затем изменить

let tmp_callback = Box::new(move || {
    button.do_stuff();

to

let button_clone = button.clone();
let tmp_callback = Box::new(move || {
    button_clone.do_stuff();

Теперь вы создали новый объект Button и вернули Arc для самого объекта, а также предоставили право собственности на второй Arc самому обратному вызову.

Обновлять

Учитывая ваш комментарий, здесь действительно существует проблема циклических зависимостей, поскольку ваш объект Clickable владеет ссылкой на Button, а Button владеет ссылкой на Clickable. Самый простой способ исправить это здесь - обновить этот код в третий раз, начиная с

let button_clone = button.clone();
let tmp_callback = Box::new(move || {
    button_clone.do_stuff();

to

let button_weak = Arc::downgrade(&button);
let tmp_callback = Box::new(move || {
    if let Some(button) = button_weak.upgrade() {
        button.do_stuff();
    }
});

поэтому Clickable будет содержать только слабую ссылку на Button, и если Button больше не ссылается, обратный вызов не будет работать.

Вы также, вероятно, захотите рассмотреть возможность создания clickables списка Weak ссылок вместо сильных ссылок, чтобы вы могли удалить из него элементы, когда элемент, на который они ссылаются, будет удален.

person loganfsmyth    schedule 17.08.2017
comment
Да, это решение, которое я тоже придумал. Есть ли другой способ, например, чтобы очистить внутреннюю кнопку, которую можно щелкнуть, когда кнопка будет сброшена, с временем жизни? В любом случае спасибо за ваши усилия! - person hodasemi; 18.08.2017
comment
Обновил ответ. - person loganfsmyth; 18.08.2017
comment
Большое тебе спасибо. Кроме того, мои обратные вызовы теперь являются монстрами, потому что у меня есть до 4 арок, в которых я должен делать то же самое, что и с кнопкой. - person hodasemi; 18.08.2017
comment
Вы также можете подумать о том, чтобы сделать Clickable чертой, затем реализовать эту черту для Button и тому подобного, тогда вы должны зарегистрировать это вместо этого. - person loganfsmyth; 18.08.2017