Why does auto borrowing not occur in Rust if I implement `TryFrom` for a reference type?

Viewed 244

Let's say I want to implement a conversion on a reference. In this case, it's a conversion from &f64 -> Foo.

use std::convert::{TryFrom, TryInto};

struct Foo {
    a: f64
}

impl TryFrom<&f64> for Foo {
    type Error = String;

    fn try_from(value: &f64) -> Result<Foo, String> {
        Ok(Foo {
            a: *value
        })
    }
}

fn main(){
    let foo: Foo = 5.0.try_into().unwrap();
    let bar: Foo = (&5.0).try_into().unwrap();
}

(Yes of course this is a pointless and stupid example, but it helps simplify the problem)

Now, the second line in the main method, with manual borrowing, succeeds. However, the first line in the main method, without the manual borrowing, fails with this error:

error[E0277]: the trait bound `Foo: From<{float}>` is not satisfied
  --> src/main.rs:18:24
   |
18 |     let foo: Foo = 5.0.try_into().unwrap();
   |                        ^^^^^^^^ the trait `From<{float}>` is not implemented for `Foo`
   |
   = note: required because of the requirements on the impl of `Into<Foo>` for `{float}`
note: required because of the requirements on the impl of `TryFrom<{float}>` for `Foo`
  --> src/main.rs:7:6
   |
7  | impl TryFrom<&f64> for Foo {
   |      ^^^^^^^^^^^^^     ^^^
   = note: required because of the requirements on the impl of `TryInto<Foo>` for `{float}`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

Playground

Why is automatic borrowing not working here?

1 Answers

Just as the error message suggests, the problem is the trait bound Foo: From<{float}> is not satisfied. When matching traits, Rust will not perform any coercion but probing the suitable method. This is actually documented in The Rustonomicon, reads

Note that we do not perform coercions when matching traits (except for receivers, see the next page). If there is an impl for some type U and T coerces to U, that does not constitute an implementation for T.

and the next page says

Suppose we have a function foo that has a receiver (a self, &self or &mut self parameter). If we call value.foo(), the compiler needs to determine what type Self is before it can call the correct implementation of the function. ... If it can't call this function (for example, if the function has the wrong type or a trait isn't implemented for Self), then the compiler tries to add in an automatic reference. This means that the compiler tries <&T>::foo(value) and <&mut T>::foo(value). This is called an "autoref" method call.

So when matching the trait bound, Rust compiler will try to auto ref/deref on the type only. In addition, the dot operator in rust is just a syntax sugar of fully qualified function call. Thus 5.0.try_into().unwrap(); will become f64::try_into(5.0).unwrap(); and since TryInto is not implemented for f64, Rust will try to auto reference it by calling &f64::try_into(5.0).unwrap();. Now the compiler can find a version of TryInto implemented for &f64, however the type of argument still doesn't match: try_into for &f64 requires &f64 as parameter type, while the current call provides f64, and Rust compiler cannot do any coercion on parameters when checking trait bound. Thus the trait bound still doesn't match (&f64 cannot take f64 argument) and the check failed. Thus you will see the error message.

Related