Reputation: 295
Is there an idiomatic Rust way of binding function arguments and producing a new function?
For example, suppose I had the following functions:
fn eq(a: i32) -> Box<Fn(i32) -> bool> {
let target = a; // copy
Box::new(move |value| value == target)
}
fn evaluate(func: Box<Fn(i32) -> bool>, value: i32) -> bool {
func(value)
}
Is the Box
mechanism used by eq
a sane way of binding an argument to a function for usage in evaluate
? e.g. something like this:
let is_42 = eq(42);
assert_eq!(true, evaluate(is_42, 42));
There's also the issue of lifetimes. I'm wondering what the correct semantics would be for extending the lifetime of target
in eq()
, so its lifetime is bound to the lifetime of the boxed function.
Upvotes: 4
Views: 5297
Reputation: 299730
Is there an idiomatic Rust way of binding function arguments and producing a new function?
Yes, Rust has closures which are literally functions + a bound environment.
Therefore, binding a function arguments, or partially applying it, is simply a matter of forming a closure which will invoke this function with a few fixed arguments.
Is the
Box
mechanism used byeq
a sane way of binding an argument to a function for usage inevaluate
?
It is, for now.
The problem of Rust closures is that they are Voldemort types (i.e., types that cannot be named). As a result, while you can assign a closure to a local variable, and let inference deduce the type, you cannot actually return it from a function1.
At the moment, the work around is thus to return -> Box<Fn(..) -> ..>
in this situation.
However, your signature for evaluate
is needlessly constraining. Instead of taking a Box
, you could take a reference:
fn evaluate(f: &Fn(i32) -> bool, value: i32) -> bool { f(value) }
This will let a user who can call evaluate
directly free NOT to allocate on the heap (which Box
does).
1 There is ongoing work to allow specifying a return type as -> impl SomeTrait
which would allow you to do so.
There's also the issue of lifetimes. I'm wondering what the correct semantics would be for extending the lifetime of
target
ineq()
, so its lifetime is bound to the lifetime of the boxed function.
It is not possible to extend a lifetime; a lifetime is descriptive, not prescriptive: it is merely a notation to denote the actual lifetime, not a way to specify the lifetime a value should have.
For closures, you may want to move
their environments inside the closure; taking to capture by value and not reference. It should solve most lifetime issues.
For the remaining lifetime issues, you actually go the other way around and constrain the type of the closure by denoting the lifetime of its environment: F: Fn(i32) -> bool + 'a
marks that F
is only valid for 'a
.
Upvotes: 8