简体   繁体   English

存储引用该对象中的对象的盒装闭包

[英]Storing a boxed closure which references an object in that object

I'm trying to implement a console system for the game I'm writing and have found a fairly simple system: I define a Console object that stores commands as boxed closures (specifically Box<FnMut + 'a> for some 'a ). 我正在尝试为正在编写的游戏实现一个控制台系统,并且发现了一个相当简单的系统:我定义了一个Console对象,该命令将命令存储为盒装闭包(特别是Box<FnMut + 'a>表示某些'a )。 This works for any component of the engine so long as the Console is created before anything else. 只要Console先于其他任何东西创建,此方法就适用于引擎的任何组件。

Unfortunately, this prevents me from adding commands that modify the Console itself, which means I can't create commands that simply print text or define other variables or commands. 不幸的是,这使我无法添加修改Console本身的命令,这意味着我无法创建仅打印文本或定义其他变量或命令的命令。 I've written a small example that replicates the error: 我写了一个小例子来复制错误:

use std::cell::Cell;

struct Console<'a> {
    cmds: Vec<Box<FnMut() + 'a>>,
}

impl<'a> Console<'a> {
    pub fn println<S>(&self, msg: S)
        where S: AsRef<str>
    {
        println!("{}", msg.as_ref());
    }

    pub fn add_cmd(&mut self, cmd: Box<FnMut() + 'a>) {
        self.cmds.push(cmd);
    }
}

struct Example {
    val: Cell<i32>,
}

fn main() {
    let ex = Example {
        val: Cell::new(0),
    };

    let mut con = Console {
        cmds: Vec::new(),
    };

    // this works
    con.add_cmd(Box::new(|| ex.val.set(5)));

    (con.cmds[0])();

    // this doesn't
    let cmd = Box::new(|| con.println("Hello, world!"));
    con.add_cmd(cmd);

    (con.cmds[1])();
}

And the error: 错误:

error: `con` does not live long enough
  --> console.rs:34:31
   |
34 |         let cmd = Box::new(|| con.println("Hello, world!"));
   |                            -- ^^^ does not live long enough
   |                            |
   |                            capture occurs here
35 |         con.add_cmd(cmd);
36 |     }
   |     - borrowed value dropped before borrower
   |
   = note: values in a scope are dropped in the opposite order they are created

error: aborting due to previous error

Is there a workaround for this, or a better system I should look into? 有没有解决方法,或者我应该研究更好的系统? This is on rustc 1.18.0-nightly (53f4bc311 2017-04-07) . 这是在rustc 1.18.0-nightly (53f4bc311 2017-04-07)

This is one of those fairly tricky resource borrowing conundrums that the compiler could not allow. 这是编译器不允许的那些相当棘手的资源借用难题之一。 Basically, we have a Console that owns multiple closures, which in turn capture an immutable reference to the same console. 基本上,我们有一个拥有多个闭包的Console ,这又捕获了对同一控制台的不可变引用。 This means two constraints: 这意味着两个约束:

  • Since Console owns the closures, they will live for as long as the console itself, and the inner vector will drop them right after Console is dropped. 由于Console拥有闭包,因此它们的生存期与控制台本身一样长,并且内部向量将在删除Console 后立即将其删除。
  • At the same time, each closure must not outlive Console , because otherwise we would end up with dangling references to the console. 同时,每次关闭都不得超过Console ,因为否则,我们最终将悬而未决地引用控制台。

It may seem harmless from the fact that the console and respective closures go out of scope at once. 从控制台和相应的封闭件立即超出范围的事实看,这似乎没有什么害处。 However, the drop method follows a strict order here: first the console, then the closures. 但是,这里的drop方法遵循严格的顺序:首先是控制台,然后是闭包。

Not to mention of course, that if you wish for closures to freely apply modifications to the console without interior mutability , you would have to mutably borrow it, which cannot be done over multiple closures. 当然,更不用说,如果您希望闭包可以在没有内部可变性的情况下自由地对控制台进行修改,那么您将不得不可变地借用它,这不能在多个闭包上完成。

An approach to solving the problem is to separate the two: let the console not own the closures, instead having them in a separate registry, and let the closures only borrow the console when calling the closure. 解决问题的一种方法是将两者分开:让控制台不拥有闭包,而是将它们放在单独的注册表中;让闭包仅在调用闭包时借用控制台。

This can be done by passing the console as an argument to the closures and moving the closure vector to another object ( Playground ): 这可以通过将控制台作为参数传递给闭包并将闭包向量移动到另一个对象( Playground )来完成:

use std::cell::Cell;

struct CommandRegistry<'a> {
    cmds: Vec<Box<Fn(&mut Console) + 'a>>,
}

impl<'a> CommandRegistry<'a> {
    pub fn add_cmd(&mut self, cmd: Box<Fn(&mut Console) + 'a>) {
        self.cmds.push(cmd);
    }
}

struct Console {
}

impl Console {
    pub fn println<S>(&mut self, msg: S)
        where S: AsRef<str>
    {
        println!("{}", msg.as_ref());
    }
}

struct Example {
    val: Cell<i32>,
}

fn main() {
    let ex = Example {
        val: Cell::new(0),
    };

    let mut reg = CommandRegistry{ cmds: Vec::new() };

    let mut con = Console {};

    // this works
    reg.add_cmd(Box::new(|_: &mut Console| ex.val.set(5)));
    (reg.cmds[0])(&mut con);

    // and so does this now!
    let cmd = Box::new(|c: &mut Console| c.println("Hello, world!"));
    reg.add_cmd(cmd);

    (reg.cmds[1])(&mut con);
}

I also took the liberty of making closures accept a mutable reference. 我还自由地使闭包接受了可变的引用。 No conflicts emerge here because we are no longer borrowing the console that was already borrowed when fetching the borrowing closure. 这里没有冲突,因为我们不再借用在获取借用封包时已经借用的控制台。 This way, the closures can also outlive the console. 这样,闭包也可以超过控制台。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM