使用作为闭包参数传​​递的引用调用可变方法时,无法推断适当的生存期 [英] Cannot infer an appropriate lifetime when calling a mutable method with references passed as closure arguments

查看:84
本文介绍了使用作为闭包参数传​​递的引用调用可变方法时,无法推断适当的生存期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在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 pushes 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆