如何将可变引用传递给 Rust 中迭代器循环内的函数?

问题描述

我有以下代码

    pub fn run_systems(mut self) {
        for mut agent in self.agents.iter_mut() {
            for mut system_id in agent.systems.iter_mut() {
                let system = self.systems.get(system_id).unwrap();
                system.simulate(&mut agent,&mut self);
            }
        }
    }

我在 system.simulate 行收到以下错误

cannot borrow `agent` as mutable more than once at a time
second mutable borrow occurs here`. Apparently,iterating over the arrays is a borrowing operation.

我也尝试过不使用 iter 函数并直接对拥有的值进行迭代(不确定那是做什么的):

    pub fn run_systems(mut self) {
        for mut agent in self.agents {
            for mut system_id in agent.systems {
                let system = self.systems.get(&system_id).unwrap();
                system.simulate(&mut agent,&mut self);
            }
        }
    }

但是一旦我引用 &system_id,它就会检测到 for mut system_id in agent.systems { 行中的隐式借用

borrow of partially moved value: `agent`
partial move occurs because `agent.systems` has type `Vec<String>`,which does not implement the `copy` traitrustcE0382
lib.rs(72,34): `agent.systems` partially moved due to this implicit call to `.into_iter()`
collect.rs(233,18): this function takes ownership of the receiver `self`,which moves `agent.systems`

我尝试了各种方法来编写此代码,但找不到有效的方法。如何迭代这些值,同时还能够将对其内容的可变引用传递给其他函数

这是一个playground of it

use std::collections::HashMap;

struct World {
    agents: Vec<Agent>,systems: HashMap<String,Box<dyn System>>,}
impl World {
    pub fn new() -> World {
        return World {
            agents: Vec::new(),systems: HashMap::new(),};
    }
    pub fn run_systems(mut self) {
        for mut agent in self.agents {
            for system_id in agent.systems {
                let system = self.systems.get(&system_id).unwrap();
                system.simulate(&mut agent,&mut self);
            }
        }
    }
    /// Adds an agent to the world
    pub fn add_agent(&mut self,agent: Agent) -> &Self {
        self.agents.push(agent);
        return self;
    }

    /// Adds a system to the available systems
    pub fn add_system<S: System + 'static>(&mut self,system: S,id: String) -> &Self {
        self.systems.insert(id,Box::new(system));
        return self;
    }
}

struct Agent {
    systems: Vec<String>,}

trait System {
    fn simulate(&self,agent: &mut Agent,world: &mut World);
}

#[derive(Default)]
struct SomeSystem;
impl System for SomeSystem {
    fn simulate(&self,world: &mut World) {
        // Code here
    }
}

fn main() {
    let system_id = String::from("SOME_SYstem");
    let world = World::new();
    world.add_system(SomeSystem::default(),system_id);
    let agent = Agent {
        systems: vec![system_id],};
    world.add_agent(agent);
    world.run_systems();
}
error[E0382]: borrow of partially moved value: `agent`
   --> src/main.rs:18:33
    |
16  |             for system_id in agent.systems {
    |                              -------------
    |                              |
    |                              `agent.systems` partially moved due to this implicit call to `.into_iter()`
    |                              help: consider borrowing to avoid moving into the for loop: `&agent.systems`
17  |                 let system = self.systems.get(&system_id).unwrap();
18  |                 system.simulate(&mut agent,&mut self);
    |                                 ^^^^^^^^^^ value borrowed here after partial move
    |
note: this function takes ownership of the receiver `self`,which moves `agent.systems`
    = note: partial move occurs because `agent.systems` has type `Vec<String>`,which does not implement the `copy` trait

error[E0502]: cannot borrow `self` as mutable because it is also borrowed as immutable
  --> src/main.rs:18:45
   |
17 |                 let system = self.systems.get(&system_id).unwrap();
   |                              ------------ immutable borrow occurs here
18 |                 system.simulate(&mut agent,&mut self);
   |                        --------             ^^^^^^^^^ mutable borrow occurs here
   |                        |
   |                        immutable borrow later used by call

error[E0382]: borrow of partially moved value: `self`
  --> src/main.rs:18:45
   |
15 |         for mut agent in self.agents {
   |                          -----------
   |                          |
   |                          `self.agents` partially moved due to this implicit call to `.into_iter()`
   |                          help: consider borrowing to avoid moving into the for loop: `&self.agents`
...
18 |                 system.simulate(&mut agent,&mut self);
   |                                             ^^^^^^^^^ value borrowed here after partial move
   |
   = note: partial move occurs because `self.agents` has type `Vec<Agent>`,which does not implement the `copy` trait

error[E0596]: cannot borrow `world` as mutable,as it is not declared as mutable
  --> src/main.rs:54:5
   |
53 |     let world = World::new();
   |         ----- help: consider changing this to be mutable: `mut world`
54 |     world.add_system(SomeSystem::default(),system_id);
   |     ^^^^^ cannot borrow as mutable

error[E0382]: use of moved value: `system_id`
  --> src/main.rs:56:23
   |
52 |     let system_id = String::from("SOME_SYstem");
   |         --------- move occurs because `system_id` has type `String`,which does not implement the `copy` trait
53 |     let world = World::new();
54 |     world.add_system(SomeSystem::default(),system_id);
   |                                             --------- value moved here
55 |     let agent = Agent {
56 |         systems: vec![system_id],|                       ^^^^^^^^^ value used here after move

error[E0596]: cannot borrow `world` as mutable,as it is not declared as mutable
  --> src/main.rs:58:5
   |
53 |     let world = World::new();
   |         ----- help: consider changing this to be mutable: `mut world`
...
58 |     world.add_agent(agent);
   |     ^^^^^ cannot borrow as mutable

解决方法

我用来解决此类问题的一种方法是让 simulate 不接受可变引用,而是返回它希望执行的操作列表。这可能看起来像这样:


// No longer uses mutable references,and returns a list of 
// actions to perform on the world.
trait System {
    fn simulate(&self,agent: &Agent,world: &World) -> Vec<Action>;
}

enum Action {
   ...
}

impl World {
    ...

    pub fn run_systems(&mut self) {
        let mut actions = vec![];
        for agent in &self.agents {
            for system_id in &agent.systems {
                let system = self.systems.get(system_id).unwrap();
                actions.extend(system.simulate(agent,self));
            }
        }

        for action in actions {
            self.apply_action(action)
        }
    }

    pub fn apply_action(&mut self,action: &Action) {
        match(action) {
           case SomeAction(agent_id) -> {
             ...
        }
    }
}
,

在任何一种情况下,这都不适用于 simulate() 的定义方式。它采用 &mut Self 作为第二个参数,这意味着对 self 的独占访问 - 所有 self。但这是不可能的,因为 A) 在第一种情况下通过迭代器存在对 self 的引用,或者 B) 在第二种情况下通过取得 self 的所有权而部分解构了 self.agents

,

我会用我最终选择的解决方案来回答我自己的问题。

从根本上说,问题中的代码的问题在于,我们试图在对其进行迭代时对其进行变异(World 结构和代理列表)。 Rust 保护我们免受这种情况的影响,因为我们可能会在迭代过程中完全改变数组的大小或条目,这是您在使用其他语言编写代码时必须考虑的常见错误。

解决方案是根本不这样做,而是确保您不会迭代与您正在迭代的相同的东西。不过,我确实需要我的系统来改变代理状态,所以我所做的是将它们分成两个不同的部分:


// Agents have an id
pub struct Agent {
    pub id: u128,pub systems: Vec<String>,}

// New AgentState separate struct
pub trait AgentState {
}

// The worrld contains a hashmap of states
struct World {
    agents: Vec<Agent>,agent_states: HashMap<u128,Box<dyn AgentState>>,systems: HashMap<String,Box<dyn System>>,}

impl World {

    // When adding a new agent,we also setup its state
    pub fn add_agent<A: AgentState + 'static>(&mut self,state: A) -> &Self{
        let agent = Agent::new(Vec::new());
        let id = agent.id;
        self.agents.push(agent);
        self.agent_states.insert(id,Box::new(state));
        self
    }

    // When running systems,we can pass a mutable reference to the states hashmap because we're not iterating on it
    pub fn run_systems(&'static mut self) -> &Self {
        for agent in self.agents.iter_mut() {
            let systems = agent.systems.clone();
            for system_id in systems {
                let system = self.systems.get(&system_id).unwrap();
                system.simulate(agent,&mut self.agent_states);
            }
        }
        self
    }
}

// The System trait signature now changes the simulate function to take the hashmap of states
/// The main trait that all system structs need to implement
pub trait System {
    fn id() -> String where Self: Sized;
    fn dyn_id(&self) -> String;
    /// This function is called for every actor that uses the system and gives user code the opportunity to change the state
    fn simulate<'a>(&self,agent: &'a mut Agent,states: &'a mut HashMap<u128,Box<dyn AgentState>>);
}

通过这种设置,当创建代理时,它们的状态是单独存储的,并且可以独立于代理迭代而改变。

对此有一个很大的警告,即您不能在系统内添加或删除代理状态,因为事情会变得不同步(代理将仍然存在,而其状态不会t)。对于此类更高级的用例,需要采用不同的方法。

只要不需要动态添加/删除代理,此解决方案就有效。