使用作为闭包参数传递的引用调用可变方法时,无法推断适当的生存期 [英] Cannot infer an appropriate lifetime when calling a mutable method with references passed as closure arguments
问题描述
我正在尝试在Rust中制作一个小型游戏。我想使用类似于实体组件系统模式的东西来处理所有游戏对象。
I'm trying to make a small game in Rust. I want to use something similar to the entity-component-system pattern to handle all the game objects.
我的一般想法是拥有一个 GameLoop
结构,其中包含所有必要的数据以更新和绘制游戏(屏幕,时间戳记,...)。
My general idea is to have a GameLoop
struct which holds all the necessary data to update and draw the game (the screen, a timestamp, ...).
World
结构应包含所有游戏实体,并在 dispatch
函数中对其进行更新。它也会调用存储在 World
结构中的所有已注册的回调(也就是系统)。但是,在示例代码中它们是多余的。
The World
struct is supposed to hold all the game entities and update them in the dispatch
function. It calls all the registered callbacks which are stored in the World
struct as well (those are the "systems"). They're redundant in the sample code, though.
我试图尽可能地分解代码,只包括相关部分。
I've tried to break down the code as much as possible and only include the relevant parts.
use std::marker::PhantomData;
struct Something;
///The "somethings" are things like the display, a timestamp, ...
struct GameLoop {
sth: Something,
sth2: Something,
}
///C = Context
///The type that is passed to every entity to give it access to things like the delta time
struct World<C> {
phantom: PhantomData<C>, //This is here so Rust doesn't complain about the type parameter not being used
}
///The data that is passed to the system functions every frame
struct TickData<'a> {
delta: u64,
sth: &'a Something,
sth2: &'a mut Something,
}
impl GameLoop {
fn new() -> GameLoop {
GameLoop {
sth: Something {},
sth2: Something {},
}
}
///One game "tick" - Supposed to do things like calculating delta time, swapping buffers, ...
///Those are then passed to the callback
fn update<F: FnMut(u64, &Something, &mut Something)>(&mut self, f: &mut F) {
f(0, &self.sth, &mut self.sth2);
}
}
impl<C> World<C> {
fn new() -> World<C> {
World { phantom: PhantomData }
}
///Supposed to update all the game entities
fn dispatch(&mut self, context: &mut C) {
//...
}
}
impl<'a> TickData<'a> {
fn new<'b>(delta: u64, sth: &'b Something, sth2: &'b mut Something) -> TickData<'b> {
TickData {
delta: delta,
sth: sth,
sth2: sth2,
}
}
}
fn main() {
let mut game_loop = GameLoop::new();
let mut world = World::<TickData>::new();
//The game update function, called once per frame
let mut update_fnc = |delta: u64, sth: &Something, sth2: &mut Something| {
let mut tick_data = TickData::new(delta, sth, sth2);
&world.dispatch(&mut tick_data); //If this line is commented out, it compiles fine
//...
};
loop {
game_loop.update(&mut update_fnc); //Calculate the delta time, call the specified function and swap buffers
}
}
借用/有效期似乎存在问题。
There seems to be a problem with borrowing / lifetimes. The compiler is everything else but verbose.
问题似乎出在& world.dispatch(& mut tick_data)
调用游戏的更新功能,该功能应该更新所有游戏实体。如果我将其注释掉,程序将编译而没有任何错误。
The problem seems to be the &world.dispatch(&mut tick_data)
call in the update function of the game, which is supposed to update all the game entities. If I comment it out the program compiles without any errors.
这是编译器告诉我的:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'b in function call due to conflicting requirements
--> src/main.rs:66:29
|
66 | let mut tick_data = TickData::new(delta, sth, sth2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 65:77...
--> src/main.rs:65:78
|
65 | let mut update_fnc = |delta: u64, sth: &Something, sth2: &mut Something| {
| ______________________________________________________________________________^ starting here...
66 | | let mut tick_data = TickData::new(delta, sth, sth2);
67 | |
68 | | &world.dispatch(&mut tick_data); //If this line is commented out, it compiles fine
69 | |
70 | | //...
71 | | };
| |_____^ ...ending here
note: ...so that reference does not outlive borrowed content
--> src/main.rs:66:55
|
66 | let mut tick_data = TickData::new(delta, sth, sth2);
| ^^^^
note: but, the lifetime must be valid for the expression at 74:25...
--> src/main.rs:74:26
|
74 | game_loop.update(&mut update_fnc); //Calculate the delta time, call the specified function and swap buffers
| ^^^^^^^^^^^^^^^
note: ...so that reference is valid at the time of borrow
--> src/main.rs:74:26
|
74 | game_loop.update(&mut update_fnc); //Calculate the delta time, call the specified function and swap buffers
| ^^^^^^^^^^^^^^^
我根本无法发现错误的原因。这些函数以一种过程方式被调用,由于我只是借用了大部分数据,因此生命周期应该没有问题。
I simply can't spot the cause of the error. The functions get called in a kind of procedural way and since I'm only borrowing most of the data there should be no problem with the lifetimes.
当我删除引用时来自 TickData
结构,因此它只包含针对 Copy
特征实现的值,它也可以正常工作。
When I remove the references from the TickData
struct so it only contains values that are implemented for the Copy
trait, it works as well.
我通常不是那种张贴代码墙并要求人们对其进行修复的人,但是我现在真的很笨。
I'm usually not the kind of person to post a wall of code and ask people to fix it but I'm really clueless right now.
推荐答案
您的代码没有 one 正确的解决方案。它看起来过于复杂,我不知道您为什么要做出一些设计决策。如果没有我要说的话,那么我道歉,您将不得不等待下一个答复者。
There's no one right solution to your code. It appears overly complicated and I don't know why you've made some of the design decisions you have. If nothing I say applies, then I apologize and you'll have to wait for the next answerer.
减少问题是正确的主意,但是为什么您停止?可以一直减小到
Reducing your problem is the right idea, but why did you stop? It can be reduced all the way down to
struct Something;
struct World<'a> {
x: TickData<'a>,
}
impl<'a> World<'a> {
fn dispatch(&mut self, context: &TickData<'a>) {}
}
struct TickData<'a>(&'a Something);
fn update_fnc(world: &mut World, sth: &Something) {
let tick_data = TickData(sth);
world.dispatch(&tick_data);
}
fn main() {}
通过试用--and-error,人们可以找到一些解决方案:
By trial-and-error, one can find some "solutions":
impl<'a> World<'a> {
fn dispatch(&self, context: &TickData<'a>) {}
}
或
impl<'a> World<'a> {
fn dispatch(&mut self, context: &TickData) {}
}
或
impl<'a> World<'a> {
fn dispatch<'b>(&'b mut self, context: &'b TickData<'b>) {}
}
要对该问题进行极其详尽的分析,要比我能提供的要好,请查看为什么链接生命周期仅对可变引用有意义?。
For an extremely thorough analysis of this problem, better than I can give, check out Why does linking lifetimes matter only with mutable references?.
让我们再看一下您的主要方法:
Let's look at another aspect, back at your main method:
let mut world = World::<TickData>::new();
我们知道 TickData
的生命周期为它,在这种情况下又是什么?我们不能像类型一样指定它,必须从用法中推断出来。那么在哪里使用它呢?
We know that TickData
has a lifetime in it, so what is it in this case? We can't specify it like a type, it must be inferred from the usage. So where is it used?
一个类似的例子是 Vec
。我们先创建一个 Vec
,然后将 Push
推到上面。那些推
es告诉我们 T
的具体类型是什么。您的代码做什么:
One analogy to look to is a Vec
. We create a Vec
and then push
things onto it later. Those push
es tell us what the concrete type of T
is. What does your code do:
let mut world = World::<TickData>::new();
// ...
world.dispatch(&mut tick_data);
您创建的类型包含一个 TickData
(这就是 PhantomData
所做的事情),然后调用一个推该类型的方法( fn dispatch(& mut self ,上下文:& mut C)
),因此第二个参数必须为所包含的类型,从而解析最终类型。
You create a type that you've said contains a TickData
(that's what PhantomData
does), then you call a method that "push"es that type (fn dispatch(&mut self, context: &mut C)
), therefore the second argument must be of the type contained, resolving the final type.
这会导致另一个问题:不知道这些参数的生存期有多长。
This leads to another problem: there's no clue how long the lifetimes of those arguments are.
但是,仅注释生存期是不够的:
However, simply annotating the lifetimes isn't enough:
fn update<'a, F>(&'a mut self, mut f: F)
where F: FnMut(u64, &'a Something, &'a mut Something)
{
f(0, &self.sth, &mut self.sth2);
}
进一步的复杂性是因为我们传递了 mutable 引用 sth2
到调度
。 dispatch
的定义允许它存储自身内的可变引用-生存期和类型匹配,并且是& mut self
。
This further complication is because we are passing the mutable reference sth2
to dispatch
. The definition of dispatch
allows it to store that mutable reference inside itself - the lifetimes and types match and it's a &mut self
.
这可能会导致多个可变别名,这是不允许的。
This could lead to multiple mutable aliases, which is disallowed.
我不知道为什么,您已经将世界
参数化了,但是您也许可以移动 C
到 dispatch
方法,完全删除 PhantomData
。这将删除 World
存储 C
的功能。
I don't know why you've parameterized your World
, but you might be able to just move the C
to the dispatch
method, removing PhantomData
completely. This removes the ability for World
to store C
.
这篇关于使用作为闭包参数传递的引用调用可变方法时,无法推断适当的生存期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!