APIT and Turbofish
Summary
Introduction
impl Trait
in argument position introduces a new generic parameter to the function:
#![allow(unused)] fn main() { fn collect_to_vec<T>(iter: impl Iterator<Item = T>) -> Vec<T> { iter.collect() } // is roughly equivalent to: fn collect_to_vec_desugared<T, I>(iter: I) -> Vec<T> where I: Iterator<Item = T>, { iter.collect() } }
With the desugared version, users can write an expression that manually specifies the values for its type parameters:
#![allow(unused)] fn main() { let f = collect_to_vec_desugared::<u32, vec::IntoIter<u32>>; }
The question addressed here is whether it should be possible to write an equivalent expression for collect_to_vec
.
Current behavior
The compiler today prevents the use of turbofish for any function with APIT. This is a future compatibility restriction and not a long term plan.
Observations
Users should not have to acknowledge impl Trait during turbofish, that'd be confusing
Today, when using turbofish form, we require that users write a value for all generic type parameters, even if that value is _
(we allow lifetime parameters to be elided if the user chooses, and in fact require them to be elided in some cases). Therefore, it would be an error to write collect_to_vec_desugared::<u32>(vec.into_iter())
. It is allowed to write collect_to_vec_desugared::<u32, _>(vec.into_iter())
, however, and thus to lean on type inference for one parameter.
While APIT is "roughly equivalent" to a new generic parameter, this parameter is not listed explicitly in the list of generics. Therefore, it would likely be surprising for users to get an error for not giving it a value. This argues that collect_to_vec::<u32>(vec.into_iter())
should be accepted.
If we wish to allow turbofish to specify the value for an impl Trait, we would need to linearize the list
Furthermore, if we wished to extend impl Trait
parameters into the list of type parameters, we would have to come up with an ordering. Presumably it would be "left to right" as they are written within the arguments.
Some type parameters are given explicit values more often than others and Rust doesn't let one make that distinction
It sometimes happens that functions have some type parameters which are useful (even mandatory) to specify, and others which are not. This usually happens when some type parameters appear in the argument list, and hence can be inferred from the call arguments, but others either do not appear anywhere or appear only in the return type. Example:
#![allow(unused)] fn main() { fn log_the_things<D: DebugLog>(strings: impl IntoIterator<Item = String>) { for s in strings { D::log_str(s); } } }
Here, it would be common for users to wish to write something like log_the_things::<SomeLoggerType>(some_vector)
, where the type D
is manually specified but the value of the impl IntoIterator
is inferred from the argument. Without using impl Trait
, this would have to be written log_the_things::<SomeLoggerType, _>(...)
.
You sometimes want to get a function type without calling it
In all the examples so far, we have assumed that it was possible to infer the value of an impl Trait
argument from the arguments that were supplied. But that might not always be true. Consider this rather artificial example:
#![allow(unused)] fn main() { struct Foo<T> { t: T } fn test_fn(x: impl Iterator) { } fn make() -> Foo<impl Debug> { Foo { x: test_fn } } }
Here, we are not calling test_fn
, just taking its value.