One of the few implicit conversions available in Rust is pointer weakening, which can turn a &mut T
into a &T
:
fn just_foo<T>(_: &T) {}
just_foo(&mut vec![1, 2, 3]);
However, this doesn't happen when matching traits. For instance, although the + operator with references as right-hand sided values is implemented for numeric types, they won't accept mutable references to the same type:
5 + &mut 5;
(&5) + &mut 5;
The error message:
error[E0277]: the trait bound `{integer}: std::ops::Add<&mut {integer}>` is not satisfied
--> src/main.rs:38:7
|
38 | 5 + &mut 5;
| ^ no implementation for `{integer} + &mut {integer}`
|
= help: the trait `std::ops::Add<&mut {integer}>` is not implemented for `{integer}`
error[E0277]: the trait bound `&{integer}: std::ops::Add<&mut {integer}>` is not satisfied
--> src/main.rs:43:10
|
43 | (&5) + &mut 5;
| ^ no implementation for `&{integer} + &mut {integer}`
|
= help: the trait `std::ops::Add<&mut {integer}>` is not implemented for `&{integer}`
For another, more intriguing example, I added an assortment of implementations of Add
for a unit type Foo
:
use std::ops::Add;
#[derive(Debug, Default)]
struct Foo;
impl Add<Foo> for Foo {
type Output = Foo;
fn add(self, _: Foo) -> Foo {
Foo
}
}
impl<'a> Add<&'a Foo> for Foo {
type Output = Foo;
fn add(self, _: &'a Foo) -> Foo {
Foo
}
}
impl<'a, 'b> Add<&'a Foo> for &'b Foo {
type Output = Foo;
fn add(self, _: &'a Foo) -> Foo {
Foo
}
}
Only to find that I can perform &Foo + &mut Foo
, but not Foo + &mut Foo
:
&Foo + &mut Foo; // ok
Foo + &mut Foo; // not ok
The second case is in line with the previous example above, but the first one isn't. It seems that the RHS &mut Foo
was coerced to &Foo
to match the implementation of &Foo + &Foo
. It doesn't look either that other coercions are taking place, because the receiving type for &Foo as Add<&Foo>
is already &Foo
. I could also throw the syntactic sugar away and obtain the same outcome:
(&Foo).add(&mut Foo); // ok
Foo.add(&mut Foo); // not ok
Given that coercions, according to the Nomicon, are not supposed to happen when doing trait matching, why does this &Foo + &mut Foo
work when &i32 + &mut i32
doesn't? Is it because there is a single implementation of Add
for &Foo
? If so, why does it make the compiler behave differently?
Is it because there is a single implementation of
Add
for&Foo
?
Let's see what happens when we add this implementation:
impl<'b> Add<Foo> for &'b Foo {
type Output = Foo;
fn add(self, _: Foo) -> Foo {
Foo
}
}
Now &Foo + &mut Foo
and &Foo + &mut &mut Foo
fail to compile:
error[E0277]: the trait bound `&Foo: std::ops::Add<&mut Foo>` is not satisfied
--> src/main.rs:39:10
|
39 | &Foo + &mut Foo;
| ^ no implementation for `&Foo + &mut Foo`
|
= help: the trait `std::ops::Add<&mut Foo>` is not implemented for `&Foo`
error[E0277]: the trait bound `&Foo: std::ops::Add<&mut &mut Foo>` is not satisfied
--> src/main.rs:40:10
|
40 | &Foo + &mut &mut Foo;
| ^ no implementation for `&Foo + &mut &mut Foo`
|
= help: the trait `std::ops::Add<&mut &mut Foo>` is not implemented for `&Foo`
So the answer is yes.
If so, why does it make the compiler behave differently?
When there is a single applicable implementation of Add<T>
(or any other generic trait), the compiler doesn't need to infer T
from the arguments; it has already resolved T
based on that single implementation. Basically, it's as if the trait wasn't generic at all. Therefore, the coercions that work on non-generic arguments can be applied too.