Matteo Monti
Matteo Monti

Reputation: 8970

Counter-intuitive double-borrow

I have been trying to implement my own Option::get_or_insert_with, with a twist: the function that produces the value to insert in the Option is fallible. This was my first, most reasonable-looking playground attempt:

fn fallible_get_or_insert_with(option: &mut Option<String>) -> Result<&mut String, &'static str> {
    if let Some(inner) = option.as_mut() {
        return Ok(inner);
    };

    let inner = if rand::random() {
        "Brand new inner value!".to_string()
    } else {
        return Err("Got unlucky");
    };

    Ok(option.insert(inner))
}

Disappointingly, I'm getting

error[E0499]: cannot borrow `*option` as mutable more than once at a time
  --> src/main.rs:12:8
   |
1  | fn fallible_get_or_insert_with(option: &mut Option<String>) -> Result<&mut String, &'static str> {
   |                                        - let's call the lifetime of this reference `'1`
2  |     if let Some(inner) = option.as_mut() {
   |                          ------ first mutable borrow occurs here
3  |         return Ok(inner);
   |                --------- returning this value requires that `*option` is borrowed for `'1`
...
12 |     Ok(option.insert(inner))
   |        ^^^^^^ second mutable borrow occurs here

For more information about this error, try `rustc --explain E0499`.

Despite my best efforts, I can't seem to understand why I'm double-borrowing. In my head, if the fallible_get_or_insert does not return at line 3, no reference to option is held anymore. In my head, I got option.as_mut(), looked at it, determined that it is not Some, then dropped it. Why am I not free to mutably borrow option again, with the insert at line 12?

I tried variations over variations of this function but I can't seem to convince the borrow checker, so I am thinking there is something inherent to this problem that I am missing altogether. Of course, I know I can settle for something like

if option.is_none() {
    // Fallibly fill `option`
}

option.as_mut().unwrap()

but I don't understand why I should be forced to do that. The unwrap() can't fail, so why do I need to go through its unsightly use instead of using more canonical Option-manipulation primitives?

Update

In an attempt to understand this better, I went and looked at the code of Option::get_or_insert. To my dismay, even they use .as_mut().unwrap_unchecked(). So I am thinking there is no way around this. But if I could understand better why I would at least find some peace!

Upvotes: 1

Views: 31

Answers (0)

Related Questions