Trait Bound Syntax and Return Types

Is there a difference between the impl Trait syntax and trait bound syntax? According to the Rust Book, one might guess that there is no difference.

The impl Trait syntax works for straightforward cases but is actually syntax sugar for a longer form, which is called a trait bound.

However, there are cases where this seems to be an incorrect assumption - namely, when the trait bound is on a return type. For instance, consider the following code.

pub fn main() {
    println!("{:x}", f());
}

pub fn f() -> impl std::fmt::LowerHex {
    48879
}

This function compiles and runs without issue. However, when one uses trait bound syntax rather than impl Trait syntax, then issues arise.

pub fn main() {
    println!("{:x}", f());
}

pub fn f<T: std::fmt::LowerHex>() -> T {
    48879
}

Below is the output from the compiler, given the above code.

error[E0282]: type annotations needed
 --> src/main.rs:2:22
  |
2 |     println!("{:x}", f());
  |                      ^ cannot infer type for type parameter `T` declared on the function `f`

error[E0308]: mismatched types
  --> src/main.rs:12:5
   |
11 | pub fn f<T: std::fmt::LowerHex>() -> T {
   |          - this type parameter       - expected `T` because of return type
12 |     48879
   |     ^^^^^ expected type parameter `T`, found integer
   |
   = note: expected type parameter `T`
                        found type `{integer}`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

The first error can be fixed by adding a type argument, i.e. f::<i64>(), though I don't understand why it is even needed. And the second is quite confusing and I cannot find a solution except for using impl Trait syntax.

At any rate, any help at all would be greatly appreciated. And if you'd like to play with code, you can go here.

Thanks in advance!

This code

pub fn f() -> impl std::fmt::LowerHex {
    48879
}

is not equivalent to the following,

pub fn f<T: std::fmt::LowerHex>() -> T {
    48879
}

Using the impl Trait syntax in the return position means that the compiler will analyze the function body and determine the correct concrete type, and that the concrete type will comply with the given trait + lifetime constraints.

The only other way to declare f is by specifying exactly what type you are returning.

The error you get is because the caller can specify what T is, for example, it could be MyCustomLowerHexType which cannot be instantiated from 48879, Rust will check generics abstractly and that means you won't get any weird errors related to generic substitutions.

1 Like

When the impl Trait syntax was first introduced, there were a lot of arguments against allowing it in arguments precisely for this reason. When used as return value, it is called an existential type.

The difference is basically: When used as an argument, it says "Any type of this trait fits here", but when used as a return value it says "There exists a type of this trait that fits here" (this is where existential comes from)

When you try to use generics as a return value, you're saying "Any type of this trait fits here", but in your example, that's not the case: Only integers fit there.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.