Mutable borrows

How can I avoid these situations? Mock up of a real scenario.

struct A {}

impl A
{
    fn f(&mut self) -> ()
    {
          self.g();
          // problem here second mutable borrow
          self.h();
    }

    fn g(&mut self) -> ()
    {}

    fn h(&mut self) -> ()
    {
    }
}

Each of these function can be invoked in isolation too. How can this be avoided or how do I work around this situation?

There's no problem with the code you have posted here. It compiles perfectly fine.

2 Likes

Given the code structure you have posted, the actual problem probably involves misuse of lifetime annotations, or returning a mutable borrow and keeping it around too long. We will need to see a compilable example that demonstrates the error to say more.

2 Likes

Sorry folks. I have the error in front of me but I guess I got ahead of myself in simplifying the problem. I apologize. Let me see what I can do to articulate better.

@kpreid you are a genius. Here is the version that I should have sent:

use std::collections::HashMap;

struct A 
{
    map : HashMap<String, String>
}

impl A
{
    fn new() -> Self
    {
        return A {
            map : HashMap::new()
        }    
    }
    
    fn f(&mut self) -> Option<&String>
    {
        let val = self.g("foo");
        // problem here second mutable borrow
        self.h();
        return val;
    }

    fn g(&mut self, key : &str) -> Option<&String>
    {
        self.map.insert(key.into(), "bar".into());
        return self.map.get(key.into());
    }

    fn h(&mut self) -> ()
    {
    }
}

The problem is with this pattern:

fn g(&mut self, key : &str) -> Option<&String>

If we write out the inferred lifetimes, this is actually

fn g<'s, 'k>(&'s mut self, key: &'k str) -> Option<&'s String>

which means that the &mut self and the &String share the same lifetime, and thus the mutable borrow required to call g extends until you drop the reference it returns. In f, you are trying to call h while val is still held. This is a limitation of Rust's lifetime model, but not one that's easy to improve on. In order to avoid it, you should in most cases not write a function that takes &mut self and then returns an immutable borrow of self.

However, even doing that will not help here, because even if val in f counted as a separate immutable borrow, you are still trying to hold it while calling self.h(), which would be a simultaneous immutable (shared) and mutable (unique) borrow. This is always prohibited.

The best general advice I can give you is to not try to use the "insert and then return the inserted" style of methods so much; it is often very restrictive due to lifetime constraints. In specific situations, there might be better specific patterns.

The reason I had the insert() and get() is I wanted a reference to the value in the map. I cannot create a value insert() it and then return a local reference for the right reasons. If I get rid of g() and make f() look like this:

fn f(&mut self) -> Option<&String>
{
        self.map.insert("foo".into(), "bar".into());
        // problem here second mutable borrow
        self.h();
        return self.map.get("foo".into());
}

Everything compiles fine.

You might want to look into the Entry API.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.