rust 将引用类型分组为单个值的好策略是什么?

dm7nw8vv  于 2023-04-12  发布在  其他
关注(0)|答案(1)|浏览(84)

我有一个签名,看起来像这样(没有必要指定生命周期参数'a):

fn equip_slot<'a, I: Join>(
    log: &'a mut WriteExpect<GameLog>,
    entities: &'a Read<EntitiesRes>,
    backpack: &'a mut WriteStorage<InBackpack>,
    items: I,
    equipped_items: &'a mut WriteStorage<Equipped>,
    names: &'a ReadStorage<Name>,
    new_equip: Equipped,
    new_equip_ent: Entity,
) -> HashSet<(Entity, Item)>
where
    I: Copy,
    I::Type: IsItem,

cargo clippy正确地指出了这个函数有太多的参数,我尝试了几种方法将这些参数分组到一个元组中,但是创建元组(甚至引用的元组)的行为似乎会造成一些困难。
例如:

type EquipData<'a, I> = (
    &'a Read<'a, EntitiesRes>,
    &'a mut WriteExpect<'a, GameLog>,
    &'a mut WriteStorage<'a, InBackpack>,
    I,
    &'a mut WriteStorage<'a, Equipped>,
    &'a ReadStorage<'a, Name>,
);

// ...

fn equip_slot<'a, I: Join + Copy>(
    equip_data: EquipData<'a, I>,
    new_equip: Equipped,
    new_equip_ent: Entity,
) -> HashSet<(Entity, Item)>
where
    I::Type: IsItem,

// Then at the call site, the following original line:
equip_slot(&mut log, &entities, &mut backpack, &items, &mut equipped, &names, new_equip, useitem.item);
// is changed to
equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);

但是现在我得到了很多错误,特别是与生命周期有关的错误。有没有更好的方法来处理这个问题,或者我在当前的方法中做错了什么?下面是错误:

error[E0521]: borrowed data escapes outside of closure
   --> src/inventory_system.rs:180:25
    |
117 |             mut log,
    |             ------- `log` declared here, outside of the closure body
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: lifetime may not live long enough
   --> src/inventory_system.rs:177:53
    |
128 |               mut equipped,
    |               ------------ lifetime `'2` appears in the type of `equipped`
...
148 |               .for_each(|(player_entity, _player, useitem, player_name)| {
    |                         ------------------------------------------------ lifetime `'1` represents this closure's body
...
177 |                       targets.first().iter().for_each(|target| {
    |  _____________________________________________________^
178 | |                         let new_equip = Equipped::new(**target, &player_equip, &equip.allowed_slots);
179 | |                         // TODO: warn on non-unit discard?:
180 | |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
181 | |                      });
    | |______________________^ closure capture requires that `'1` must outlive `'2`
    |
    = note: closure implements `FnMut`, so references to captured variables can't escape the closure

error[E0499]: cannot borrow `log` as mutable more than once at a time
   --> src/inventory_system.rs:187:49
    |
128 |             mut equipped,
    |             ------------ lifetime `'2` appears in the type of `equipped`
...
177 |                     targets.first().iter().for_each(|target| {
    |                                                     -------- first mutable borrow occurs here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                         --------------------------------------------------------------------------------------------------------
    |                         |                           |
    |                         |                           first borrow occurs due to use of `log` in closure
    |                         argument requires that `log` is borrowed for `'2`
...
187 |                         targets.iter().for_each(|target| {
    |                                                 ^^^^^^^^ second mutable borrow occurs here
...
194 |                                 log.entries.push(format!(
    |                                 --- second borrow occurs due to use of `log` in closure

error[E0499]: cannot borrow `log` as mutable more than once at a time
   --> src/inventory_system.rs:220:29
    |
128 |             mut equipped,
    |             ------------ lifetime `'2` appears in the type of `equipped`
...
177 |                     targets.first().iter().for_each(|target| {
    |                                                     -------- first mutable borrow occurs here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                         --------------------------------------------------------------------------------------------------------
    |                         |                           |
    |                         |                           first borrow occurs due to use of `log` in closure
    |                         argument requires that `log` is borrowed for `'2`
...
220 |                             log.entries.push(format!(
    |                             ^^^ second mutable borrow occurs here

error[E0499]: cannot borrow `log` as mutable more than once at a time
   --> src/inventory_system.rs:234:34
    |
128 |             mut equipped,
    |             ------------ lifetime `'2` appears in the type of `equipped`
...
177 |                     targets.first().iter().for_each(|target| {
    |                                                     -------- first mutable borrow occurs here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                         --------------------------------------------------------------------------------------------------------
    |                         |                           |
    |                         |                           first borrow occurs due to use of `log` in closure
    |                         argument requires that `log` is borrowed for `'2`
...
234 |                             .map(|victim| {
    |                                  ^^^^^^^^ second mutable borrow occurs here
...
245 |                                     log.entries.push(format!(
    |                                     --- second borrow occurs due to use of `log` in closure

error[E0597]: `log` does not live long enough
   --> src/inventory_system.rs:180:53
    |
148 |             .for_each(|(player_entity, _player, useitem, player_name)| {
    |                       ------------------------------------------------ value captured here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                                                     ^^^ borrowed value does not live long enough
...
259 |     }
    |     -
    |     |
    |     `log` dropped here while still borrowed
    |     borrow might be used here, when `log` is dropped and runs the destructor for type `specs::Write<'_, GameLog, PanicHandler>`

error[E0597]: `backpack` does not live long enough
   --> src/inventory_system.rs:180:63
    |
148 |             .for_each(|(player_entity, _player, useitem, player_name)| {
    |                       ------------------------------------------------ value captured here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                                                               ^^^^^^^^ borrowed value does not live long enough
...
259 |     }
    |     -
    |     |
    |     `backpack` dropped here while still borrowed
    |     borrow might be used here, when `equipped` is dropped and runs the destructor for type `Storage<'_, components::Equipped, FetchMut<'_, MaskedStorage<components::Equipped>>>`
    |
    = note: values in a scope are dropped in the opposite order they are defined

error[E0597]: `equipped` does not live long enough
   --> src/inventory_system.rs:180:86
    |
148 |             .for_each(|(player_entity, _player, useitem, player_name)| {
    |                       ------------------------------------------------ value captured here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                                                                                      ^^^^^^^^ borrowed value does not live long enough
...
259 |     }
    |     -
    |     |
    |     `equipped` dropped here while still borrowed
    |     borrow might be used here, when `equipped` is dropped and runs the destructor for type `Storage<'_, components::Equipped, FetchMut<'_, MaskedStorage<components::Equipped>>>`

error[E0597]: `names` does not live long enough
   --> src/inventory_system.rs:180:97
    |
148 |             .for_each(|(player_entity, _player, useitem, player_name)| {
    |                       ------------------------------------------------ value captured here
...
180 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                                                                                                 ^^^^^ borrowed value does not live long enough
...
259 |     }
    |     -
    |     |
    |     `names` dropped here while still borrowed
    |     borrow might be used here, when `log` is dropped and runs the destructor for type `specs::Write<'_, GameLog, PanicHandler>`
    |
    = note: values in a scope are dropped in the opposite order they are defined

error[E0382]: use of partially moved value: `equip_data`
   --> src/inventory_system.rs:347:21
    |
270 |     let (entities, log, backpack, items, equipped_items, names) = equip_data;
    |                                          -------------- value partially moved here
...
347 |                     equip_data,
    |                     ^^^^^^^^^^ value used here after partial move
    |
    = note: partial move occurs because `equip_data.4` has type `&mut Storage<'_, components::Equipped, FetchMut<'_, MaskedStorage<components::Equipped>>>`, which does not implement the `Copy` trait
cgyqldqp

cgyqldqp1#

通过对我的问题的评论(特别是建议在任何可能的地方使用不同的生命周期参数),我能够提出一个解决方案。
请注意,在我将一些参数提取到元组中之前,equip_slot函数的生命周期参数被推断出来。特别是,内部存储值的生命周期参数没有被指定(我只是-可选地-指定了对Storage结构的引用的生命周期参数)。但是当我们声明一个类型时,我们必须指定生命周期参数:

type EquipData<'a, 'b, I> = (
    &'a Read<'b, EntitiesRes>,
    &'a mut WriteExpect<'b, GameLog>,
    &'a mut WriteStorage<'b, InBackpack>,
    I,
    &'a mut WriteStorage<'b, Equipped>,
    &'a ReadStorage<'b, Name>,
);

这表明内部存储的值必须有一个不同的生命周期参数('b),从存储本身的引用('a)。特别是,对于可变引用,我们必须有例如&'a mut WriteExpect<'b, GameLog>而不是&'a mut WriteExpect<'a, GameLog>。如果我们不这样做,那么我们会得到几个错误,但特别是:

error: lifetime may not live long enough
   --> src/inventory_system.rs:160:53
    |
100 |               mut log,
    |               ------- lifetime `'2` appears in the type of `log`
...
131 |               .for_each(|(player_entity, _player, useitem, player_name)| {
    |                         ------------------------------------------------ lifetime `'1` represents this closure's body
...
160 |                       targets.first().iter().for_each(|target| {
    |  _____________________________________________________^
161 | |                         let new_equip = Equipped::new(**target, &player_equip, &equip.allowed_slots);
162 | |                         // TODO: warn on non-unit discard?:
163 | |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
164 | |                      });
    | |______________________^ closure capture requires that `'1` must outlive `'2`
    |
    = note: closure implements `FnMut`, so references to captured variables can't escape the closure

error[E0597]: `log` does not live long enough
   --> src/inventory_system.rs:163:53
    |
131 |             .for_each(|(player_entity, _player, useitem, player_name)| {
    |                       ------------------------------------------------ value captured here
...
163 |                         equip_slot((&entities, &mut log, &mut backpack, &items, &mut equipped, &names), new_equip, useitem.item);
    |                                                     ^^^ borrowed value does not live long enough
...
242 |     }
    |     -
    |     |
    |     `log` dropped here while still borrowed
    |     borrow might be used here, when `log` is dropped and runs the destructor for type `specs::Write<'_, GameLog, PanicHandler>`

我们的引用值(例如log)可能会被删除,但我们仍然需要确保Storage值本身比引用log更长寿,因为它是通过闭包捕获的(并且可能在闭包外部使用)。因此我们需要通过单独的生命周期参数'a'b进行解耦。在我们的例子中,这就是我们需要的所有分辨率。
现在,我们可以回到equip_slot自动推断生命周期参数:

fn equip_slot<I: Join + Copy>(
    equip_data: EquipData<I>,
    new_equip: Equipped,
    new_equip_ent: Entity,
) -> HashSet<(Entity, Item)>
where
    I::Type: IsItem,

相关问题