Reborrowing in generic function calls

Hi all,

Can someone explain the error in the following code:

fn foo_normal(value: &mut i32) {}

fn foo_generic<T>(value: T) {}

fn main() {
    let mut x = 10;

    let mut_ref: &mut i32 = &mut x;
    foo_normal(mut_ref);
    foo_normal(mut_ref); 

    foo_generic(mut_ref); // <== Reborrow here to avoid error: change to: foo_generic(&mut *mut_ref)
    foo_generic(mut_ref); // <== ERROR

    println!("x: {}", x);
}

I believe I understand how reborrowing works:
The compiler converts foo_normal(mut_ref) to foo_normal(&mut *mut_ref), thus creating a temporary reference—otherwise, foo_normal would gain ownership over mut_ref.

But I don't understand the error for the generic function call.
When calling foo_generic, T is inferred to be &mut i32, so why don't we see the same behavior?
As far as I understand, T is deduced before type coercion is checked, so why must we explicitly create a temporary reference?

Thanks

Remember that the lifetime is part of the type of the reference — therefore, when T is deduced, that includes a choice of lifetime, and the lifetime rustc chooses is the one that's already in the type of mut_ref — its entire remaining life. Arguably it should do something more flexible there, but it doesn’t.

2 Likes

Reborrowing isn't well documented,[1] but I think it works the same as let statements: you only get a coercion as specific as the annotation.

    let mut local = ();

    let borrow = &mut local;

    // Moves
    // let _this = borrow;
    // let _this: _ = borrow; // <-- similar to a generic T

    // Reborrows
    let _this: &mut _ = borrow;

    drop(borrow);

  1. pretty much not at all outside RFCs last I looked ↩︎

1 Like

Thanks! Everything makes sense now.

Thanks you for your help. I actually have a related question to your answer
Please check the following code:

fn main() {
    let mut num  = 10_u32;
    let a  = &mut num;
    *a = 20;

    let b = a;
    *b = 30;
    *a = 30; // This lines fails ( use of moved value: `a`)
    println!("num = {}", num);
}

I understand why the code fails.
But the thing is that I can replace let b = a with let b : &mut u32 = a and then everything works.
I was wondering if you can explain the difference betwen let with type annotation and without.
I couldn't find anything specific in the docs except that let with type annotation is a cohesion site. But I still don't understand why it matters, and what exactly is the b in let b = a?

I believe the difference is that if type inference is needed, then reborrowing doesn't happen. Type inference happens for example when you do let a = b, or when you call a generic function (where its generic type parameter needs to be inferred). If instead you manually specify the type of the let or call a non-generic function then no type inference needs to happen. This allows the compiler to insert an implicit reborrow.

1 Like

let with an annotation is a coercion site, yeah. And as far as I understand, that's when reborrowing kicks in. Even that is an incomplete bit of documentation: the &mut type constructor needs to be explicit in the annotation.

That's just a pile of words if you don't already understand what I mean, so here's another example:

    // Moves
    //let _a = Some(a);
    //let _a: _ = Some(a);
    //let _a: Option<_> = Some(a);

    // Reborrows
    let _a: Option<&mut _> = Some(a);
    let _a: Option<&mut u32> = Some(a);

Two of the three moving examples are annotated, just not annotated "enough".


If the core of the question is "why doesn't reborrowing kick in without the annotation anyway", I don't have an answer. It would make purposefully "discarding" a &mut _ impossible, I think (but I also think I've only done that in examples and borrow-checking experiments, not "real" code).