I’ve written out this post here as well: https://www.reddit.com/r/rust/comments/3mqfqm/why_is_the_argument_type_of_the_fn_family_of/
Shouldn’t the definition of FnOnce, et al, be something more like
trait Fn {
type Input: Tuple;
type Output;
}
Then, that makes
Fn(I) -> O
sugar for
Fn<Input=(I,), Output=I>
If this discrepancy seems unnecessary and obtuse, I offer some reasoning. Parameterized traits in Rust represent possibly open relations between types. All floats are related to integral types through the Add trait implementations for floats, but f32 and f64 are also reflexively related to themselves by the very same trait! Convenient. Convertable types T to V are related by the type relation Into for T. The flexibility in this is that for any type V, I can always add a new instance of Into or Add for type T (satisfying coherence, of course). This is because relations need not be right unique! This is also convenient.
Of course this is undesirable for sets of types that relate many associated types. We wouldn’t want many different Iterator implementations for any concrete iterating struct, after all. Thus for any multiparameter traits where one or more of the types are uniquely determined by any number of the previous types, we move those types into the trait members as associated types.
What does this have to do with closures and functions? For any function-like type F, its input and output types are uniquely determined by the type F. I don’t see this changing, ever, unless Rust accepts variadic functions (which I hope it does not), so it makes no sense for F to have a parameterized input rather than an associated one.
So let’s say this change actually happened. Does anything get easier, or become possible? And why do I care about this so much?
Related to the Higher Kinded Types discussion in Rust’s community, I have been investigating using Haskell’s constraint kinds to implement certain traits in Rust. This solves a lot of problems and in my opinion it is actually imperative for Rust to adopt because there are so many trait constraints in Rust compared to Haskell. For instance, in Haskell, every type is comonoidal (Copy), but most value types in Rust are linear unless specified otherwise! So being able to let trait implementations specify their own constraints is absolutely necessary if HKTs are going to be anything more than a limited-functionality curiosity. (For an example of where this pops up and why it is important, see “The Constrained Monad Problem”.)
Now here’s the bit about functions. If you wanted to specify fn traits at the implementation level, you inevitably need the input types just to write out a valid constraint because the traits are parameterized over the input type, not associated with them into families. So I have to write “Fn<(T,)>”, because a bare Fn will not do. But what is “T”? If one is writing a trait over constructor types, i.e the very reason for wanting HKTs in the first place, then one does not have access to the input type of the function in the first place!
Okay, you say, then make the impl of the trait be generic over T. But T appears nowhere in the top of the trait definition, nor should it because it is a trait over constructors, not “filled” types. So this kind of abstraction becomes impossible while function-like Fs are parameterized over the inputs instead of being associated with them.