【问题标题】:Mutate a immutable reference that points to vector element改变指向向量元素的不可变引用
【发布时间】:2022-08-19 17:59:00
【问题描述】:

我想要一个名为 Outcome 的结构,它包含对实体的引用。然后我想找到它指向的实体,可变地借用它并根据 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;
      },
}

  • 如果你想通过一个引用发生变异,它必须是一个可变引用。这是绝不从不可变引用中获取可变引用是安全的,除非它通过UnsafeCell 发生。
  • (或者显然是基于UnsafeCell 的安全抽象,例如RefCell
  • 请注意,&UnsafeCell<T> -> &mut UnsafeCell<T> 转换也不可靠。只有 &UnsafeCell<T> -> &mut T 可以,如果正确检查了结果引用的唯一性。
  • Outcome 不能保存可变的 refs 吗?

标签: rust reference mutable


【解决方案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 中,并且消除实体会将其插槽设置为 None。生成实体需要在向量中搜索空闲槽(又名None),或者如果没有空闲槽,则将Some(entity) 附加到向量的末尾以创建新槽。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-09
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    • 2019-09-28
    • 1970-01-01
    • 2018-02-17
    • 2019-05-15
    相关资源
    最近更新 更多