rusttype-synonyms

Type synonym for functions in Rust


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.


Solution

  • 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.

    trait MyType<T, V>: FnMut(T) -> (T, V) {}
    

    This declares the trait MyType with the same generic arguments, but requires that anything implementing this trait must also implement the closure trait. This means when the compiler sees something implementing MyType<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 MyType to actually be implemented on anything implementing the closure trait. This is pretty easy to do:

    impl<F, T, V> MyType<T, V> for F
    where F: FnMut(T) -> (T, V) {}
    

    So now we have a trait that:

    These are two sides of the equation that makes MyType<T, V> and FnMut(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 compose around our new trait:

    fn compose<T, U, V>(
        mut fst: impl MyType<T, U>,
        mut snd: impl MyType<U, V>,
    ) -> impl MyType<T, V> {
        move |x| {
            let (t, u) = fst(x);
            let (_, v) = snd(u);
            (t, v)
        }
    }
    

    A few important changes here:

    (Playground)