如何表示共享可变状态?

PL_*_*lek 7 rust

我正在努力学习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;
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

而且,如果Resource拥有一个可变的引用Player,我甚至不能拥有两个Resource与同一所有者的s.

什么是Rust解决此类案件的方法?


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

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

问题:

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

Resources指向Player因为 - 进行此类操作的自然方式是从玩家A的某些资源开始,通过地图移动到玩家的B资源,然后从该资源移动到玩家B以减去这些点.它在Rust中似乎并不自然(至少对我而言).

oli*_*obk 7

单元文档页面具有相当好的例子.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());
}
Run Code Online (Sandbox Code Playgroud)

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

你不会牺牲编译时的安全性.Rust确保(在编译时)您正确使用库.但是,如果使用这些功能,您的程序可能会在运行时发生混乱borrow*.如果您使用这些try_borrow*函数,则可以检查它是否成功,如果不成功,则执行一些回退操作.

您还可以使用类型的引用计数框a RefCell(Rc<RefCell<Player>>).然后你只需要确保不创建循环,或者永远不会释放你的记忆.这将更像Java(尽管Java会自动查找周期).


She*_*ter 5

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

使类型这样做:

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());
}
Run Code Online (Sandbox Code Playgroud)

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

  • 现在每个玩家只拥有一个资源。那不一样。(没看问题) (3认同)