Trouble with closure that returns mutable reference

This code works:

fn call_closure<'a, F>(f: F) -> &'a i32
where
    F: Fn() -> &'a i32,
{
    f()
}

#[test]
fn test_closure() {
    let val = 17;
    let f = || &val;
    assert_eq!(*call_closure(f), 17);
}

but when I make the reference mutable, it doesn't compile:

fn call_closure_mut<'a, F>(mut f: F) -> &'a mut i32
where
    F: FnMut() -> &'a mut i32,
{
    f()
}

#[test]
fn test_closure_mut() {
    let mut val = 17;
    let f = || &mut val;
    assert_eq!(*call_closure_mut(f), 17);
}

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
   --> evo_main\src\main.rs:337:20
    |
337 |         let f = || &mut val;
    |                    ^^^^^^^^
    |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the body at 337:17...
   --> evo_main\src\main.rs:337:17
    |
337 |         let f = || &mut val;
    |                 ^^^^^^^^^^^
note: ...so that closure can access `val`
   --> evo_main\src\main.rs:337:20
    |
337 |         let f = || &mut val;
    |                    ^^^^^^^^
note: but, the lifetime must be valid for the expression at 338:20...
   --> evo_main\src\main.rs:338:20
    |
338 |         assert_eq!(*call_closure_mut(f), 17);
    |                    ^^^^^^^^^^^^^^^^^^^^
note: ...so that pointer is not dereferenced outside its lifetime
   --> evo_main\src\main.rs:338:20
    |
338 |         assert_eq!(*call_closure_mut(f), 17);
    |                    ^^^^^^^^^^^^^^^^^^^^

I'm afraid I don't understand what these error messages are telling me. Thanks for any help.

1 Like

What happens if you call f() twice (which is allowed because it is FnMut)? You would get aliasing exclusive references! This is disallowed. You can use FnOnce instead and a little finagling to get things to work

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=faafd17e23f4932d164f49921e7b7512

You need to create the exclusive reference outside the closure otherwise Rust doesn't infer the correct lifetimes, and you need to use a move closure so that it correctly captures it's environment by move, (no reborrows).

You can read more about how closures work in my blog post

1 Like

Thank you, Krishna. I'll read your post and think about your solution. However, I don't think FnOnce will work for my actual use case, which maybe is impossible. My code has some opaque handles to objects elsewhere. I'd like to be able to give that code a closure that it can use to convert handles to mutable references to the objects. Internally, a handle is just an index into a vector that holds the objects. I was hoping the closure could capture a mutable reference to the vector. My code could then call the closure, passing it a handle, and the closure would return a mutable reference to the indexed object. Does that sound doable?

No, that's not doable. A closure can't pass out exclusive references to it's environment multiple times. That would lead to shared exclusive references. Which is something that Rust cannot have. Instead of using closures, you can create your own opaque type that has a method that accepts an index and yields a reference. I don't think that would be too much boilerplate.

1 Like

Okay, that works fine, thanks, but why doesn't it have the same problem as the closure? Is it just easier for Rust to reason about?

struct ValueAccess<'a> {
    values: &'a mut [i32],
}

impl<'a> ValueAccess<'a> {
    fn get_value(&mut self, index: usize) -> &mut i32 {
        &mut self.values[index]
    }
}

#[test]
fn test_foo() {
    let mut values = vec![2, 4, 6, 8];
    let mut access = ValueAccess {
        values: &mut values,
    };

    assert_eq!(*access.get_value(1), 4);

    *access.get_value(1) = 5;
    assert_eq!(*access.get_value(1), 5);
}

When the closure yields mutable reference to the environment, it is supposed to live in some scope, unique to the closure (not to its call). When the function returns mutable reference, acquired from the mutable reference to its argument (and &mut self is just the argument, after all), this returned reference lives only as long as the original reference, and when the returned reference is no longer used, object can be borrowed again for another call.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.