Skip to content

Implementations of traits for &mut T yields surprises about mutability #12381

Closed
@chris-morgan

Description

@chris-morgan

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions