I take it from the title that you don't want -> Box<dyn ReturnThat>?
-> impl Trait is an opaque alias around some other type. It doesn't let you return more than one type.
This doesn't work either:
fn return_static_dispatch_long_syntax<T>(fruit: bool) -> T
where
T: ReturnThat,
{
if fruit { Orange { .. } } else { Tomato { ... } }
}
The caller chooses what generics like T resolves to, not the function body. Inside the function body, T represents a single type that meets the bounds. And you still can't return more than one type. You have to return whatever T the caller chose.
If you want to return either a Tomato or an Orange without type erasure (dyn ReturnThat), you could use an enum.
I understand the error. What is surprising to me is that using generic T and impl ReturnThat are two different things - in other words it is not just a different syntax.
@quinedot's answer is very good, but I would like to extend it a little bit.
This is called "enum dispatch", and there is a popular crate that abstracts a lot of boilerplate.
Actually returning impl ReturnThat might still be a good idea even in this case. It all depends on what semver guarantees do you want to give to your caller. When public function returns concrete type, you cannot change it without major release. In above code snippet SomeReturnThat enum was marked as #[non_exhaustive], which gives you some flexibility (you can add new variants), but you cannot ever remove variants or change it to a dynamic dispatch. Type erasure allows it. And with recently stabilised associated type bounds feature, expressing complex bounds is very easy.
Unfortunately the meanting of impl Trait is context-specific. In almost all contexts where it's supported or planned to be supported, it has the "opaque type alias" meaning that -> ReturnThat has.
The exceptional context is in an argument to a function:
fn foo(thing: impl Display) { ... }
That is like using a generic T: Display on foo.[1]
except not as useful due to not being nameable, turbofishable, or compatible with precise capturing âŠī¸
impl Trait has many meanings depending on its position (which I admit, can be confusing). TL;DR is that fn foo(_: impl Trait) is mostly similar to fn foo<T: Trait>(_: T) (other that you cannot name this type using fully qualified syntax), but fn foo() -> impl Trait means "function foo returns some unnameable type that implements trait Trait".
There is a great talk by John Gjengset explaining all current (and future) meanings of this feature, and differences between them - https://www.youtube.com/watch?v=CWiz_RtA1Hw.
Thank you both @quinedot and @akrauze , I didn't even realize that impl can be used as an argument. I'll watch the video today but at this moment generics look like more useful / something to use more often.
Just to clarify. impl Trait in return position is still useful and often used. They just express different things. But if you are talking about function argument position, then I agree. I personally never use impl Trait in this context, because it is strictly less flexible than introducing a named generic type. If you want to deny it in your codebase, there is a clippy lint clippy::impl_trait_in_params, that will force it.