Reference variable with explicit immutable reference type specified accepts both mutable and immutable borrow?

I noticed that a reference variable with type explicitly specified as immutable reference can accept both mutable as well as immutable borrow. Like

fn main() {
    let s1 = String::from("Hello");
    let sref: &str = &s1; //This is ok as expected
    println!("{sref}");
}

fn main() {
    let mut s1 = String::from("Hello");
    let sref: &str = &mut s1; // This is ok too!!
    println!("{sref}");
}

In both cases, the type of sref is &str and cannot call any str methods, that take &mut self as argument. Same holds true for simple types like i32, u32 etc as well. I was expecting only something like below to be possible: i.e only mutable reference type should be able to mutably borrow a value.

let sref : &mut str = &mut s1;

Why is this allowed and what is the purpose?

Thanks!

1 Like

Why is this allowed

&mut T to &T is one of the automatic coercions in Rust.

and what is the purpose?

To save you from having to write &*some_reference every time you need &T and have &mut T. Usually you would not do this when explicitly taking a reference; you would use it when:

  • You have a &mut T reference and are passing it to various functions, some of which do not need mutable/exclusive access.
  • You called a function which returns an &mut T reference (such as Option::get_or_insert()), but you only need &T.

In either of these cases, because another function is involved, you don’t have the option of just making all the reference types match.

4 Likes

Also note that the referent of the &mut _ is reborrowed, in case you wondered why the &_ doesn't conflict with the &mut _.

1 Like

Thanks for the link on reborrow. Upon trying couple of other examples around it, it is still not clear to me when &mut is considered a move and when is it a reborrow. For example:

fn main() {
   	let mut a = vec![10, 20, 30];
   	let b = &mut a;
   	let c: &mut Vec<i32> = b; // This compiles fine -> Is this reborrow?
   	c.push(4);
    b.push(5);
}

whereas

fn main() {
   	let mut a = vec![10, 20, 30];
   	let b = &mut a;
   	let c = b; // here b is moved. Compiler complains that b.push(5) was done after a move
   	c.push(4);
    b.push(5);
}

In both cases, the type of c is &mut Vec<i32>, except that in the latter case, it was inferred by compiler. From the example given in the link, looks it compiler considered it a reborrow because the function definition to which &mut _ was passed also explicitly declared the function parameter as &mut.

1 Like

Playing with this:

fn main() {
    woof();
}
 
fn woof(){
  	let mut a = vec![10, 20, 30];
  	let b = &mut a;
  	let c = &mut *b;
  	c.push(4);
    b.push(5);
}

this code runs fine. This is in my view a clear re-borrow, and since it's the same behaviour than let c: &mut Vec<i32> = b, this is also likely a re-borrow (that is, the same underlying operation). So the answer to the question in the code:

// This compiles fine -> Is this reborrow?

is potentially yes. I still find the behaviour of the type annotation on this case confusing and would expect an error instead.

(The only reason why the two borrows are allowed is due to the non-overlapping lifetime of the borrows iiuc. i.e c dies before b is used.)

And the case let c = b moves ownership of the reference, which I think it's expected.

Your examples have to do with coercion sites. The version with a c: &mut _ annotation is a reborrow, the version with no type annotation is a move. Making the expression on the right &mut *b is performing an explicit reborrow.

Sometimes moves happens due to generics, too.

use std::fmt::Display;

fn example<T: IntoIterator<Item: Display>>(iter: T) {
    for item in iter {
        println!("{item}");
    }
}

fn main() {
    let mut vec = vec![1, 2, 3];
    let borrow = &mut vec;
    // Fails to compile because `borrow` is moved
    // example(borrow);
    // example(borrow);
    
    // Compiles due to reborrowing...
    example(&mut *borrow);
    example(&mut *borrow);
    
    // ...though I'd suggest this instead
    example(&*borrow);
    example(&*borrow);
}

If example took a reference instead, the reborrow would be automatic.

2 Likes

Interestingly, the example by @TheRedTree is the same one in the OP here: better documentation of reborrowing · Issue #788 · rust-lang/reference · GitHub

I think by now that should be some good tutorials on this topic, but I don't know any. (there are some links in that post though.)