Question about reborrowing?

Am I right that a reborrow happens when I pass a &mut T through to another function?

fn f1(t: &mut Thing) { ... }
fn f2(t: &mut Thing) {
    ...
    f1(t); // here?
}

But it’s not really possible to package up a few mutable references and pass them together and have them reborrowed? Something like this:

struct Context<'a, 'b> {
    thing: &mut 'a Thing,
    other: &mut 'b Other,
}
fn f1(ctx: Context) { ... }
fn f2(t: &mut Thing) {
    let mut other_thing: Other = ...
    let ctx = Contex {
        thing: t,
        other: &mut other_thing
    };
    f1(ctx);
    t.x = 123;
    ...
}

Yes. One way to see this happening is with below:

fn foo(_: &'static mut ()) {}
fn bar(x: &'static mut ()) {
    foo(x);
    foo(x);
}
error[E0499]: cannot borrow `*x` as mutable more than once at a time
 --> src/lib.rs:4:9
  |
3 |     foo(x);
  |     ------
  |     |   |
  |     |   first mutable borrow occurs here
  |     argument requires that `*x` is borrowed for `'static`
4 |     foo(x)
  |         ^ second mutable borrow occurs here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `example` (lib) due to 1 previous error

Another way to see this is by calling f1 twice with t without a compilation error despite the fact &mut T doesn't implement Copy.

Not sure I understand the problem with that specific example beyond the syntax errors seeing how below compiles:

struct Thing {
    x: usize,
}
struct Context<'a, 'b> {
    thing: &'a mut Thing,
    other: &'b mut (),
}
fn f1(ctx: Context<'_, '_>) {}
fn f2(t: &mut Thing) {
    let mut other_thing = ();
    let ctx = Context {
        thing: t,
        other: &mut other_thing,
    };
    f1(ctx);
    t.x = 123;
}

You'll likely need to "reborrow" manually if the compiler doesn't do it for you.

1 Like

Pretty much, yep. Workarounds include

  • Recreate the Context
  • Have some approximation of reborrowing
    impl Context<'_, '_> {
        // The return value keeps the original `Context<'_, '_>` borrowed,
        // so this isn't true reborrowing.
        fn subborrow(&mut self) -> Context<'_, '_> {
            Context {
                thing: self.thing,
                other: self.other,
            }
        }
    }
    

Reborrowing isn't necessary for your example as written, and wouldn't have helped if you had something like

    f1(ctx.subborrow());
    t.x = 123;
    // Further uses of `ctx`
    f1(ctx);

Just like it doesn't help here

fn f3(t: &mut String) {
    let reborrow_somewhere: &mut _ = t;
    reborrow_somewhere.push('h');
    t.push('i');
    reborrow_somewhere.push('!');
}

But it would help cases like

    f1(ctx.subborrow());
    ctx.thing.x = 123;
    f1(ctx);
2 Likes

It's probably possible, but it's not clear how is that related to reborrow.

Without reborrow you wouldn't be able to call f1(t: &mut Thing) if you only have t2: &mut Thing: mutable references are invariant and their lifetime couldn't be neither expanded not reduced. But reborrow turns one, single mutable reference into two: one exist in f2 and one exist in f1.

No one outside of f1 or f2 knows about that evil plot and it's valid because f1/t and f2/t are never active simultaneously.

What you talk about is some other thing: f1 and f2 have different types and that makes the whole thing much less problematic.

The story with reborrow exist to help with POLA: on one hand calling f1(t: &mut Thing) from f2(t: &mut Thing) shouldn't be possible if you follow “mutable xor shared” model, yet the mere idea that you couldn't even just simply do an empty wrapper f2 with the exact same declaration that just simply calls f1 is violating principle of least astonished pretty severely: developers who have not idea about these “egghead-invented” lifetimes and their rules would object pretty vehemently to such a language.

Because… come on, that's just stupid an wrong: if types are exactly the same then why should that be forbidden? It makes not sense whatsoever!

Adding reborrows was probably simpler than trying to convince people that such “an obvious correct” code is, somehow, wrong.

1 Like

Here’s an example where I just flail. I’m trying to pass the mutable ref down the call tree. Only one is active at a time. But I’m lost in the errors that I’ve never seen before Rust…

pub struct Ctx<'s> {
    pub window_state: &'s mut State
}
fn connected_to_window(&self, ctx: Ctx) { 
   self.for_each_child(&|ch: &dyn Controller| {
       let ctx2 = Ctx {
           window_state: ctx.window_state
       };
       ch.connected_to_window(ctx2);
       // ch.connected_to_window(ctx);  // Can move out of captured var
   });
   ...

Error:

error[E0596]: cannot borrow `*ctx.window_state` as mutable, as it is a captured variable in a `Fn` closure
  --> ui/src/controller.rs:86:31
   |
84 |         self.for_each_child(&|ch: &dyn Controller| {
   |                              ------------------------------- in this closure
85 |             let ctx2 = Ctx {
86 |                 window_state: ctx.window_state
   |                               ^^^^^^^^^^^^^^^^ cannot borrow as mutable


Recreating Ctx approach:

            let ctx2 = Ctx {
                window_state: ctx.window_state,
            };
            ch.connected_to_window(ctx2);
            let ctx2 = Ctx {
                window_state: ctx.window_state,
            };
            ch.connected_to_window(ctx2);

Approximating reborrows approach:

        self.for_each_child(&|ch: &dyn Controller| {
            ch.connected_to_window(ctx.sub());
            ch.connected_to_window(ctx.sub());
        });

These both need FnMut (or FnOnce). If that's not possible -- if you can't change the Fn bound indicated in the error message -- you'll need to introduce some sort of shared mutability.

        let ctx = RefCell::new(ctx);
        self.for_each_child_fn(&move |ch: &dyn Controller| {
            ch.connected_to_window(ctx.borrow_mut().sub());
            ch.connected_to_window(ctx.borrow_mut().sub());
        });

What the OP is talking about is the problem that user-defined "reference-like" types cannot implement reborrowing in Rust. &mut is privileged in that regard. There's no reason why OP's Context type couldn't in principle be "reborrowed" just like its contained references individually can; it's just a missing feature. There's a project goal aiming to add a reborrow trait and the associated language support, so maybe one day…

Ah. Got it. I guess it's “curse of knowledge”: because I have deeply interned the fact that there would always be programs that are sensible, yet rejected by Rust compiler (it's simply mathematically not possible to do otherwise) the fact that there could be properties of &mut that other types couldn't recreate wasn't suprising to me: we want/need to pass &mut around often, while turning temporary borrow into a large structure shouldn't be common, thus having special rulest just for &mut makes sense.

And yes, who knows, perhaps one day someone would expand reborrow on user-defined structs, who knows?

Custom reborrow is actually a quite common need (example: ndarray, capnp), and has a lang experiment going on.

1 Like