1

Task

A simple data-structure that gets and sets a value of a field in a struct.

Issue

The following get_or_set function errors with

cannot borrow *self as mutable because it is also borrowed as immutable

Code

struct MyValue<A> {
    value: Option<A>,
}

impl<A> MyValue<A> {
    fn get(&self) -> &Option<A> {
        &self.value
    }

    fn set<F: FnOnce() -> A>(&mut self, setter: F) -> &Option<A> {
        self.value = Some(setter());
        self.get()
    }

    //this function is causing borrow error
    fn get_or_set<F: FnOnce() -> A>(&mut self, setter: F) -> &Option<A> {
        match self.get() {
            None => self.set(setter),
            some @ Some(_) => some
        }
    }
}

Why does the error occur?

Following the error the issue is that I am call self.set a second time before the first self.get goes out of scope and multiple borrows are not allowed.

Fix:

Use if & else instead. Here I'm calling self.get twice which is not nice.

fn get_or_set<F: FnOnce() -> A>(&mut self, setter: F) -> &Option<A> {
    if self.get().is_some() {
        self.get()
    } else {
        self.set(setter)
    }
}

Question

How do I get the borrow check to allow the match statement to pass as well? My attempt at setting lifetimes has not succeeded.

fn get_or_set<'a, F: FnOnce() -> A + 'a>(&'a mut self, setter: F) -> &'a Option<A>
user794783
  • 3,619
  • 7
  • 36
  • 58
  • 1
    because match is one expression. – Stargateur May 02 '21 at 13:17
  • 1
    Does this answer your question? [Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in?](https://stackoverflow.com/questions/38023871/returning-a-reference-from-a-hashmap-or-vec-causes-a-borrow-to-last-beyond-the-s) – Stargateur May 02 '21 at 13:31
  • 1
    (quite similar but I'm unsure it's a correct answer for this question, can you test with `-Zpolonius` ?) – Stargateur May 02 '21 at 13:32
  • @Stargateur just tested and -Zpolonius flag on nightly and it fixes this issue. I would suggest leaving this question open since they are two different problems with probably a similar solution. – user794783 May 03 '21 at 00:27

1 Answers1

1

You can write your function like this:

fn get_or_set1<F: FnOnce() -> A>(&mut self, setter: F) -> &Option<A> {
    match *self.get() {
        None => self.set(setter),
        _ => &self.value,
    }
}
lpiepiora
  • 13,659
  • 1
  • 35
  • 47
  • That worked! Thank you. But it's so weird. I don't get the reason behind this. The error said I borrowed the `self` twice and points to `None`'s body but calling `self` the third time with `_ => &self.value` fixes it :S – user794783 May 02 '21 at 12:31
  • 1
    `match self.get()` creates a temp variable which references `&Option` returned from `get` as a shared reference, then when you call `self.set(setter)` you try to borrow it again, mutably this time. When you deref it then the reference is not held anymore. In your `if`, you don't hold reference because you call `is_some` method and get `bool` result. Check the second paragraph here: https://doc.rust-lang.org/reference/expressions/match-expr.html – lpiepiora May 02 '21 at 13:31
  • 1
    The question states that calling `self.get()` twice is "not nice", but this answer does essentially the same thing, except it inlines the second call by accessing `self.value` directly (thus breaking encapsulation, if it was intended). – user4815162342 May 02 '21 at 19:46
  • @user4815162342 that's right I didn't notice that. Is `-Zpolonius` flag the only solution? – user794783 May 03 '21 at 00:25
  • 1
    @captain-inquisitive I wouldn't count `-Zpolonius` as a solution, since it's not available on stable. That the code works with that flag indicates that the problem is not with your code but with the current borrow checker, and that the issue is likely to go away in some future release of Rust. But Polonius is still in development and there is no public schedule for when it will be ready for general use. Looking at other similar questions, it looks like calling `get()` twice is the only solution without unsafe. If you're interested with solutions with unsafe, one can probably be written too. – user4815162342 May 03 '21 at 06:25