闭包可能比当前函数的寿命更长

2024-04-20

我刚刚开始学习 Rust。为此,我正在用 Rust 重写我的 C++ 项目,但最大的问题是闭包的生命周期等。

我创建了一个绝对最小的问题场景及以下:

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 and my Context需要彼此。有没有更好的方法来创建这种依赖关系?


摆脱你最初的代码

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值进入回调闭包,但是你also在体内使用它new当你返回它时,它会被调用,并且你不能有两个不同的东西试图拥有这个值。

希望这个问题的解决方案就是您所猜测的。您必须复印一份can取得所有权。然后你会想要改变

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回调本身。

Update

鉴于您的评论,这里确实存在循环依赖问题,因为您的Clickable对象拥有引用的所有权Button, while 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();
    }
});

so the Clickable只会持有一个弱引用Button,如果Button不再被引用,回调将是无操作。

您可能还想考虑制作clickables的列表Weak引用而不是强引用,因此当删除它们引用的项目时,您可以从中删除项目。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

闭包可能比当前函数的寿命更长 的相关文章

随机推荐