I’m a really big fan of impl Trait
, especially in argument position. A lot of times there’s a type parameter for a function you really just don’t ever need anywhere else, and being able to avoid adding another parameter that is always inferred is great!
I propose adding some definition-time inference to impl Trait
in argument positon. Consider the following
code:
struct Complex<T: Num>(T, T);
fn consume<T: Num>(z: Complex<T>) { .. }
With impl Trait
we can get rid of T
and simplify this to
fn consume(z: Complex<impl Num>) { .. }
I propose allowing the following even simpler form (actual syntax TBD):
fn consume(z: Complex<impl>) { .. }
Using impl
without a bound after it in argument type position will infer the bound to be the weakest possible. In this case, it looks at Complex<?0>
and realizes that ?0: Num
, so it desugars to impl Num
. Formally, impl
in the type parameter or associated type position of a type constructor T
desugars to impl Bound
, whre Bound
is the sum of all of there where
clauses of T
involving that type parameter or assoc type. In code,
struct K<.., T, ..> where T: B1, T: B2, .. { .. }
fn foo(_: K<.., impl, ..>) { .. }
// desug
fn foo(_: K<.., impl Sized + B1 + B2 + .., ..>) { .. }
In particular, fn foo(_: impl)
desugars to fn foo(_: impl Sized)
. If the parameter is T: ?Sized
, we get
fn foo(_: &impl) { .. }
// desug
fn foo(_: &impl ?Sized) { .. }
as expected.
This is useful for avoiding repeating the same bounds all over the place for a particular struct
which are easily understood from context. @Centril’s parameterized modules come to mind here:
mod<Db: Database + Serialize + 'static> { struct K<T> { .. } }
fn frob(k: K<i32, impl>) { .. }
// instead of
fn frob(k: K<i32, impl Database + Serialize + 'static>) { ..}
This doesn’t introduce any particularly wild inference; looking up what impl
infers is as easy as looking up the type it’s used in.
The form fn foo(_: impl)
is also useful for when you want to take ownership of something but ignore its value.
In the precense of const
generics, this becomes even more powerful, since const
parameters usually involve a long <..>
clause. For example,
const fn last<T, const n: usize>(xs: [T; n]) -> T { xs[n - 1] }
// becomes the following, recalling that [_; _]::len can be made `const`
const fn last<T>(xs: [T; impl]) -> T { xs[xs.len() - 1] }
While I think I am one of the more obnoxious “enforce readability with an iron fist” people, I think this is actually a readability win. Anywhere you can use impl
, the implied bounds are a single code jump away! I could also imagine this is useful for by-example macros, which might not want to repeat a bunch of user-provided bounds all over the place.
I don’t feel like bikeshedding about syntax quite yet, but I’ll mention this anyway. An alternative syntax is any
, to reflect that this represents the “most universal type” available in the given position. This, one might write
fn foo(z: Complex<any>) { .. }
I think it’s more illustrative of what I’m proposing, but I don’t think it’s worth arguing about yet.