【问题标题】:How to circumvent "cannot borrow `*self` as mutable more than once at a time" in a performant way for agent based simulation?对于基于代理的模拟,如何以一种高效的方式规避“不能一次多次借用 `*self` 作为可变变量”?
【发布时间】:2021-03-30 11:38:56
【问题描述】:

我想在 Rust 中实现基于代理的模拟,但遇到借用检查器。

代理应该生活在一个可变的网格中,在每个单元格中携带一个状态。 每个代理都带有一些可变状态。 通常,我会实现一组代理,例如作为HashMap 从网格位置到代理。 在模拟步骤中,我将遍历所有代理,然后根据代理自身的状态、该位置的网格状态以及附近其他代理的状态更新代理的状态。

虚构的示例可能如下所示:

use std::collections::HashMap;

struct Agent { // each agent carries some state
    id: i64,
    state: i32,
}

struct CellState { // some state of a grid cell
    state: i64,
}

struct Chart {
    agents: HashMap<usize, Agent>,
    grid: Vec<CellState>,
}

impl Chart {
    fn new(size: usize) -> Chart {
        let mut agents = HashMap::new(); // generate hash and populate with 2 agents
        agents.insert(10, Agent { id: 1, state: 1 });
        agents.insert(11, Agent { id: 2, state: 0 });
        let mut grid: Vec<CellState> = Vec::with_capacity(size);

        Chart {
            agents: agents,
            grid: grid,
        }
    }

    fn do_stuff(&mut self, agent: &mut Agent) {
        // here we want to update the state of agent,
        // based on the state of other agents in the grid
    }

    fn step_agents(&mut self) {
        for (_, agent) in &mut self.agents {
            self.do_stuff(agent);
        }
    }
}

fn main() {
    let mut ch = Chart::new(128);
    ch.step_agents();
}

此代码产生错误

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:37:13
   |
36 |         for (_, agent) in &mut self.agents {
   |                           ----------------
   |                           |
   |                           first mutable borrow occurs here
   |                           first borrow later used here
37 |             self.do_stuff(agent);
   |             ^^^^ second mutable borrow occurs here

我了解错误以及 Rust 编译器出现问题的原因。我不明白的是如何以高效的方式规避这个问题。

如果我不可变地借用对代理的引用,则无法更新其状态。在一个真实的例子中,代理会携带相当多的状态,因此克隆并不便宜。

实现这一点的惯用 Rust 方式是什么?

【问题讨论】:

  • 如果你能保证你不会违反do_stuff中的rust参考规则,你可以使用RefCell
  • 请注意,如果此代码有效,其行为将对self.values 的迭代顺序非常敏感,因为代理 A 可能使用代理 B 的“旧”值,而代理 B 使用代理 A 的“新”值值,因此代理永远不会真正同意网格的状态。如果您正在做类似元胞自动机的事情,其中​​“代理”与全局时钟同步,那么您无法以这种方式实现它(在 Rust 或任何其他语言中)。如果代理是异步的,内部可变性应该可以正常工作。
  • 感谢您的提示。我知道迭代顺序问题。在我的问题中,这没问题,因为我有第二个网格,其中存储了一个时间步骤中的更改以确保同步(我从我的示例中取出了这个以保持代码简短)。但是,如果我可能会问,同步代理的首选方式是什么?
  • 如果你在计算阶段建立一个网格变化列表,然后同步应用它们,你应该能够在第一阶段和应用阶段只使用&amp;引用无论如何你只有一个参考,所以你可以让它&amp;mut,没有内部可变性。但这可能取决于您正在做什么。

标签: rust borrow-checker


【解决方案1】:

有多个独占引用,在这种情况下指向同一个HashMap,确实违反了借用检查器的约束。

鉴于 Agent 显然复制起来并不便宜,我认为您可能需要考虑使用 std::cell::RefCell 包装 Agent 以动态借用值。

这是一个简单的例子:

use std::cell::RefCell;
use std::collections::HashMap;

#[derive(Debug, PartialEq)]
struct Agent {
    id: i64,
    state: i32,
}

struct CellState {
    state: i64,
}

struct Chart {
    agents: HashMap<usize, RefCell<Agent>>,
    grid: Vec<CellState>,
}

impl Chart {
    fn new(size: usize) -> Self {
        let mut agents = HashMap::new();
        agents.insert(1, RefCell::new(Agent { id: 1, state: 0 }));
        agents.insert(2, RefCell::new(Agent { id: 2, state: 1 }));

        let mut grid: Vec<CellState> = Vec::with_capacity(size);

        Self { agents, grid }
    }

    fn do_stuff(&self, agent: &RefCell<Agent>) {
        for other in self.agents.values().filter(|&other| agent != other) {
            if other.borrow().state == 1 {
                agent.borrow_mut().state += 1;
            }
        }
    }

    fn step_agents(&self) {
        for agent in self.agents.values() {
            self.do_stuff(agent);
        }
    }
}

fn main() {
    let mut chart = Chart::new(128);
    chart.step_agents();
    
    for agent in chart.agents.values() {
        println!("{:?}", agent);
    }
}

由于HashMap是按任意顺序访问的,上面的可以然后返回:

RefCell { value: Agent { id: 1, state: 1 } }
RefCell { value: Agent { id: 2, state: 1 } }

【讨论】:

  • 谢谢,这解决了我的问题。我不知道 RefCell 可以做到这一点(认为如果只是将问题从编译器转移到运行时。也感谢我的代码中的小改进(将帮助我更好地编码 Rust)
  • 不客气!我很高兴听到它解决了您的问题@Bernd 祝您度过愉快的一周
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-25
  • 1970-01-01
  • 1970-01-01
  • 2023-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多