Function returning generic type with trait bound vs. impl Trait

Hi everyone,

While learning about the impl Trait notation, created a small example, where a function returns a type that must implement a certain trait. I implemented two functions, one using generics and one using impl Trait.
For reasons I don't yet understand, only the function using impl Trait works.

Consider this simple trait:

trait Walk {
    fn walking(&self);
}

Lets implement the trait for the Human struct:

struct Human;

impl Walk for Human {
    fn walking(&self) {
        println!("Walking...");
    }
}

Here are the two function definitions:

// compiler error:  expected type parameter `T`, found struct `Human`
//fn foo<T: Walk>() -> T {
//    Human {}
//}

fn bar() -> impl Walk {
    Human {}
}

fn main() {
    //let walker = foo();
    let walker = bar();
    walker.walking();
}

Why does bar() work but foo() doesn't?
Aren't we providing the compiler with the same information in both cases?

Thanks!

When you have a generic type parameter like foo, the caller gets to choose the type (within any stated bounds, like : Walk), and the function is universal over all those types. It's an error in your code because you always return a Human, but a user of the function might call

let fido = foo::<Dog>();

That is, your function signature claimed you could return any type than can Walk, but you only actually return Human.


Things are somewhat confusing because what impl Trait means varies based on where you use it. When you use it in argument position:

fn quz(_: impl Walk) {}

It is basically the same as the generics discussed above:

fn quz<T: Walk>(_: T) {}

(except you lose the ability to name the type).

In return position, it means something different: the function returns some singular type that meets the stated bounds. In this case, the writer of the function chooses the type, and it's not generic -- there can only be one type. So this is an error for example:

// Does not compile: error[E0308]: `match` arms have incompatible types
fn zip(b: bool) -> impl Walk {
    match b {
        false => Human,
        true  => Dog,
    }
}

The returned type is opaque (and unnameable) to users of the function, but they can utilize whatever trait bounds are specified (like Walk).

1 Like

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.