Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does reference weakening from &mut occur in some trait method calls?

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

Full Playground

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?

like image 244
E_net4 stands with Ukraine Avatar asked Mar 23 '18 16:03

E_net4 stands with Ukraine


People also ask

Why are references weak?

A weak reference allows the garbage collector to collect an object while still allowing an application to access the object. If you need the object, you can still obtain a strong reference to it and prevent it from being collected.

How does a weak reference work?

A weakly referenced object is cleared by the Garbage Collector when it's weakly reachable. Weak reachability means that an object has neither strong nor soft references pointing to it. The object can be reached only by traversing a weak reference.

What are weak references in Python?

A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else.

What is a weak reference in C++?

In computer programming, a weak reference is a reference that does not protect the referenced object from collection by a garbage collector, unlike a strong reference.


1 Answers

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.

like image 82
Francis Gagné Avatar answered Oct 12 '22 22:10

Francis Gagné