Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does calling a method on a mutable reference involve "borrowing"?

Tags:

rust

I'm learning Rust and I'm trying to cargo-cult this code into compiling:

use std::vec::Vec;
use std::collections::BTreeMap;

struct Occ {
    docnum: u64,
    weight: f32,
}

struct PostWriter<'a> {
    bytes: Vec<u8>,
    occurrences: BTreeMap<&'a [u8], Vec<Occ>>,
}

impl<'a> PostWriter<'a> {
    fn new() -> PostWriter<'a> {
        PostWriter {
            bytes: Vec::new(),
            occurrences: BTreeMap::new(),
        }
    }

    fn add_occurrence(&'a mut self, term: &[u8], occ: Occ) {
        let occurrences = &mut self.occurrences;
        match occurrences.get_mut(term) {
            Some(x) => x.push(occ),
            None => {
                // Add the term bytes to the big vector of all terms
                let termstart = self.bytes.len();
                self.bytes.extend(term);
                // Create a new occurrences vector
                let occs = vec![occ];
                // Take the appended term as a slice to use as a key
                // ERROR: cannot borrow `*occurrences` as mutable more than once at a time
                occurrences.insert(&self.bytes[termstart..], occs);
            }
        }
    }
}

fn main() {}

I get an error:

error[E0499]: cannot borrow `*occurrences` as mutable more than once at a time
  --> src/main.rs:34:17
   |
24 |         match occurrences.get_mut(term) {
   |               ----------- first mutable borrow occurs here
...
34 |                 occurrences.insert(&self.bytes[termstart..], occs);
   |                 ^^^^^^^^^^^ second mutable borrow occurs here
35 |             }
36 |         }
   |         - first borrow ends here

I don't understand... I'm just calling a method on a mutable reference, why would that line involve borrowing?

like image 625
Matt Chaput Avatar asked Dec 26 '22 00:12

Matt Chaput


1 Answers

I'm just calling a method on a mutable reference, why would that line involve borrowing?

When you call a method on an object that's going to mutate the object, you can't have any other references to that object outstanding. If you did, your mutation could invalidate those references and leave your program in an inconsistent state. For example, say that you had gotten a value out of your hashmap and then added a new value. Adding the new value hits a magic limit and forces memory to be reallocated, your value now points off to nowhere! When you use that value... bang goes the program!

In this case, it looks like you want to do the relatively common "append or insert if missing" operation. You will want to use entry for that:

use std::collections::BTreeMap;

fn main() {
    let mut map = BTreeMap::new();

    {
        let nicknames = map.entry("joe").or_insert(Vec::new());
        nicknames.push("shmoe");

        // Using scoping to indicate that we are done with borrowing `nicknames`
        // If we didn't, then we couldn't borrow map as
        // immutable because we could still change it via `nicknames`
    }

    println!("{:?}", map)
}
like image 147
Shepmaster Avatar answered Dec 31 '22 01:12

Shepmaster