4

I'm trying to make this work

use std::collections::HashMap;

struct Test1 {
    total: u32,
    hash: HashMap<u32, u32>,
}

impl Test1 {
    fn new() -> Test1 {
        Test1 {
            total: 0,
            hash: HashMap::new(),
        }
    }

    fn add(&mut self) -> u32 {
        self.total += 1;
        self.total
    }

    fn get_or_create(&mut self, id: u32) -> u32 {
        match self.hash.get(&id) {
            Some(value) => *value,
            None => {
                let value = self.add();
                self.hash.insert(id, value);
                value
            }
        }
    }
}

fn main() {
    let mut test = Test1::new();
    println!("{:?}", test.get_or_create(1));
    println!("{:?}", test.get_or_create(1));
}

(playpen)[http://is.gd/hDLEaL]

but I get

<anon>:25:33: 25:37 error: cannot borrow `*self` as mutable because `self.hash` is also borrowed as immutable [E0502]

removing pattern matching doesn't address the problem, but I don't understand why.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
TlmaK0
  • 3,578
  • 2
  • 31
  • 51
  • 2
    Try to change `match self.hash.get(&id) {` into `match self.hash.get(&id).cloned() {` and `*value` to `value` – qthree Mar 20 '16 at 19:49
  • Thanks, it has a lot of sense. It works!! – TlmaK0 Mar 20 '16 at 19:52
  • 1
    That's because `get` returns `Option<&u32>` but you actually don't need that borrowed reference but only value. So `cloned()` transforms `Option<&T>` into `Option` and releases borrow. – qthree Mar 20 '16 at 20:01
  • 3
    There is a better idiom for what you are trying to do, `.entry().or_insert()`: https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html#method.entry – starblue Mar 20 '16 at 21:34
  • 1
    [**Highly** relevant](http://stackoverflow.com/q/28512394/155423). – Shepmaster Mar 20 '16 at 23:47

1 Answers1

9

Update

With the addition of Non-Lexical Lifetimes in Rust, this problem should no longer be an issue if you're using 1.31.0 with Rust 2018 edition and 1.36.0 with Rust 2015 edition.

Original Answer

This is a problem with the current state of Rust, where borrows are always lexical. That is, they last the entire {} or block scope. In a match expression, the borrow performed on self continues into the Some and None blocks. The simplest way to solve this problem is to use an if let statement. It provides pattern matching, and allows you to use self in both blocks.

Code

use std::collections::HashMap;

struct Test1 {
    total: u32,
    hash: HashMap<u32, u32>,
}

impl Test1 {
    fn new() -> Test1 {
        Test1 {
            total: 0,
            hash: HashMap::new(),
        }
    }

    fn add(&mut self) -> u32 {
        self.total += 1;
        self.total
    }

    fn get_or_create(&mut self, id: u32) -> u32 {
        if let Some(&value) = self.hash.get(&id) {
            value
        } else {
            let value = self.add();
            self.hash.insert(id, value);
            value
        }
    }
}

fn main() {
    let mut test = Test1::new();
    println!("{:?}", test.get_or_create(1));
    println!("{:?}", test.get_or_create(1));
}
XAMPPRocky
  • 3,160
  • 5
  • 25
  • 45