E0499 error: how to use a method with &mut self?


#1

Hi all,

I try to make this work:

use std::collections::HashMap;

struct Field {
    name: String
}

struct Record<'a> {
    name: String,
    flist: Vec<Field>,
    fmap: HashMap<String, Vec<&'a Field>>,
}

impl<'a> Record<'a> {
    // ctor
    pub fn new(name: String) -> Record<'a> {
        // initialize all relevant members
        Record {
            name: name, 
            flist: Vec::new(),
            fmap: HashMap::new(),
        }        
    }
    
    // add a Field object
    pub fn push(&'a mut self, field: Field) {
        // add FIeld to tour list
        self.flist.push(field); 
        
        // copy current length
        let length = self.flist.len();
        
        // get a mutref on the last pushed element
        let ref mut f = self.flist[length-1];
        let fname = f.name.clone();

        if self.fmap.contains_key(&fname) {
            self.fmap.get_mut(&fname).unwrap().push(f);
        } else {
            self.fmap.insert(fname, Vec::new());
        }

    }    
}


fn main() {
    let mut rec = Record::new("REC1".to_string());
    
    let f1 = Field { name: "F1".to_string() };
    let f2 = Field { name: "F2".to_string() };    
    
    rec.push(f1);
    rec.push(f2);
            
}

but got E0499 error:

error[E0499]: cannot borrow `rec` as mutable more than once at a time
  --> <anon>:53:5
   |
52 |     rec.push(f1);
   |     --- first mutable borrow occurs here
53 |     rec.push(f2);
   |     ^^^ second mutable borrow occurs here
54 |     
55 | }
   | - first borrow ends here

So how to write a method which is adding others structs in another struct?

It works fine with the std::Vec because push is fn push(&mut self, value: T). I understand rec is a mutable borrow with &mut self, but can’t get out of it.

Any hint ?

Thanks a lot.


#2

Change “&'a mut self” in push to just “& mut self” - otherwise you’re borrowing mutably for longer than needed.


#3

Thank vitalyd

I already tried but got another error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> <anon>:33:25
   |
33 |         let ref mut f = self.flist[length-1];
   |                         ^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

#4

It’s seems that you’re trying to do self-referential struct here. (The hashmap storing references to elements in the flist vector. Am I right?) If you really need both the flist and fmap to coexist, then the simplest solution would be to store Field in the hashmap directly:

fmap: HashMap<String, Vec<Field>>

You can achieve this either by cloning the String (and use slightly more additional memory), or wrapping the name field of Field inside an Rc:

name: Rc<String>

In this case, you’d also clone the field, but the clone would be cheap (just an refcount bump).

The more complex, but fastest, solution would be to make both flist and fmap use &Field and store the actual Fields in the TypedArena (there’s a crate for that) outside the Record.


#5

@krdln,

I wanted to keep a reference on Field to have a single access on the Field values.

I solved the problem by storing indexes of the Field objects in the flist vec, and not the
reference on those. It simplifies the design, but doesn’t explain the errors.


#6

You can’t store references to self (or anything owned by it), as @krdln mentioned. A simple example of what could go wrong is imagine you add more values to the vec, and the vec reallocates internal storage (memory) - the references you’re holding are now invalid.

The error message you were getting is a long-winded way of saying the same thing - you were trying to push f onto a vec that expects &'a Field, which is something that outlives Record itself. But, you were trying to give it a reference that’s tied to self (essentially), and it rejects that.


#7

@krdln

Thanks, your explanation sounds the way it should work :wink: