I would like to have type synonyms for functions, so that they are less cluttersome. For instance, I would like something like this:
type MyType<T, V> = FnMut(T) -> (T, V);
fn compose<T, U, V>(fst: MyType<T, U>, snd: MyType<U, V>) -> MyType<T, V> {
|mut& x| {
let (t, u) = fst(x);
let (_, v) = snd(u);
(t, v)
}
}
But it fails to compile. I can add dyn keywords but type aliases cannot be used as traits.
Something like this works in Haskell:
type MyType a b = a -> (a, b)
compose :: MyType a b -> MyType b c -> MyType a c
compose f g = \x ->
let (a, b) = f x
(_, c) = g b
in (a, c)
A less-toy-example use-case: The synonym needs to use FnMut, as I am trying to make synonyms based off nom's Parser.
Peter Hall's answer does a good job of explaining the differences in the type systems between Rust and Haskell. He is much more knowledgeable about that, so I will point to his answer for any explanations about that. Instead, I want to give you a practical way that you can accomplish what you want within Rust.
One of the things that's very different about Rust compared to other common languages is that traits in Rust can be implemented on pretty much any type, including types your crate doesn't define. This allows you to declare a trait and then implement it on anything else, in contrast to languages where you can only implement interfaces on types that you are defining. This gives you an incredibly large amount of freedom, and it takes some time to fully grasp the potential this freedom grants you.
So, while you can't create aliases for traits, you can create your own trait that is automatically implemented on anything that implements the other trait. Semantically this is different, but it winds up being close enough to the same thing that you can use it like an alias in most cases.
This declares the trait
MyTypewith the same generic arguments, but requires that anything implementing this trait must also implement the closure trait. This means when the compiler sees something implementingMyType<T, V>, it knows it also implements the closure supertrait. This is important so that you can actually invoke the function.That's half of the solution, but now we need
MyTypeto actually be implemented on anything implementing the closure trait. This is pretty easy to do:So now we have a trait that:
These are two sides of the equation that makes
MyType<T, V>andFnMut(T) -> (T, V)effectively equivalent, even if they aren't actually the same trait within the type system. They aren't the same trait, but you can use them almost interchangeably.Now, we can adjust the definition of
composearound our new trait:A few important changes here:
impl MyType<_, _>so that the function can receive anything implementing your trait, which includes the closure types you're trying to target. Note there is nodynwhich also means there is no dynamic dispatch. This removes a level of indirection.impl MyType<_, _>which means we can return a closure without boxing it, which both prevents an unnecessary heap allocation as well as an unnecessary level of indirection.composeas well as calls to the closure it returns, which can make this abstraction "free" in terms of runtime performance!fstandsndto bemutin order to invoke the functions because the underlying closure type isFnMut.moveto the closure so that the closure takes ownership offstandsnd, otherwise it would try to borrow function local variables in the return value, which cannot work.(Playground)