rustrust-analyzer

Does the core crate will implement a default trait for my struct and why the core crate does this?


code:

struct Foo;

impl Into<u32> for Foo {
    fn into(self) -> u32 {
        1
    }
}

fn main() {
    let foo = Foo;
    let bar: u32 = foo.into() * 1u32;
}

I get error message.

error[E0283]: type annotations needed
  --> src/main.rs:11:24
   |
11 |     let bar: u32 = foo.into() * 1u32;
   |                        ^^^^
   |
note: multiple `impl`s satisfying `Foo: Into<_>` found
  --> src/main.rs:3:1
   |
3  | impl Into<u32> for Foo {
   | ^^^^^^^^^^^^^^^^^^^^^^
   = note: and another `impl` found in the `core` crate:
           - impl<T, U> Into<U> for T
             where U: From<T>;
help: try using a fully qualified path to specify the expected types
   |
11 |     let bar: u32 = <Foo as Into<T>>::into(foo) * 1u32;
   |                    +++++++++++++++++++++++   ~

For more information about this error, try `rustc --explain E0283`.
error: could not compile `hello` (bin "hello") due to 1 previous error

I can not understand the error message.

What does the error mean? Does it mean the language core also implemented the Into trait for my struct Foo and the compiler can not known which one to use? Why does the core crate implement a Into for me? Where can I learn more about this topic?


Solution

  • What is the core crate?

    Does it mean the language core also implemented the Into trait for my struct?

    No. Language (compiler really) only implements "auto traits" (like Send, Sync, etc.). However the standard library contains a bunch of generic trait implementations.

    Compiler error speaks about the core crate. Rust's standard library is split into 3 crates. core (dependency free, lowest level building blocks), alloc (everything that involves allocation) and std (interacting with the OS, reexports items from core and alloc crates). So really compiler tells you that there exist some implementation in the standard library (core part of it).

    Why does compiler error?

    Traits From and Into are reflexive. This means that there exist in the standard library following blanket implementations:

    impl<T> From<T> for T { ... }
    impl<T, U> Into<U> for T
    where
        U: From<T>
    { ... }
    

    This both blanket implementations imply impl<T> Into<T> for T.

    This means that using those traits with type inference is tricky. In general you cannot write

    let x: _ = Into::into(<expression>);
    

    because compiler cannot infer both types of left hand side pattern and right hand side expression. You might think that you described its type by writing type annotation on bar:

    let bar: u32 = foo.into() * 1u32;
    

    But since you are using * operator compiler is really invoking some specific implementation of std::ops::Mul trait. So above line is translated into something like this:

    let bar: u32 = <<Some unknown type> as Mul<Rhs=u32>>::mul(foo.into(), 1);
    

    compiler tries to infer <Some unknown type>, but the only thing it can infer about it, is that this is a type of expression foo.into(). So we are back to let x: _ = foo.into() problem.

    Notice that if we change order of arguments that we multiply to let bar: u32 = 1u32 * foo.into();, then compiler will produce an additional error, which further hints at the problem:

    error[E0284]: type annotations needed
      --> src/main.rs:12:31
       |
    12 |     let bar: u32 = 1u32 * foo.into();
       |                         -     ^^^^
       |                         |
       |                         type must be known at this point
       |
       = note: cannot satisfy `<u32 as Mul<_>>::Output == u32`
    help: try using a fully qualified path to specify the expected types
       |
    12 |     let bar: u32 = 1u32 * <Foo as Into<T>>::into(foo);
       |  
    

    How to fix this error?

    You must do what the compiler ask of you. Specify types, that it cannot infer. For example using fully qualified syntax:

    let bar: u32 = <Foo as Into<u32>>::into(foo) * 1u32;
    

    or by defining an intermediate variable:

    let bar: u32 = {
        let foo: u32 = foo.into();
        foo * 1
    };