Prana
Prana

Reputation: 703

Is there a shorter way of writing function signatures that take and return closures?

Is there a shorter way of writing these function signatures?

fn hof_five(a: i32, func: impl Fn(i32) -> i32) -> impl Fn(i32) -> i32 {
    move |v| func(a + v)
}

fn hof_six(a: i32, func: Box<dyn Fn(i32) -> i32>) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |v| func(a + v))
}

Something like using F: impl Fn(i32) -> i32 or F: Box<dyn Fn(i32) -> i32>

So that the signature becomes something like:

fn hof_five<F>(a: i32, func: F) -> F {
    move |v| func(a + v)
}

Upvotes: 1

Views: 110

Answers (2)

Sven Marnach
Sven Marnach

Reputation: 601529

The two examples you gave are quite different.

fn hof_five(a: i32, func: impl Fn(i32) -> i32) -> impl Fn(i32) -> i32 {
    move |v| func(a + v)
}

This function accepts a closure of a type implementing Fn(i32) -> i32, and returns a closure of a type implementing this trait, but the argument and return types are different types. The argument type is inferred by the compiler based on the closure you pass in, and the return type is inferred based on the closure you return. Each closure has its own individual type.

Since argument type and return type are different types, there is no way to refer to them by the same name. The only thing you could do is define a trait that requires Fn(i32) -> i32 as a prerequisite:

trait MyFn: Fn(i32) -> i32 {}

impl<T> MyFn for T
where
    T: Fn(i32) -> i32,
{}

With this trait definition, you can rewrite hof_five as

fn hof_five(a: i32, func: impl MyFn) -> impl MyFn {
    move |v| func(a + v)
}

The blanket implementation of MyFn makes sure that all closures implementing Fn(i32) -> i32 automatically also implement MyFn.

fn hof_six(a: i32, func: Box<dyn Fn(i32) -> i32>) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |v| func(a + v))
}

This function accepts a pointer pointing to a closure implementing Fn(i32) -> i32, but the concrete type of that closure is not known at compile time. If you actually call the closure, the concrete type of the closure is determined at runtime, and the generated code dynamically dispatches to the right function. In this case, argument type and return type are the same type, so you can use a type alias if you want to:

type MyBoxedFn = Box<dyn Fn(i32) -> i32>;

fn hof_six(a: i32, func: MyBoxedFn) -> MyBoxedFn {
    Box::new(move |v| func(a + v))
}

This is completely equivalent to the original version.

Upvotes: 2

Shepmaster
Shepmaster

Reputation: 430634

Not usefully, no, there is not.

To make something shorter, you generally have to reduce redundancy or remove characters.

  • There's no semantic redundancy because the argument and return type mean different things.
  • impl and fn and i32 are already abbreviated, so they can't have characters removed.

All that's left is textual similarities. You can use a macro to do that, but I don't think it's a good idea:

macro_rules! F {
    () => (impl Fn(i32) -> i32);
}

fn hof_five(a: i32, func: F!()) -> F!() {
    move |v| func(a + v)
}

See also:


You might also be interested in using where clauses to spread out the complexity:

fn hof_five<F>(a: i32, func: F) -> impl Fn(i32) -> i32
where
    F: Fn(i32) -> i32,
{
    move |v| func(a + v)
}

Upvotes: 1

Related Questions