如何表示共享可变状态?
How to represent shared mutable state?
我正在尝试学习 Rust,但我唯一做的就是不断碰壁,试图将(对我来说)熟悉的 Java 概念硬塞进它的类型系统中。或者尝试硬塞Haskell概念等
我想用 Player
和许多 Resource
编写一个游戏。每个 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
的可变引用,我什至无法拥有同一个所有者的两个 Resource
。
解决此类情况的 Rust 方法是什么?
我把我的问题简单化了,虽然 Shepmaster 的回答是正确的,但这不是我想要得到的(因为我问的不是我真正想问的)。我会尝试改写它并添加更多上下文。
- 资源以某种方式连接 - 所有资源的地图
资源形成一个(非)有向图。
- 每个玩家可以拥有很多资源,每个资源可以由一个玩家拥有。玩家应该能够从他们拥有的资源中获得积分。我想到了这样的签名:
fn addPoints(&mut self, allResources: &ResourcesMap) -> ()
.
- 玩家可以从另一个玩家那里接管与其资源之一相连的资源。这可能会导致其他玩家损失一些分数。
问题:
- 如何在 Rust 中表示这样的图(可能是循环结构,其中每个节点都可以从许多节点指向)?
- 原问题:如果
Resource
指向一个Player
,我无法修改播放器!
Resource
s 指向 Player
因为 - 执行此类操作的自然方式是从玩家 A 的一些资源开始,通过地图移动到玩家 B 的资源,然后从该资源给玩家 B 以减去积分。这在 Rust 中似乎并不自然(至少对我而言)。
Each Resource can be owned by one Player.
然后让类型这样做:
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个资源,但他们仍然是资源的唯一拥有者。
cell documentation page 有很好的例子。 Rust 总是试图保护你不做坏事(比如对同一事物有两个可变引用)。因此它不像使用 Rust 的内置引用那样 "easy",因为你需要进行运行时检查(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());
}
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?
您并没有牺牲编译时的安全性。 Rust 确保(在编译时)你正确地使用了你的库。不过,如果您使用 borrow*
函数,您的程序可能会在运行时 崩溃 。如果你改用 try_borrow*
函数,你可以检查它是否成功,如果不成功,做一些回退操作。
您还可以根据您的类型 (Rc<RefCell<Player>>
) 使用 RefCell
的引用计数框。然后你只需要确保你不创建循环,否则你的内存将永远不会被释放。这会更像 Java(尽管 Java 会自动找到循环)。
我正在尝试学习 Rust,但我唯一做的就是不断碰壁,试图将(对我来说)熟悉的 Java 概念硬塞进它的类型系统中。或者尝试硬塞Haskell概念等
我想用 Player
和许多 Resource
编写一个游戏。每个 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
的可变引用,我什至无法拥有同一个所有者的两个 Resource
。
解决此类情况的 Rust 方法是什么?
我把我的问题简单化了,虽然 Shepmaster 的回答是正确的,但这不是我想要得到的(因为我问的不是我真正想问的)。我会尝试改写它并添加更多上下文。
- 资源以某种方式连接 - 所有资源的地图 资源形成一个(非)有向图。
- 每个玩家可以拥有很多资源,每个资源可以由一个玩家拥有。玩家应该能够从他们拥有的资源中获得积分。我想到了这样的签名:
fn addPoints(&mut self, allResources: &ResourcesMap) -> ()
. - 玩家可以从另一个玩家那里接管与其资源之一相连的资源。这可能会导致其他玩家损失一些分数。
问题:
- 如何在 Rust 中表示这样的图(可能是循环结构,其中每个节点都可以从许多节点指向)?
- 原问题:如果
Resource
指向一个Player
,我无法修改播放器!
Resource
s 指向 Player
因为 - 执行此类操作的自然方式是从玩家 A 的一些资源开始,通过地图移动到玩家 B 的资源,然后从该资源给玩家 B 以减去积分。这在 Rust 中似乎并不自然(至少对我而言)。
Each Resource can be owned by one Player.
然后让类型这样做:
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个资源,但他们仍然是资源的唯一拥有者。
cell documentation page 有很好的例子。 Rust 总是试图保护你不做坏事(比如对同一事物有两个可变引用)。因此它不像使用 Rust 的内置引用那样 "easy",因为你需要进行运行时检查(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());
}
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?
您并没有牺牲编译时的安全性。 Rust 确保(在编译时)你正确地使用了你的库。不过,如果您使用 borrow*
函数,您的程序可能会在运行时 崩溃 。如果你改用 try_borrow*
函数,你可以检查它是否成功,如果不成功,做一些回退操作。
您还可以根据您的类型 (Rc<RefCell<Player>>
) 使用 RefCell
的引用计数框。然后你只需要确保你不创建循环,否则你的内存将永远不会被释放。这会更像 Java(尽管 Java 会自动找到循环)。