Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Not sure I agree with "relatively clean code" when one of the examples show "fn compose <T>(f1: impl Fn(T)->T, f2: impl Fn(T)->T) -> impl Fn(T)->T {" which is just a mix-match of keywords and other things, with tons of syntax embedded in just one line.

But as always, depends on where you come from. I mostly deal with lisp languages nowadays, so guessing it's just my view that the line quoted above seems complex enough to not be interested one bit in Rust.



That line is extremely simple to me. What makes it seem like a "mix-match" of symbols to you?

The equivalent in Haskell would be "compose :: (a -> a) -> (a -> a) -> a -> a", which lacks "impl" because it boxes all closures (each closure in Rust has its own type), and lacks "Fn" because it doesn't care about what kind of access the closure needs to its context (Rust distinguishes between Fn, FnMut and FnOnce). Still, the Rust is only a tiny bit more complicated than the Haskell.


Trait aliases will make some of those unwieldly declarations a bit easier on the eyes

    #![feature(trait_alias)]

    trait Map<T> = Fn(T) -> T;
    fn compose <T>(f1: impl Map<T>, f2: impl Map<T>) -> impl Map<T> {
      |n| f1(f2(n))
    }


This is a nice feature, do you know any other language that has something similar?


Scala's type aliases are pretty similar.


looks doable with C++ using :

template<typename T> using Map = std::function<T (T)>;


Perhaps you should pick your examples more carefully. That function just repeats impl Fn(T)->T three times which is exactly what yo would expect from the compose function. If there was a mix of multiple different types of functions you could demonstrate the additional complexity of Rust. What you did is just prove that Rust is the same as most languages.


    fn compose<T>(
        f1: impl Fn(T)->T,
        f2: impl Fn(T)->T
    ) -> impl Fn(T)->T
    {
        // both arguments as well as the returned value 
        // are functions which take and return the generic type T.
        // (to be precise, they implement the Fn trait).
    }
    
    let returned_function = compose(|val| val == 1, |val| val < 0);
    let value: bool = returned_function(true);


That line can be simplified to:

  fn compose<T, F: Fn(T)->T>(f1: F, f2: F) -> F {


Lambdas have unique types, and you can't use a generic parameter in a return type (which is impl Trait's raison d'etre), so I think it would have to be this:

    fn compose<T, F: Fn(T) -> T, G: Fn(T) -> T>(f: F, g: G) -> impl Fn(T) - > T
or:

    fn compose<T, F, G>(f: F, g: G) -> impl Fn(T) -> T
      where F: Fn(T) -> T, G: Fn(T) -> T


Maybe you just miscommunicated, but you absolutely can use generic parameters in a return type:

https://doc.rust-lang.org/nightly/std/iter/trait.Iterator.ht...

... and `impl Trait` exists to be able to have return types which are difficult, verbose, or impossible to name.


It's impossible to write a compose function with the declaration

  fn compose<T, F: Fn(T) -> T>(f1: F, f2: F) -> F
As far as I'm aware the only way to write a function like that is

  fn compose<T, F: Fn(T) -> T>(f1: F, f2: F) -> F {
    f1
  }
and even then you can only call it like this

  let clos = |x| 2 * x;
  compose(clos, clos);


Yes, I think he meant that it would be a bad idea to lock all of the type parameters to the same type, because all closures have different types. So you wouldn't be able to do something like this

    compose(|x| x, |x| x)
because the closures have the same type.


Because the closures don't have the same type, if I'm following you correctly.


Because the parameters demand the same type (and for the return value), but the supplied parameters are not the same type because the only way to get that is to pass in exactly the same instance of the closure.

It's a fun little thing, and a good example of why impl Trait in argument position is a really nice addition to Rust, even though based on what we were originally excited about (impl Trait in return position for returning Iterators and other such things), the argument position form didn't seem so important.


Return position impl Trait also exists so that you can change the actual return type without making a breaking change.


While that may be an extreme example I agree. nothing clean about that code. I've only just begun to dive into rust, but I feel like I'd need a concordance to navigate the meaning of that snippet alone.

Bt then again, maybe it depends on where you come from.


Takes a week or two of studying the language. It's well worth it, in my opinion.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: