What is the rationale for allowing mutable references as values for immutable function parameters?


#1

Hi,
I started learning Rust last week and so far I had a great experience using it. When building sandbox examples I stumbled over a peculiar thing, though:
When defining a function:

fn foo(n : &i32) -> i32 {
return *n;
}

I can then go on and call it with:

let mut n = 5;
let ret = foo(&mut n);

without the compiler complaining.

So my question is: why is this not a type mismatch compiler error? (&mut vs &)

I understand that this is a constructed case and that the function can safely operate on the passed mutable reference. It just feels inconsistent to me, am I missing something?

PS: for another example see this repo (rust-clippy)


#2

It’s just a coercion. It exists to allow me to write this function:

fn bar(n: &mut i32) -> i32 {
    foo(n)
}

If it wasn’t allowed, I’d have to do this:

fn bar(n: &mut i32) -> i32 {
    foo(&*n)
}

#3

Then I don’t really understand what’s left to discuss.

  • &mut allows code to read and write
  • & allows code to read

So it’s perfectly safe to reborrow from &mut x into &x, since the allowed operations of &x are a subset of the ones allowed for ´&mut x`.


#4

Just because it can doesn’t mean that it should. In this case, it should, but there’s still stuff to discuss here.

  • Am I correct in assuming that this is a coercion and not a bug?
    " Under what circumstances does this coercion apply?
  • Is it the same as &*n or does it do it some other way?
  • Why was the decision made to add this coercion?

etc.


#5

One case where it doesn’t coerce, and I wish it would, is in trait implementations.

use std::fmt::Display;

trait Foo: Display {}
impl<'a> Foo for &'a String {}
fn foo<F: Foo>(f: F) { println!("{}", f) }

fn bar(s: &String) { foo(s) }

fn main() {
    let mut s = "hello".to_string();
    foo(&s);     // OK
    foo(&mut s); // error: the trait bound `&mut std::string::String: Foo` is not satisfied 
    bar(&s);     // OK
    bar(&mut s); // OK
}

playground


#6

Your example works if you add this impl:

impl<'a> Foo for &'a mut String {}

I guess because & T and &mut T are different types strictly speaking. (This gets related to the discussion we were having on the thread where I originally thought a borrow implicitly matched a trait bound of whatever it was borrowed)


#7

As an aside for a relatively important detail:

If you are developing a trait Foo, think over immediately if it needs blanket implementations for Box<F>, &F and &mut F where F: Foo. Adding any of those blanket implementations is a breaking change.