Use of an object consisting of a mutable reference after passing the object to a function by value

Hello.

In short: Why this code doesn't compile? Is it a problem with lifetime specifiers in the reference function, can it be fixed to make the code compile?

Long version, to explain the relationale behind the reference function:

I have a mutable reference that I want to pass to a function, but I want to be able to use it later, after the function returns. Example:

fn foo(aref: &mut i32) {}
let mut a = 1;
let aref = &mut a;
foo(aref);
*aref = 2;

It's obviously possible with immutable references: an immutable reference can be copied to pass it by value into a function, leaving the original untouched.

I thought, since a mutable reference cannot be cloned/copied, if it passed by value into a function, then it won't be accesible anymore in the original scope. But it turns out passing mutable references into functions behaves similar to immutable references, so the code above works.

But I wanted to wrap the mutable reference into a wrapper type, to encapsulate the reference and provide additional methods. I wrapped the mutable reference into a wrapper, but the wrapper seems to lack the privileges of mutable references; when it is passed by value into a function, it's gone in the parent scope and cannot be used anymore (unless the function returns it back, but that's not what I want). Example:

struct Parent<'a>(&'a mut i32);
fn foo(aref: Parent<'_>) {}
let mut a = 1;
let aref = Parent(&mut a);
foo(aref);
*aref.0 = 2; // error[E0382]: use of moved value: `aref.0`

Clone/Copy cannot be implemented for a wrapper either because the contained mutable reference cannot be cloned.

Initially, I solved it by copying the wrapper "by hand", creating a new wrapper object referencing the mutable reference from the previous:

foo(Parent(aref.0)); // Parent(aref.0) instead of aref
*aref.0 = 2; // Works again

But that meant exposing the internals of the wrapper, making the contained mutable reference public, and I wanted to avoid that, so I thought I could create a reference method that does exactly that "copy by hand": Parent(aref.0), but in a method of Parent. I tried that, but it didn't work; the return value of reference, even after leaving its scope, is still considered an active borrow. See the code in the beginning.

 impl<'a> Parent<'a> {
-    fn reference(&'a mut self) -> Parent<'a> {
+    fn reference(&mut self) -> Parent<'_> {
         Parent(self.0)
     }
 }

The problem with your original function is that 'a represents the entire lifetime in which self.0 is valid and exclusively borrowed. By returning a new Parent<'a>, you're asserting that the return value will also be valid and exclusively borrowed for the same lifetime, which necessarily prevents any further use of the original value.

The new signature, on the other hand, borrows self for some unspecified shorter time and guarantees the exclusivity of the return value for only that shorter lifetime. Once the return value is no longer being used, the borrow ends and the original object is "unlocked" for further use.

6 Likes

Thank you for your reply, this actually works!

Actually, that was the first thing I tried. Only after I got an error I started juggling lifetimes.

I was getting the following error for almost the same code as you proposed:

error: lifetime may not live long enough
 --> src/main.rs:7:14
  |
5 | impl<'a> Parent<'a> {
  |      -- lifetime `'a` defined here
6 |     fn reference(&mut self) -> Parent<'_> {
  |                  - let's call the lifetime of this reference `'1`
7 |         Self(self.0)
  |              ^^^^^^ this usage requires that `'1` must outlive `'a`

with a small, but significant difference:

    fn reference(&mut self) -> Parent<'_> {
        Self(self.0)
    }

instead of:

    fn reference(&mut self) -> Parent<'_> {
        Parent(self.0)
    }

I guess, Self includes not only the type Parent, but also the lifetime 'a, and that is the reason I was getting the error.

Right. That's a subtle one.

And related.

4 Likes