Mutating a borrowed value but it was never returned

#1

Hey friends,

I’ve put together a playground example of a lifetime error that’s confusing me.

I’m borrowing a value and returning it to the caller at the top of a function.
Bellow that (if it falls through without returning the borrowed value), I’m attempting to mutate the the value.

The compiler is telling me that I’m trying to mutate and borrowed value. Seems to me like the logic is correct though. Is this a “known flaw” in the compiler or am I actually doing something sketchy? Any suggestions on how I might get around an error like this? I thought something something non lexical lifetimes were supposed to have helped with this kind of thing?

The following error and code example is an attempted simplification of my actual code.

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/main.rs:18:19
   |
10 |     fn get_or_mut(&mut self, key: usize) -> Receiver<&String> {
   |                   - let's call the lifetime of this reference `'1`
...
13 |         if let Some(val) = self.lookup(key) {
   |                            ---- immutable borrow occurs here
14 |             sender.send(val).unwrap();
15 |             return receiver
   |                    -------- returning this value requires that `*self` is borrowed for `'1`
...
18 |         let def = self.mutate_and_get_default();
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;

struct Store {
    lookup: Vec<String>,
    def: String,
}

impl Store {
    fn get_or_mut(&mut self, key: usize) -> Receiver<&String> {
        let (sender, receiver) = channel();
        
        if let Some(val) = self.lookup(key) {
            sender.send(val).unwrap();
            return receiver
        }
        
        let def = self.mutate_and_get_default();
        sender.send(def).unwrap();
        return receiver;
    }
    
    fn mutate_and_get_default(&mut self) -> &String {
        &self.def
    }
    
    fn lookup(&self, key: usize) -> Option<&String> {
        if key < self.lookup.len() {
            Some(&self.lookup[key])
        } else {
            None
        }
    }
}

fn main() {
    let mut store = Store{
        lookup: vec!["one".to_string(), "two".to_string(), "three".to_string()],
        def: "default".to_string(),
    };
    println!("{:?}", store.get_or_mut(0));
    println!("{:?}", store.get_or_mut(1));
    println!("{:?}", store.get_or_mut(2));
    println!("{:?}", store.get_or_mut(3));
}

(Playground)

0 Likes

#2

This is the key – NLL is still limited regarding conditional returns. Polonius is the next feature working on that, described here:

http://smallcultfollowing.com/babysteps/blog/2018/06/15/mir-based-borrow-check-nll-status-update/#polonius

3 Likes

#3

See also this question on StackOverflow and the duplicate linked from there.

0 Likes