gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-29 08:12:30 25 4
gpt4 key购买 nike

我正在尝试为我正在编写的游戏实现一个控制台系统,并且找到了一个相当简单的系统:我定义了一个 Console将命令存储为盒装闭包的对象(特别是 Box<FnMut + 'a> 对于某些 'a )。这适用于引擎的任何组件,只要 Console是在其他任何事情之前创建的。

不幸的是,这阻止了我添加修改 Console 的命令。本身,这意味着我无法创建仅打印文本或定义其他变量或命令的命令。我写了一个复制错误的小例子:

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])();
}

错误:

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

是否有解决此问题的方法,或者我应该研究更好的系统?这是在 rustc 1.18.0-nightly (53f4bc311 2017-04-07) .

最佳答案

这是编译器不允许的相当棘手的资源借用难题之一。基本上,我们有一个拥有多个闭包的 Console,这些闭包依次捕获对同一控制台的不可变引用。这意味着两个约束:

  • 由于 Console 拥有闭包,它们将与控制台本身一样存在,并且内部向量将在之后立即删除它们 Console 被丢弃。
  • 与此同时,每个闭包的生命周期不得超过 Console,否则我们最终会得到对控制台的悬空引用。

控制台和相应的闭包立即超出范围这一事实似乎无害。然而,drop method这里遵循严格的顺序:首先是控制台,然后是闭包。

当然更不用说,如果您希望闭包在没有 interior mutability 的情况下自由地将修改应用于控制台,您将不得不可变地借用它,这不能通过多个闭包来完成。

解决该问题的一种方法是将两者分开:让控制台不拥有闭包,而是将它们放在单独的注册表中,并且让闭包仅在调用闭包时借用控制台。

这可以通过将控制台作为参数传递给闭包并将闭包向量移动到另一个对象 (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);
}

我还冒昧地让闭包接受可变引用。这里不会出现冲突,因为我们不再借用在获取借用闭包时已经借用的控制台。这样,闭包也可以比控制台更有效。

关于rust - 存储引用该对象中的对象的盒装闭包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43301090/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com