2

I struggle with iterator which also mutates other fields of it's owner.

I've re-created a simplified example Playground:

#[derive(PartialEq)]
enum ResourceEnum {
    Food,
}

struct Resource {
    r#type: ResourceEnum,
    amount: u32,
}

trait Building {
    fn produce(&self) -> Option<Resource>;
}

struct Farm {}

struct City {
    buildings: Vec<Box<dyn Building>>,
    resources: Vec<Resource>,
}

impl City {
    fn add_resource(&mut self, received: Option<Resource>) {
        if let Some(received) = received {
            if let Some(mut res) = self
                .resources
                .iter_mut()
                .find(|r| r.r#type == received.r#type)
            {
                res.amount += received.amount;
            } else {
                self.resources.push(received);
            }
        }
    }
}

impl Building for Farm {
    fn produce(&self) -> Option<Resource> {
        Some(Resource {
            r#type: ResourceEnum::Food,
            amount: 10,
        })
    }
}

fn main() {
    let mut city = City {
        buildings: vec![],
        resources: vec![],
    };

    city.buildings.iter().for_each(|f| {
        city.add_resource(f.produce());
    });
}

Error:

error[E0502]: cannot borrow `city` as mutable because it is also borrowed as immutable
  --> src/main.rs:55:36
   |
53 |     city.buildings.iter().for_each(|f| {
   |     --------------        -------- ^^^ mutable borrow occurs here
   |     |                     |
   |     |                     immutable borrow later used by call
   |     immutable borrow occurs here
54 |         city.add_resource(f.produce());
   |         ---- second borrow occurs due to use of `city` in closure

What I'm trying to achieve is to have a single struct holding my 'world' -> City, which holds buildings like farms and all it's available resources like food.

At each state update I want to compute harvest from all farms and so on... and add produced resources into City storage, but can't figure out a way without storing all production as a separate Vector and iterating over it once again to just add it into City storage which seems redundant.

I guess I struggle to find a better way to design a structure of my world, but can't think of anything.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 1
    TL;DR: you can't do that, "I guess I struggle to find a better way to design a structure of my world, but can't think of anything." Does this answer your question? [Why does refactoring by extracting a method trigger a borrow checker error?](https://stackoverflow.com/questions/57017747/why-does-refactoring-by-extracting-a-method-trigger-a-borrow-checker-error) to give you a more precise answer you need to give a more precise use case your [mcve] is incomplete what is suppose to to `add_ressource()` ?. – Stargateur Dec 08 '19 at 14:58
  • I've filled up the add_resource: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b7f4f80426c83496fe290cd248418224 It adds produced resources into a storage that holds each of a ResourceEnum type with associated amount. – David Horáček Dec 08 '19 at 16:08
  • 1
    I advice you to never use `for_each()` side effect have no place in iterator. [Here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=08b177db40fb434eb3d8739addb10f49) the duplicate solution for your code. By the way you should use a hashmap for your resources or even put them directly. – Stargateur Dec 08 '19 at 16:47
  • Thats way better... and good tip for a HashMap, thank you a lot, good bye. – David Horáček Dec 08 '19 at 17:46

1 Answers1

1

What can of course work is to separate the production of resources and adding them to the city. I've modified your Playground to get it to compile:

let mut v:Vec<Option<ResourceEnum>> = city.buildings.iter().map(|f| f.produce()).collect();
v.drain(..).for_each(|r| city.add_resource(r));

But certainly you cannot call a mutable function on City while iterating on the buildings inside the same object.

JP Moresmau
  • 7,388
  • 17
  • 31
  • Ah, seems unavoidable to collect it first. :/ Thanks, I wasn't sure if I just missed something or I'm really trying to do impossible. – David Horáček Dec 08 '19 at 16:11