如何表示共享的可变状态? [英] How to represent shared mutable state?

查看:50
本文介绍了如何表示共享的可变状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我想用一个 Player 和许多 Resource 编写一个游戏.每个Resource可以被一个Player拥有:

struct Player {积分:i32,}struct Resource<'a>{所有者:选项<&'a Player>,}fn 主(){让 mut player = Player { 点数:0 };让 mut 资源 = Vec::new();资源.推送(资源{所有者:一些(和播放器),});玩家点数 = 30;}

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

error[E0506]: 不能分配给 `player.points` 因为它是借用的-->src/main.rs:15:5|13 |所有者:一些(和播放器),|------ `player.points` 的借用发生在这里14 |});15 |玩家点数 = 30;|^^^^^^^^^^^^^^^^^^ 分配给借来的`player.points` 发生在这里

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

解决此类情况的 Rust 方法是什么?

<小时>

我把我的问题简单化了,虽然 Shepmaster 的回答是正确的答案,但这不是我想要的(因为我问的不是我真正想问的).我会尝试重新措辞并添加更多上下文.

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

问题:

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

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

解决方案

cell文档页面 有相当不错的例子.Rust 将始终试图保护您免于做坏事(例如对同一事物有两个可变引用).因此,它不像使用 Rust 的内置引用那么容易",因为您需要进行运行时检查(Rust 引用在编译时检查).

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

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

使用 std::cell::RefCell;结构播放器{积分:i32,}//仍然需要生命周期来保证资源//不要超过他们的玩家struct Resource<'a>{所有者:&'a RefCell,}impl'a>资源a{fn test(&self) ->i32 {self.owner.borrow().points}}fn 主(){让玩家 = RefCell::new(Player { 点数: 0 });让 mut 资源 = Vec::new();resources.push(Resource { owner: &player });player.borrow_mut().points = 30;println!("{:?}", resources[0].test());}

<小时><块引用>

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

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

您还可以将 RefCell 的引用计数框用于您的类型(Rc>).那么你只需要确保你不创建循环,否则你的内存将永远不会被释放.这将更像 Java(尽管 Java 会自动查找循环).

I'm trying to learn Rust, but the only thing I do is keep hitting the wall trying to shoehorn familiar (to me) Java concepts into its type system. Or try to shoehorn Haskell concepts, etc.

I want to write a game with a Player and many Resources. Each Resource can be owned by one 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;
}

It doesn't compile, because I can't have the resource point to player, while at the same time modifying it:

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

Moreover, if the Resource owned a mutable reference to Player, I couldn't even have two Resources with the same owner.

What is the Rust way to solve such cases?


I oversimplified my question, and while Shepmaster's answer is a correct answer to it, it's not what I wanted to get (because what I asked was not what I really wanted to ask). I'll try to rephrase it and add more context.

  1. The resources are connected in some way - the map of all resources forms a (un)directed graph.
  2. Each player can own many resources, each resource can be owned by one player. The player should be able to get points from resources they own. I thought of a signature like: fn addPoints(&mut self, allResources: &ResourcesMap) -> ().
  3. The player can take over a resource connected to one of their resources from another player. It could result in some points loss for the other player.

Problems:

  1. How to represent such graph in Rust (a possibly cyclic structure, where each node can be pointed to from many nodes)?
  2. The original problem: if the Resource points to a Player, I can't modify the player!

Resources point to Player because - the natural way to do such an operation would be to start from some of Player A's resources, move through the map to a player's B resource and from that resource to player B to subtract the points. It just doesn't seem natural in Rust (at least for me).

解决方案

The cell documentation page has rather good examples. Rust will always try to protect you from doing bad things (like having two mutable references to the same thing). Therefor it's not quite as "easy" as using Rust's built-in references, since you need to do runtime-checking (Rust references are checked at compile-time).

The RefCell type exists just for that. It checks the mutability rules at runtime. You will get some memory and computation-time overhead, but you end up with the same memory-safety that Rust promises in it's compile-time checks.

Your example ported to RefCell looks like the following.

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());
}


My concern is, if what I'm trying to do is trying to write Java code in Rust, can it be done in a Rust-way without sacrificing compile time safety? Avoid that shared mutable state at all?

You are not sacrificing compile-time-safety. Rust makes sure (at compile-time) that you are using your libraries correctly. Still, your program might panic at runtime if you use the borrow* functions. If you use the try_borrow* functions instead, you can check if it succeeded and if not, do some fallback operation.

You can also use a reference counted box of a RefCell to your type (Rc<RefCell<Player>>). Then you only need to make sure that you do not create cycles, or your memory will never be freed. This would be much more Java like (although Java automatically finds cycles).

这篇关于如何表示共享的可变状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆