Is this "reborrowing"? How to transform this struct?

I’ve got a LayoutCtx that wraps a &mut reference. I’m passing this down a tree of “widgets” as they do layout. But I’ve hit an bit of ugliness in my design, and feel I need to cheat a bit and create an OtherCtx from a LayoutCtx, just for a few lines of code.

This looks like what people are calling a “reborrow”, but I’m not sure.

The compile error is show below. Is there any way to do this? Maybe I need to pass my LayoutCtx by value, not by &mut reference. That would mean I have to keep reconstructing them, which is annoying.

struct State { }
struct LayoutCtx<'s> {
    window_state: &'s mut State
}
struct OtherCtx<'s> {
    window_state: &'s mut State
}

impl<'s> LayoutCtx<'s> {
    fn fee(&self) { }
    fn to_other_ctx(&mut self) -> OtherCtx<'s> {
    // fn to_other_ctx(mut self) -> OtherCtx<'s> {
        OtherCtx {
            window_state: self.window_state
        }
    }
}
impl<'s> OtherCtx<'s> {
    fn faa(&self) { }
}
fn layout(lo_ctx: &mut LayoutCtx) {
    // But I need OtherCtx, just for a bit!
    let mut other_ctx = lo_ctx.to_other_ctx();
    other_ctx.faa();
    lo_ctx.fee();
}
fn main() {
    println!("Test");
    let mut state = State { };
    let mut lo_ctx = LayoutCtx { window_state: &mut state };
    layout(&mut lo_ctx);
}

Compile error:

error: lifetime may not live long enough
  --> learning/rust_lang/examples/transform_ctxs.rs:13:9
   |
 9 |   impl<'s> LayoutCtx<'s> {
   |        -- lifetime `'s` defined here
10 |       fn fee(&self) { }
11 |       fn to_other_ctx(&mut self) -> OtherCtx<'s> {
   |                       - let's call the lifetime of this reference `'1`
13 | /         OtherCtx {
14 | |             window_state: self.window_state
15 | |         }
   | |_________^ method was supposed to return data with lifetime `'s` but it is returning data with lifetime `'1`


This fixes the error:

 impl<'s> LayoutCtx<'s> {
     fn fee(&self) { }
-    fn to_other_ctx(&mut self) -> OtherCtx<'s> {
+    fn to_other_ctx(&mut self) -> OtherCtx<'_> {

It's not reborrowing, but it is emulating reborrowing to the extent possible with a function signature.

The reason you can't return OtherCtx<'s> is that you can't get a &'long mut U through dereferencing a &'short mut &'long mut U, which is what window_state: self.window_state is effectively trying to do with the original signature. (If the OP compiled, after the method returned you would have two active &mut _ to the same State, and that's undefined behavior.)

I don't see why OtherCtx and LayoutCtx need be different types, but that's a separate issue.


Reborrowing is only possible with references (so far[1]). The idea behind reborrowing is: you have an existing reference r and referent *r. The reborrow is a borrow of *r. It cannot be valid for any longer than r was valid, but -- crucially -- the reborrow is not a borrow of r itself.

That means, for example, r can go out of scope without making the reborrow invalid (the reborrow reference can "out last" the original reference).

In contrast, with to_other_ctx, the original LayoutCtx<'s> remains borrowed so long as the returned OtherCtx<'_> is in use.

Here's a demonstration in code form:

As demonstrated in the playground, creating OtherCtx<'_> (or LayoutCtx<'_>) in place may work in situations where to_other_ctx does not. On the other hand, to_other_ctx may be enough for your use case.


  1. There are some experiments around being able to implement reborrowing for your own struct, but they're in the infant stages. ↩︎

1 Like

Okay, let me see if I can get some terms right, and some other pieces of this.

So in the fixed code, the lifetime parameter ‘x of other_ctx: OtherCtx<‘x> is the lifetime of the reference lo_ctx: &'x mut LayoutCtx? It knows that while I have other_ctx, I can’t use lo_ctx. Do you say that other_ctx is a “borrow” of lo_ctx in that case (inside the layout function)?

Part of what’s confusing me now is that the lifetime of other_ctx, is obviously less than the lifetime of lo_ctx, but the lifetime parameter in OtherCtx is taken from the lo_ctx.

Let's add some annotations:

fn layout<'x, 'lo>(lo_ctx: &'x mut LayoutCtx<'lo>) {
    let mut other_ctx: OtherCtx< /* 'other */ > = lo_ctx.to_other_ctx();
    other_ctx.faa();
    // `'other` can end here
    lo_ctx.fee();
}

other_ctx ends up being OtherCtx<'other> and not OtherCtx<'x>. How? *lo_ctx was automatically reborrowed when you called .to_other_ctx().[1]

The lifetimes are related: you can't reborrow *lo_ctx for longer than 'x. But you can reborrow it for some shorter duration. The relationship in code form is 'x: 'other.

So the parameter of OtherCtx<'_> is not 'x exactly. But it is true that while you have other_ctx, you can't use lo_ctx.

I guess I'd say something like: other_ctx contains a reborrow of *lo_ctx.

If you force other_ctx to be OtherCtx<'x>, there is indeed a conflict. So the implicit reborrowing in the call to to_other_ctx was necessary.

Does that clear it up?


  1. Turns out this reborrow business happens invisibly all over the place. Rust code would be painfully verbose if it didn't. ↩︎

1 Like

That helps a lot, thanks. There’s still a lot I don’t get about lifetimes, but one step at a time. I need to read your entire site that you linked to above.