A werid dot syntax question in trait impl with minimum code

struct Test<'a> {
    v: &'a i32,
}

trait MyTestTrait {
    type Output;
    fn borrow_mut(self) -> Self::Output;
}

trait Trait2 {
    type Output;
    fn borrow_mut2(self) -> Self::Output;
}

impl<'a> MyTestTrait for &'a mut Test<'a> {
    type Output = i32;
    fn borrow_mut(self) -> i32 {
       0
    }
}

impl Trait2 for Test<'_> {
    type Output = i32;
    fn borrow_mut2(self) -> i32 {
       0
    }
}

fn main(){
   let i = 0;
   let mut m = Test { v: &i };
   m.borrow_mut(); // why this line passes the compile checker? m don't have type &mut Test 

   let mut m2 = Test { v: &i };
   m2.borrow_mut2();
}

OP: 调用trait中需要mutable借用方法影响后续对象的使用 - Rust语言中文社区

This is generally known as "autoref": If you call value.method() where value: T, the compiler will search for methods on T, followed by &T, followed by &mut T. This is explained in the Rust Reference (emphasis mine):

The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate T, add &T and &mut T to the list immediately after T.

[...]

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  1. T's inherent methods (methods implemented directly on T).
  2. Any of the methods provided by a visible trait implemented by T. If T is a type parameter, methods provided by trait bounds on T are looked up first. Then all remaining methods in scope are looked up.

So when m.borrow_mut() is called, the compiler tries to resolve the method call in this order:

<Test<'_> as MyTestTrait>::borrow_mut(m)
// no match: `Test<'_>` does not impl `MyTestTrait`
<&'_ Test<'_> as MyTestTrait>::borrow_mut(&m)
// no match: `&'_ Test<'_>` does not impl `MyTestTrait`
<&'_ mut Test<'_> as MyTestTrait>::borrow_mut(&mut m)
// match: `&'_ mut Test<'_>` impls `MyTestTrait`; the borrow checker is
// able to match the lifetimes of `&mut m` and `m.v`
1 Like

Thank you, that's a rather neat answer to me, hence leads to another question:

What if I just want to imply the trait for the pointer to that type,I'll have to define a another concrete struct which wraps the pointer type right?

Then why even allow this kind of writing

impl & mut Test<'a>

which might leads to unexplicit behaviour, rather than only accepting writing like this:

impl Test<'a>

What if I just want to imply the trait for the pointer to that type,I'll have to define a another concrete struct which wraps the pointer type right?

Yes, there's no way to prevent the autoref conversion. It's the same concept behind inherent methods with (&self) or (&mut self) receivers, which implicitly create references to the object they're called on.

Then why even allow this kind of writing

impl & mut Test<'a>

which might leads to unexplicit behaviour, rather than only accepting writing like this:

impl Test<'a>

Implicit reference creation by method-call syntax is generally not considered much of an issue. If you own a value, then it's always legal to reference it, as long as the borrow checker can resolve the lifetimes.

Also, trait implementations like impl Trait for &mut Type can be useful for generic code. For instance, Vec has three IntoIterator impls:[1]

impl<T> IntoIterator for Vec<T> {
    type Item = T;
    type IntoIter = vec::IntoIter<T>;
    ...
}

impl<'a, T> IntoIterator for &'a Vec<T> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;
    ...
}

impl<'a, T> IntoIterator for &'a mut Vec<T> {
    type Item = &'a mut T;
    type IntoIter = slice::IterMut<'a, T>;
    ...
}

With these, generic code expecting a type I: IntoIterator<Item = &T> can accept a &Vec<T>, since &Vec<T>: IntoIterator<Item = &T>.

Blanket implementations for references can also be very useful. For example, the PartialEq trait can look through any number of references to determine equality; &&&x == &&&y is equivalent to x == y. It does this through a pair of blanket impls for all references:

impl<A: ?Sized, B: ?Sized> PartialEq<&B> for &A
where
    A: PartialEq<B>,
{ ... }

impl<A: ?Sized, B: ?Sized> PartialEq<&mut B> for &mut A
where
    A: PartialEq<B>,
{ ... }

Is there any particular issue that autoref conversion is causing?


  1. for brevity, I've omitted the A: Allocator parameter ↩︎

2 Likes

Because &mut T is a type in its own right, first of all. It'd be unnecessarily inconsistent, if it couldn't have its own implementations.

2 Likes

So here is my take on auto ref

// m.borrow_mut() triggers autoRef, be like:
let a = &mut m;

//a take m, borrow_mut() take a, return a i32.
a.borrow_mut(); 

// m and a been dropped by now.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.