Modifying and returning reference in match statement

Below is a simplified version of a bit of code I'm struggling with.

enum Bar {
    NotReady,
    Ready(u64),
}

struct Foo {
    bar: Bar,
}

impl Foo {
    fn baz(&mut self) -> Option<&u64> {
        match &mut self.bar {
            Bar::NotReady => {
                self.bar = Bar::Ready(123);
                None
            }
            Bar::Ready(val) => Some(val),
        }
    }
}

The compiler error I get is:

105 |     fn baz(&mut self) -> Option<&u64> {
    |            - let's call the lifetime of this reference `'1`
106 |         match &mut self.bar {
    |               ------------- `self.bar` is borrowed here
107 |             Bar::NotReady => {
108 |                 self.bar = Bar::Ready(123);
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^ `self.bar` is assigned to here but it was already borrowed
...
111 |             Bar::Ready(val) => Some(val),
    |                                --------- returning this value requires that `self.bar` is borrowed for `'1`

I don't completely understand this error message. Can someone please explain it and offer an idiomatic way of accomplishing something like this?

Don't borrow self.bar when matching:

impl Foo {
    fn baz(&mut self) -> Option<u64> {
        match self.bar {
            Bar::NotReady => {
                self.bar = Bar::Ready(123);
                None
            }
            Bar::Ready(val) => Some(val),
        }
    }
}

Thanks for the reply but this doesn't quite work for me.

You have made one other change, you are no longer return a reference to u64. In my case, I have to return a reference as the object stored there cannot be cloned / copied.

This is the problem with examples that don't reproduce the issue you're facing. I went with what you had showed.

Do it like this then. Note the use of the ref keyword:

enum Bar {
    NotReady,
    Ready(Uncopyable),
}

struct Foo {
    bar: Bar,
}

struct Uncopyable {
    value: String
}

impl Foo {
    fn baz(&mut self) -> Option<&Uncopyable> {
        match self.bar {
            Bar::NotReady => {
                self.bar = Bar::Ready(Uncopyable { value: "now".to_owned() });
                None
            }
            Bar::Ready(ref val) => Some(val),
        }
    }
}
3 Likes

Sorry about being unclear in my initial question. I'll be more careful next time.

Thank you very much for your answer. This works perfectly for my situation.

Do you have an explanation or a link to some documentation that can explain what is happening here please?

Here's an explanation of how the current borrow checker doesn't have enough flow sensitivity to make the borrow duration of the &mut self.bar be either short or long based on which match branch will be taken. The next borrow checker should handle the original code.

In contrast, @firebits.io's latest solution defers creating a borrow until a particular branch is decided, which the current borrow checker can handle.

3 Likes

Here's a link to the documentation about bindings in match expressions, and here's the link to the documentation for the ref keyword.

My thought process is just to read what the compiler is saying and adjust the code accordingly, one step at a time:

  1. Using your original version, the compiler error showed that self.bar was borrowed, and Rust's borrow checker rule known as "aliasing XOR mutability" prevents us from doing what you had attempted. Thus I started changing your code by removing that borrow.
  2. If in my final code you remove the ref keyword and try to return Option<Uncopyable> instead, you'll notice that the compiler will complain about the value being moved (due to how bindings work in match patterns, as mentioned in the link I mentioned before). Thus I added the ref keyword to the pattern in order to only borrow the value instead of moving it.
3 Likes

thank you both very much!

1 Like

FWIW, you can also use the binding operator:

#[derive(Debug, Eq, PartialEq)]
struct NoCopy;

enum Bar {
    NotReady,
    Ready(NoCopy),
}

struct Foo {
    bar: Bar,
}

impl Foo {
    fn baz(&mut self) -> Option<&NoCopy> {
        match &mut self.bar {
            bar @ Bar::NotReady => {
                *bar = Bar::Ready(NoCopy);
                None
            }
            Bar::Ready(val) => Some(val),
        }
    }
}
3 Likes

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.