Why does the compiler not infer the concrete type of an associated type of an impl trait return value?

I have a trait with an associated type:

pub trait Speak {
    type Error;
    fn speak(&self) -> Result<String, Self::Error>;
}

An implementation of that trait:

#[derive(Default)]
pub struct Dog;

impl Speak for Dog {
    type Error = ();
    fn speak(&self) -> Result<String, Self::Error> {
        Ok("woof".to_string())
    }
}

And a function returning an instance of that implementation:

pub fn speaker() -> impl Speak {
    Dog::default()
}

I know that in this example I could just use Dog as the return type, but in my actual code I have to use impl Speak instead (the above function is in fact generated by a macro).

As I understand it, the impl Trait notation lets the compiler figure out which concrete type is actually returned, so I would expect the following function to compile correctly because speaker() returns a Dog and that Dog::Error is the type ():

fn test() -> Result<String, ()> {
    speaker().speak()
}

playground

Instead, I get the following error:

error[E0308]: mismatched types
  --> src/lib.rs:21:5
   |
20 | fn test() -> Result<String, ()> {
   |              ------------------ expected `std::result::Result<std::string::String, ()>` because of return type
21 |     speaker().speak()
   |     ^^^^^^^^^^^^^^^^^ expected (), found associated type
   |
   = note: expected type `std::result::Result<_, ()>`
              found type `std::result::Result<_, <impl Speak as Speak>::Error>`

It is as if the compiler could not (at this point) infer the return type of the speaker function.

Who is missing something something, the compiler or myself?

2 answers

  • answered 2018-10-16 08:55 DK.

    You are.

    You never specified the associated Error type, so you're not allowed to assume anything about it. Even if it really is (), the compiler won't allow you to use that knowledge. To solve this, just specify what Error is:

    pub fn speaker() -> impl Speak<Error = ()> {
        Dog::default()
    }
    

  • answered 2018-10-16 08:57 Sebastian Redl

    Use -> impl Speak<Error = ()> as the return type of speaker().

    The problem is that the compiler needs, from the signature alone, enough information that the caller can actually use the function. If you just return impl Speak, then the compiler knows that speak() returns a Result<String, ???> - the error type isn't known, and thus the compiler issues an error.

    The compiler cannot infer anything here. It cannot infer the error type from the call site, because impl Trait in return position doesn't allow inference from the call site. It cannot infer the error type from the implementation, because that would mean whether the caller type-checks depends on the implementation, and that's not how impl Trait works. The caller must always type-check in presence of only the information of the signature; the concrete type is only plugged in after that.