更改指向向量元素的不可变引用

Mutate a immutable reference that points to vector element

提问人:JameEnder 提问时间:8/18/2022 更新时间:8/19/2022 访问量:589

问:

我想要一个名为 Outcome 的结构,它包含对实体的引用。然后,我想找到它所指向的实体,可变地借用它,并根据结果的效果来改变它。我的代码现在如下所示

fn main() {
    let mut entities = vec![
        Entity {
            name: "George".to_string(),
            current_hp: 200.0,
            damage: 10.0,
        },
        Entity {
            name: "Jacob".to_string(),
            current_hp: 100.0,
            damage: 5.0,
        },
    ];

    let outcome = Outcome {
        caster: &entities[0],
        target: &entities[1],
        effect: Effect::Damage(entities[0].damage),
    };

    match outcome.effect {
        Effect::Damage(amount) => {
            outcome.target.current_hp -= amount;
        }
    }
}

这当然不起作用,因为我正在尝试修改不可变的引用。当我在作用域中具有可变向量时,我可以以某种方式将不可变引用转换为可变引用吗?或者也许有一种更生疏的方法来解决这个问题?

(作为信息,Outcome 是由函数返回的结构,我将不可变的引用传递给它,然后它以效果返回它们)。

我发现的唯一可行的解决方案是在这样的不安全块中更改不可变的引用

match outcome.effect {
      Effect::Damage(amount) => unsafe {
          let target = outcome.target as *const Entity as *mut Entity;
          (*target).current_hp -= amount;
      },
}

参考 可变

评论

1赞 isaactfa 8/18/2022
如果要通过引用进行更改,则它必须是可变引用。从不可变引用获取可变引用是绝对不安全的,除非它通过 UnsafeCell 发生。
0赞 isaactfa 8/18/2022
(或者显然是基于 like 的安全抽象UnsafeCellRefCell)
0赞 Cerberus 8/19/2022
请注意,转换也不合理。仅当正确检查生成的引用的唯一性时,才能这样做。&UnsafeCell<T> -> &mut UnsafeCell<T>&UnsafeCell<T> -> &mut T
1赞 Bamontan 8/19/2022
Outcome 不能保存可变的 refs 吗?

答:

3赞 vgel 8/19/2022 #1

您可以在可变向量中使用索引:

fn main() {
    let mut entities = vec![
        Entity {
            name: "George".to_string(),
            current_hp: 200.0,
            damage: 10.0,
        },
        Entity {
            name: "Jacob".to_string(),
            current_hp: 100.0,
            damage: 5.0,
        },
    ];

    let outcome = Outcome {
        caster: 0,
        target: 1,
        effect: Effect::Damage(entities[0].damage),
    };

    match outcome.effect {
        Effect::Damage(amount) => {
            entities[outcome.target].current_hp -= amount;
        }
    }
}

这需要一定的纪律:你显然不能对向量重新排序,否则索引也会变得无效。要支持在不移动元素的情况下取消实体,您可以将它们包装在 Option 中,并且取消实体会将其插槽设置为 。生成实体需要在向量中搜索空闲槽(又名 a),或者在没有空闲槽的情况下将 a 附加到向量的末尾以创建新槽。NoneNoneSome(entity)

这导致了一个微妙的问题:在生成和使用索引之间,它所指向的实体可能会死亡,并且插槽可以重新用于不同的实体,从而导致效果应用于错误的实体。这可以使用分代索引来修复,其中实体索引实际上是一个元组。向量中的每个槽都变为 ,当一个实体在重用的槽中生成时,它会递增槽的生成。使用索引访问实体(现在通过 getter 方法)将检查索引的生成与插槽的生成,如果生成不匹配,则返回。(index, generation)(usize /*generation*/, Option<Entity>)None

一旦你实现了所有这些,你就已经进入了实体组件系统,如 Bevy 的 ECS、Hecs、Legion 或其他在 Rust 游戏开发生态系统中流行的库,因为它们正好解决了这些问题:-)

评论

0赞 JameEnder 8/19/2022
这是一个非常优雅的解决方案,我不知道 Bevy 使用类似的东西。谢谢!