Description
Here are a couple of minimal example of the situation:
trait Trait {
fn b(&mut self) {}
}
struct Struct;
impl<'a> Trait for &'a mut Struct {}
#[allow(dead_code)]
fn foo(mut a: &mut Struct) {
a.b();
}
fn main() {}
trait TraitA {}
trait TraitB {
fn b(&mut self) {}
}
impl<A: TraitA> TraitB for A {}
impl<'a> TraitA for &'a mut TraitA {}
#[allow(dead_code)]
fn foo(mut a: &mut TraitA) {
a.b();
}
fn main() {}
Now why was mut
required on a
in both cases? Because otherwise, the call to a.b()
won't work: error: cannot borrow immutable argument
a as mutable
.
The reason why this is so is that the trait implementation is on &mut T
, and so that (&mut T)
is an opaque type for the purposes of the trait implementation, having no special mutability as it would otherwise have. Then, when calling the b
method, it will take a second borrow &mut (&mut T)
. But in order to do that, it not having special knowledge of the fact that &mut T
is considered mutable already, it requires you to place it in a mutable slot.
The real-life case where I hit this a couple of weeks ago and where someone else in IRC hit it today is implementing additional methods on Writer
or Reader
and then using the trait objects:
trait ReaderUtils: Reader {
fn read_foo(&mut self) -> Foo {
unimplemented!();
}
}
impl<R: Reader> ReaderUtils for R {}
fn unfoo(mut r: &mut Reader) {
let _ = r.read_foo();
}
In this case, all of a sudden read_foo
is a second-class citizen, behaving differently from the standard Reader
methods. If you don't use such a method, mut r
will warn you about unnecessary mutability. If you do use such a method, then all of a sudden you must use mut r
.
(I'm not sure if there is a real use case for implementing traits on &mut T
other than trait objects, but it's possible. It's more the trait objects that I care about.)
This is not incorrect behaviour—it is consistent; it is merely surprising behaviour. Thus, if it can be improved, it would be good. I'm not sure whether or not it can be improved, because I suspect that any special detection of a &mut T
implementation as mutable would just defer the problem to the next level of generic bounds.