Trying to pass borrowed refs betwen structs

In my library, I have a context type which holds some mutable references its methods use to do stuff (get common data, send messages, etc). Let's call it MyContext. An example of a function that uses MyContext:

fn example_function(context: &mut MyContext) {
    *context.my_ref = 42;
}

struct MyContext<'a> {
    my_ref: &'a mut i32,
    // ...
}

For various reasons, I have another context type OtherContext, which holds some of the same mutable references as the first one. I need to create an OtherContext object from a MyContext. The OtherContext will never outlive the MyContext.

At first I tried to write it this way:

struct MyContext<'a> {
    my_ref: &'a mut i32,
    // ...
}

impl<'a> MyContext<'a> {
    fn get_other_context(&mut self) -> OtherContext<'a> {
        OtherContext {
            my_ref: self.my_ref,
        }
    }
}

struct OtherContext<'a> {
    my_ref: &'a mut i32,
}

To which the compiler replies:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/lib.rs:12:9
   |
12 |         OtherContext {
   |         ^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined here...
  --> src/lib.rs:11:26
   |
11 |     fn get_other_context(&mut self) -> OtherContext<'a> {
   |                          ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:13:21
   |
13 |             my_ref: self.my_ref,
   |                     ^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined here...
  --> src/lib.rs:10:6
   |
10 | impl<'a> MyContext<'a> {
   |      ^^
note: ...so that the types are compatible
  --> src/lib.rs:12:9
   |
12 | /         OtherContext {
13 | |             my_ref: self.my_ref,
14 | |         }
   | |_________^
   = note: expected `OtherContext<'a>`
              found `OtherContext<'_>`

Ouch.

From what I'm understanding, the compiler thinks I'm borrowing from self, and, given 's the lifetime of &mut self, wants 's: 'a, in other words, want the lifetime of the context object and the context being pointed to to be the same.

But this is super inconvenient!

While this compiles:

fn get_other_context<'s: 'a>(&'s mut self) -> OtherContext<'a> { ... }

If I try to call it from my example function:

fn example_function(context: &mut MyContext) {
    *context.my_ref = 42;
    let other_context = context.get_other_context;
}

... then I get the following error:

error[E0623]: lifetime mismatch
 --> src/lib.rs:3:33
  |
1 | fn example_function(context: &mut MyContext) {
  |                              --------------
  |                              |
  |                              these two types are declared with different lifetimes...
2 |     *context.my_ref = 42;
3 |     let other_context = context.get_other_context();
  |                                 ^^^^^^^^^^^^^^^^^ ...but data from `context` flows into `context` here

In other words, if I want to re-borrow MyContext into a different type of context object, then I need to tell the compiler that the lifetime of MyContext and the lifetime of the data it points to are the same, which reasonably breaks the code I might want to use MyContext in, even though I'm creating temporary references that are dropped when they're no longer used anyway.

Is there a saner way to write get_other_context() that I'm not seeing? Conceptually speaking, what potential harm in my code is the borrow checker trying to warn me about?

This original error is the same situation as in this other recent thread. I'll come back to this later, but for now, let's look at how you got past this step a little closer:

    fn get_other_context<'s: 'a>(&'s mut self) -> OtherContext<'a> {

Here you're saying, 's has to live as long as, or longer than, 'a. But you can't have a reference last longer than the thing it points to -- you can never have a &'long mut &'short mut T. And remember that self here is a MyContext<'a>. So there's a pre-existing implied bound that 'a: 's. Taken together, it must be the case that 's and 'a are the same. You can rewrite it like so to be more clear:

    fn get_other_context(&'a mut self) -> OtherContext<'a> {

Now let's give the lifetimes on example_function names to make things more clear there too, and consider the error:

20 | fn example_function<'r, 'c>(context: &'r mut MyContext<'c>) {
   |                                      ---------------------
   |                                      |
   |                                      these two types are declared with different lifetimes...
21 |     *context.my_ref = 42;
22 |     let other_context = context.get_other_context();
   |                                 ^^^^^^^^^^^^^^^^^ ...but data from `context` flows into `context` here

Here, 'r and 'c might be different, but get_other_context forces them to be the same. The compiler can't coerce it for you, because lifetimes behind a &mut are invariant -- it can't shorten 'c to match 'r. So, you need to require them to be the same in order to call get_other_context:

fn example_function<'r>(context: &'r mut MyContext<'r>) {
    *context.my_ref = 42;
    let other_context = context.get_other_context();
}

Now, that compiles, but it's probably not what you actually want. &'a mut SomeStruct<'a> means that the struct is exclusively borrowed for the entirety of its lifetime. This is almost always too restrictive -- calling that function makes the struct worthless afterwards. (Actually worse than worthless, as things like Drop can make the lifetimes unworkable.)

So let's take a step back.


Here:

impl<'a> MyContext<'a> {
    fn get_other_context(&mut self) -> OtherContext<'a> {
        OtherContext {
            my_ref: self.my_ref,
        }
    }
}

Do you really need to return an OtherContext<'a> specifically? If you can be more flexible by returning a shorter lifetime -- the lifetime of the borrow of self -- then the example as given will just work.

impl<'a> MyContext<'a> {
    fn get_other_context(&mut self) -> OtherContext<'_> {
        OtherContext {
            my_ref: self.my_ref,
        }
    }
}

fn example_function(context: &mut MyContext<'_>) {
    *context.my_ref = 42;
    let other_context = context.get_other_context();
}

Playground.

3 Likes

Thanks!

My problem was actually more complicated (OtherContext and get_other_context took additional references), but I wasn't able to solve the simplified version before your answer.

Now I've managed to get from the simplified version to the complex one. The resulting code looks like:

impl<'a> MyContext<'a> {
    fn get_other_context<'b>(&mut self, another_ref: &'b mut i32) -> OtherContext<'_, 'b>
    {
        OtherContext {
            my_ref: self.my_ref,
            other_ref: another_ref,
        }
    }
}

struct OtherContext<'a, 'b> {
    my_ref: &'a mut i32,
    other_ref: &'b mut i32,
}

Which compiles without problem.

The frustrating thing is I tried things close to OtherContext<'_>, but because I was neck deep in lifetimes, I had added other clauses which added other errors. Taking a step back and looking at a simplified example really helped.

Thanks again for your clear explanation!

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.