【问题标题】:How to represent shared mutable state?如何表示共享可变状态?
【发布时间】:2015-02-09 20:20:55
【问题描述】:

我正在尝试学习 Rust,但我唯一要做的就是不断尝试将熟悉的(对我而言)Java 概念硬塞到它的类型系统中。或者尝试硬塞 Haskell 概念等。

我想编写一个包含Player 和许多Resources 的游戏。每个Resource 可以被一个Player 拥有:

struct Player {
    points: i32,
}

struct Resource<'a> {
    owner: Option<&'a Player>,
}

fn main() {
    let mut player = Player { points: 0 };
    let mut resources = Vec::new();
    resources.push(Resource {
        owner: Some(&player),
    });
    player.points = 30;
}

它没有编译,因为我不能让资源指向播放器,同时修改它:

error[E0506]: cannot assign to `player.points` because it is borrowed
  --> src/main.rs:15:5
   |
13 |         owner: Some(&player),
   |                      ------ borrow of `player.points` occurs here
14 |     });
15 |     player.points = 30;
   |     ^^^^^^^^^^^^^^^^^^ assignment to borrowed `player.points` occurs here

此外,如果Resource 拥有对Player 的可变引用,我什至不能拥有两个Resources 具有相同的所有者。

解决这种情况的 Rust 方法是什么?


我过于简单化了我的问题,虽然 Shepmaster 的答案是正确的答案,但这不是我想要得到的(因为我问的不是我真正想问的)。我会尝试改写它并添加更多上下文。

  1. 资源以某种方式连接 - 所有资源的地图 资源形成一个(无)有向图。
  2. 每个玩家可以拥有许多资源,每个资源可以由一个玩家拥有。玩家应该能够从他们拥有的资源中获得积分。我想到了这样的签名:fn addPoints(&amp;mut self, allResources: &amp;ResourcesMap) -&gt; ()
  3. 玩家可以从其他玩家手中接管与他们的一个资源相连的资源。这可能会导致其他玩家失去一些分数。

问题:

  1. 如何在 Rust 中表示这样的图(可能是循环结构,其中每个节点都可以从多个节点指向)?
  2. 原问题:如果Resource指向Player,我不能修改播放器!

Resources 指向Player 因为 - 执行此类操作的自然方法是从玩家 A 的一些资源开始,穿过地图移动到玩家 B 的资源,然后从该资源移动到玩家 B减去分数。这在 Rust 中似乎并不自然(至少对我而言)。

【问题讨论】:

    标签: rust


    【解决方案1】:

    cell documentation page 有很好的例子。 Rust 总是会试图保护你免于做坏事(比如有两个对同一事物的可变引用)。因此,它不像使用 Rust 的内置引用那么“简单”,因为您需要进行运行时检查(在编译时检查 Rust 引用)。

    RefCell 类型就是为此而存在的。它在运行时检查可变性规则。您将获得一些内存和计算时间开销,但最终会获得与 Rust 在其编译时检查中所承诺的相同的内存安全性。

    移植到RefCell 的示例如下所示。

    use std::cell::RefCell;
    
    struct Player {
        points: i32,
    }
    
    // the lifetime is still needed to guarantee that Resources
    // don't outlive their player
    struct Resource<'a> {
        owner: &'a RefCell<Player>,
    }
    
    impl<'a> Resource<'a> {
        fn test(&self) -> i32 {
            self.owner.borrow().points
        }
    }
    
    fn main() {
        let player = RefCell::new(Player { points: 0 });
        let mut resources = Vec::new();
        resources.push(Resource { owner: &player });
        player.borrow_mut().points = 30;
        println!("{:?}", resources[0].test());
    }
    

    我担心的是,如果我正在尝试用 Rust 编写 Java 代码,是否可以在不牺牲编译时安全性的情况下以 Rust 方式完成?完全避免共享可变状态?

    您并没有牺牲编译时的安全性。 Rust 确保(在编译时)你正确使用你的库。不过,如果您使用 borrow* 函数,您的程序可能会在运行时恐慌。如果你改用try_borrow*函数,你可以检查它是否成功,如果没有,做一些回退操作。

    您还可以将 RefCell 的引用计数框用于您的类型 (Rc&lt;RefCell&lt;Player&gt;&gt;)。然后你只需要确保你不创建循环,否则你的内存将永远不会被释放。这将更像 Java(尽管 Java 会自动找到循环)。

    【讨论】:

    • 听起来不错。我担心的是,如果我正在尝试用 Rust 编写 Java 代码,是否可以在不牺牲编译时安全性的情况下以 Rust 方式完成?完全避免共享可变状态?
    • 注意:你并没有牺牲编译时的安全性。 Rust 确保(在编译时)你正确使用你的库。不过,如果您使用 borrow* 函数,您的程序可能会在运行时出现恐慌。如果改为使用 try_borrow* 函数,则可以检查它是否成功,如果不成功,请执行一些回退操作。
    • 您还可以将引用计数框 (doc.rust-lang.org/std/rc/index.html) 用于您的类型的 RefCell。然后你只需要确保你不创建循环,否则你的内存将永远不会被释放。这将更像 java(尽管 java 会自动找到循环)
    【解决方案2】:

    每个资源可以由一个玩家拥有。

    然后让类型这样做:

    struct Player {
        points: i32,
        resources: Vec<Resource>,
    }
    
    struct Resource {
        gold: i32,
    }
    
    fn main() {
        let player1 = Player {
            points: 30,
            resources: vec![Resource { gold: 54 }],
        };
        let player2 = Player {
            points: 50,
            resources: vec![Resource { gold: 99 }],
        };
    
        // If you really need an array of all the resources...
        // Although this seems like you should just ask the Player to do something
        let mut resources: Vec<_> = vec![];
        resources.extend(player1.resources.iter());
        resources.extend(player2.resources.iter());
    }
    

    编辑感谢@ziggystar 指出我的原始版本允许玩家只有一个Resource。现在玩家可能拥有 N 个资源,但他们仍然是资源的唯一所有者。

    【讨论】:

    • 现在每个玩家都拥有一个资源。那不一样。 (没看问题)
    • 我编辑了我的问题,因为按照我所说的方式,正确的答案是你给我的答案,但它几乎没有推动我对 Rust 的理解。
    • 在创建结构实例的代码风格方面,最好的方法是:Player{Player {(带有空格)?
    • @ker,他在哪里说的? aturon.github.io/style/braces.html - 没有提及。
    • 我的意思是官方风格指南每次都有空格
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2016-04-05
    相关资源
    最近更新 更多