rust 如何表示共享可变状态?

f0ofjuux  于 12个月前  发布在  其他
关注(0)|答案(3)|浏览(93)

我正在努力学习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的答案是正确的,但这不是我想得到的答案(因为我问的不是我真正想问的)。
1.资源以某种方式连接-所有资源的Map形成一个(无)有向图。
1.每个玩家可以拥有多个资源,每个资源可以归一个玩家所有,玩家应该可以从自己拥有的资源中获得积分,我想到了一个签名:fn addPoints(&mut self, allResources: &ResourcesMap) -> ()
1.玩家可以从另一个玩家手中接管与他们的资源相连的资源。这可能会导致另一个玩家损失一些积分。
问题:
1.如何在Rust中表示这样的图(可能是循环结构,其中每个节点可以从许多节点指向)?
1.原来的问题:如果Resource指向一个Player,我不能修改播放器!
Resource s指向Player,因为--做这样一个操作的自然方法是从玩家A的一些资源开始,通过Map移动到玩家B的资源,然后从该资源移动到玩家B以减去点数。

pexxcrt2

pexxcrt21#

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*函数,你的程序可能会在运行时“panic”。如果你使用try_borrow*函数,你可以检查它是否成功,如果没有,做一些回退操作。
你也可以使用一个RefCell类型的引用计数框(Rc<RefCell<Player>>)。然后你只需要确保你没有创建循环,否则你的内存将永远不会被释放。这将更像Java(尽管Java会自动找到循环)。

ha5z0ras

ha5z0ras2#

每个资源可以由一个玩家拥有。
然后让类型执行此操作:

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个资源,但他们仍然是一个资源的唯一所有者。

iyr7buue

iyr7buue3#

这是一个常见的问题,Rc<RefCell<T>>是一个常见的答案。它在小示例中工作正常,或者当你只需要一点点共享时。但是当你的程序状态是一个有循环的图时,Rc往往会导致内存泄漏,RefCell往往会在运行时死机。你失去了一些你期望从Rust得到的编译时正确性,而且它也是令人痛苦的冗长。这在游戏中尤其是个问题。
对于游戏和一般具有图/关系状态的程序来说,一个更好的方法是使用索引而不是引用。你的例子可能看起来像这样(我将避免假设每个资源都被一个玩家唯一引用,以使解决方案更通用):

struct Player {
    resource_ids: Vec<usize>,
    points: i32,
}

struct Resource {
    owner_id: usize,
}

struct GameState {
    players: Vec<Player>,
    resources: Vec<Resource>,
}

fn new_player(state: &mut GameState) -> usize {
    state.players.push(Player { points: 0, resource_ids: Vec::new() });
    state.players.len() - 1
}

fn new_resource(state: &mut GameState, owner_id: usize) -> usize {
    state.resources.push(Resource { owner_id });
    let new_id = state.resources.len() - 1;
    state.players[owner_id].resource_ids.push(new_id);
    state.players[owner_id].points += 30;
    new_id
}

fn main() {
    let mut state = GameState { players: Vec::new(), resources: Vec::new() };
    let player_id = new_player(&mut state);
    let resource_id = new_resource(&mut state, player_id);
}

字符串
编写游戏是一个很深的主题,我们可以用很多方法来扩展这个例子。我们可能需要为PlayerResource使用不同的索引类型,而不是为所有内容使用usize,这样我们就不会把它们弄混了。如果我们需要支持删除,我们可能想用HashMap来代替Vec。但这只是一个开始。参见Using Rust For Game DevelopmentObject Soup is Made of Indexes

相关问题