2

From the duplicates, a truly excellent set of patterns for solving this problem was documented by @Shepmaster in this answer. Thank you for that


Account need to access fields of Bank when calling a method. Bank has deposit() that calls set() on an account, but set() needs to know something about the bank, so bank has to be passed into the Account::set. I'm sure there are alternative ways to approach this that makes more sense in Rust. I struggle to find good alternate patterns to things I would do in other languages.

This is a minimal example that came from a Twitch stream where I tried to make a simple epidemiological model in Rust - I'll feature the answer in the next one if I understand it .

fn main() {
    // create a bank and fill with accounts:
    let mut bank = Bank { accounts: vec![] };
    for i in 0..100 {
        bank.accounts.push(Account {
            id: i,
            balance: 0,
            mean_deviation: 0,
        });
    }

    // set the balance of an account
    bank.deposit(42, 10000);
}

// BANK:
struct Bank {
    accounts: Vec<Account>,
}

impl Bank {
    pub fn deposit(&mut self, id: usize, balance: i32) {
        let account = self.accounts.get_mut(id).unwrap();

        // this fails, because account needs access to information from the bank struct,
        // and we cannot borrow bank as mutable twice, or immutable when already mutable:
        account.set(balance, self);
    }
}

// ACCOUNT:
struct Account {
    id: i32,
    balance: i32,
    mean_deviation: i32,
}

impl Account {
    pub fn set(&mut self, balance: i32, bank: &Bank) {
        self.balance = balance;

        /* use bank to calculate how far from the mean account value this account is */
    }
}
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
  --> src/main.rs:27:30
   |
23 |         let account = self.accounts.get_mut(id).unwrap();
   |                       ------------- mutable borrow occurs here
...
27 |         account.set(balance, self);
   |                 ---          ^^^^ immutable borrow occurs here
   |                 |
   |                 mutable borrow later used by call
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
knut
  • 689
  • 5
  • 13

1 Answers1

1

Honestly, the best way is to avoid it if at all possible. the circular references are not good at all.

Here is another way to think about it: By using a Cell that wraps the account balance then you could read/write the new values as you want.

use std::cell::Cell;
fn main() {
    // create a bank and fill with accounts:
    let mut bank = Bank { accounts: vec![] };
    for i in 0..100 {
        bank.accounts.push(Account {
            id: i,
            balance: Cell::new(0),
            mean_deviation: Cell::new(0),
        });
    }

    // set the balance of an account
    bank.deposit(42, 10000);
}

// BANK:
struct Bank {
    accounts: Vec<Account>,
}

impl Bank {
    pub fn deposit(&self, id: usize, balance: i32) {
        let account = self.accounts.get(id).unwrap();
        account.set(balance, &self);
    }
}

// ACCOUNT:
struct Account {
    id: i32,
    balance: Cell<i32>,
    mean_deviation: Cell<i32>,
}

impl Account {
    pub fn set(&self, balance: i32, bank: &Bank) {
        let old_balance = self.balance.get();
        self.balance.set(old_balance + balance);
        /* use bank to calculate how far from the mean account value this account is */
        let _ = bank.accounts.len();
    }
}

if for some reason you are willing to use this in multithreading, you should use atomic types for simple primitives.

Shady Khalifa
  • 177
  • 4
  • 7
  • Thank you for you reply (: So to work with the language's restrictions, it makes more sense to e.g. move all the logic out of Account and into Bank, or perhaps a third object that manages the logic? – knut Jun 30 '20 at 12:35
  • 1
    I guess it is not a language restriction, because there is no GC that would manage the references, also you could use `Rc` or `Arc` with `RwLock` but that is a lot of overhead that could be avoided by using a Cell instead (for your case). – Shady Khalifa Jun 30 '20 at 12:49