rustyhu
rustyhu

Reputation: 2147

How to implement a multi-level currying function in Rust?

I attempt to implement a currying function similar to Functional Programming Jargon in Rust:

fn add_origin(x: i32) -> impl Fn(i32) -> i32 {
    return move |y| {
        x + y
    };
}

fn main() {
    let add5 = add_origin(5);
    println!("Call closure: {}", add5(6));
}

This works, but if I add one level deeper:

fn add(x: i32) -> impl Fn(i32) -> impl Fn(i32) -> i32 {
    return move |y: i32| {
        return move |z: i32| {
            x + y + z
        }
    };
}

fn main() {
    let add5 = add(5);
    let add5_10 = add5(10);
    println!("Call closure: {}", add5_10(6));
}

Compiler does not accept and tells me:

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/main.rs:7:35
  |
7 | fn add(x: i32) -> impl Fn(i32) -> impl Fn(i32) -> i32 {
  |                                   ^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

Why is this not allowed? Is there a better alternative in Rust?

Upvotes: 9

Views: 4150

Answers (2)

talz
talz

Reputation: 1200

There is now a solution for this, using the nightly feature type_alias_impl_trait, as written in this nice post (that then goes on to automate currying in a proc macro): https://peppe.rs/posts/auto-currying_rust_functions/#refinement

Adapting the method from the post to the code from the question we get:

#![feature(type_alias_impl_trait)]  // allows us to use `impl Fn` in type aliases!

type T0 = i32;                 // the return value when zero args are to be applied
type T1 = impl Fn(i32) -> T0;  // the return value when one arg is to be applied
type T2 = impl Fn(i32) -> T1;  // the return value when two args are to be applied


fn add(x: i32) -> T2 {
    return move |y: i32| {
        return move |z: i32| {
            x + y + z
        }
    };
}

fn main() {
    let add5 = add(5);
    let add5_10 = add5(10);
    println!("Call closure: {}", add5_10(6));
}

You can play with this code in the playground

Upvotes: 1

pretzelhammer
pretzelhammer

Reputation: 15085

impl Trait syntax can only be used in argument position or return position of a function signature. Both of these are fine:

fn takes_fn(fn_arg: impl Fn(i32) -> i32) {}

fn returns_fn() -> impl Fn(i32) -> i32 {
  |x| x
}

However neither of these work:

fn takes_fn(fn_arg: impl Fn(i32) -> impl Fn(i32) -> i32) {}

fn returns_fn() -> impl Fn(i32) -> impl Fn(i32) -> i32 {
  |x| x
}

Because the second nested impl Trait is no longer in argument or return position but is now part of the argument or return type's signature, which is not somewhere where it is allowed. Basically, multiple nested impl Traits will never work regardless of where you place them and you'll always get the same compile error.

The good news is this doesn't stop you from accomplishing what you want to accomplish because impl Trait is just syntax sugar and its use is optional. In your particular example we can use a boxed trait object to return your curried function.

fn add(x: i32) -> impl Fn(i32) -> Box<dyn Fn(i32) -> i32> {
    move |y: i32| {
        Box::new(move |z: i32| {
            x + y + z
        })
    }
}

fn main() {
    let add5 = add(5);
    let add5_10 = add5(10);
    println!("Call closure: {}", add5_10(6)); // prints "Call closure: 21"
}

playground

See also:

Upvotes: 9

Related Questions