Issue when mutably borrowing a struct containing a &mut


#1

When I try to compile this code,

struct Foo<'a> {
    foo: &'a mut str
}

#[derive(Debug)]
struct Bar;

impl<'a> Foo<'a> {
    
    fn get_thing(&'a mut self) -> Bar {
        //Code modifying self.foo...
        Bar
    }
    
    fn get_and_log(&'a mut self) -> Bar {
        let res = self.get_thing();
        format!("logging: {}", self.foo);
        res
    }
}

fn main() {
    let mut s = "foo".to_string();
    let mut foo = Foo { foo: &mut s };
    
    format!("{:?}", foo.get_and_log());
}

I get this error:

error[E0502]: cannot borrow `self.foo` as immutable because `*self` is also borrowed as mutable
  --> src/main.rs:18:32
   |
17 |         let res = self.get_thing();
   |                   ---- mutable borrow occurs here
18 |         format!("logging: {}", self.foo);
   |                                ^^^^^^^^ immutable borrow occurs here
19 |         res
20 |     }
   |     - mutable borrow ends here

If I replace the &'a mut selfs by &mut selfs, the code compiles, but I can’t in the real code as I need to access self.foo in get_thing.

The code seems it should be correct, and I don’t understand why the mutable borrow gets dragged until the end of the function. Is there a way to get this to compile?


#2

Maybe I’m misunderstanding the problem, but I have no trouble modifying self.foo inside get_thing, even if the reference is &mut self instead of &'a mut self:

struct Foo<'a> {
    foo: &'a mut str
}

#[derive(Debug)]
struct Bar;

impl<'a> Foo<'a> {
    
    fn get_thing(&mut self) -> Bar {
        unsafe {
            self.foo.as_bytes_mut()[0] = b'A';
        }
        Bar
    }
    
    fn get_and_log(&mut self) -> Bar {
        let res = self.get_thing();
        format!("logging: {}", self.foo);
        res
    }
}

fn main() {
    let mut s = "foo".to_string();
    let mut foo = Foo { foo: &mut s };
    
    format!("{:?}", foo.get_and_log());
}

Playground link

To wit, by taking &'a mut self, you require from the caller that they pass you a reference to Foo that lives at least as long as the reference Foo contains. But that is not necessary to modify self.foo. The reference to Foo may well live for a shorter time than the reference to str. After all, you don’t intend to keep it around (I assume).


#3

Huh, in my original code I definitely need to take &'a mut self; I must have messed up my reduction.
I’ll try reducing it again.


#4

Okay, the issue was, in fact, be coming from elsewhere.
My Foo struct contains a struct defined as:

struct StackFrame<'a> {
    //fields...
    parent: Option<&'a mut StackFrame<'a>>
}

I thought that this would allow me to do things like:

let mut s1 = StackFrame { parent: None };
let mut s2 = StackFrame { parent: Some(&mut s1) };
let mut s3 = StackFrame { parent: Some(&mut s2) };

But this doesn’t work; instead I have to declare:

struct StackFrame<'a, 'b: 'a> {
    //fields...
    parent: Option<&'a mut StackFrame<'b, 'b>>
}

If I propagate the second lifetime to the Foo struct (like this: struct Foo<'a, 'b: 'a> {...}), I can remove the &'a mut selfs, and everything works correctly.


I suppose the self-referential lifetime in StackFrame was somehow “pinning it down” and making it invariant, and so forcing every borrow to have the same lifetime.

Anyway, thanks for the help :slight_smile:


#5

You’re seen to be trying to use references as if they were pointers, but that generally doesn’t work. It’s more useful to think of mut references as temporary exclusive write locks on data.

You probably want to build trees from Box<StackFrame>, which is also a pointer, but is generally usable without crippling restrictions.

You can also use RefCell and use immutable references everywhere, and rely on RefCell to “cheat” mutability where needed.


#6

That’s exactly the way I use them!
When you construct a StackFrame, you borrow its parent, so that you are the only one who can modify it. As soon as you drop the frame, you can access its parent again.

My code wasn’t compiling because I got the lifetimes wrong: the parent StackFrame must live longer than the current one, not exactly as long. When I fixed this, all the other lifetime annotations which shouldn’t have been there simply disappeared, and now everything’s working great!


#7

Ah, OK. Sorry I haven’t noticed this.


#8

Specifically because the reference is a mutable one - it makes types invariant. &'a mut StackFrame<'a> will not allow the StackFrame to outlive the 'a lifetime; in other words, you cannot substitute a longer lived StackFrame. If that was allowed, you could smuggle a shorter lived reference (ie 'a) into the longer lived StackFrame, leaving t with a dangling reference.