I want to write an i32
-returning function that accepts a closure taking zero arguments, a closure taking one argument, and a closure taking two arguments, where all closure arguments are of type i32
and every closure returns f32
.
What is that function's signature going to look like?
I want to accept these via the Fn
and FnMut
traits. What does the signature look like? Is the use of features in the crate required? If so, which ones and why?
If known: what does it look like sugared? Desugared?
If known: what is likely to be changed in the future?
In Rust, closures and functions aren't interchangeable. In fact, even 2 closures with the same type signature aren't interchangeable! So we can't use fn (u8) -> bool as the type of the parameter to call_function, because our closure isn't compatible with it.
If you use FnOnce as a trait bound, that is most permissive - your callers can call it with any type of closure possible in Rust. But it might consume non-copy types.
You can use Fn instead of FnMut (and remove mut before f1, f2 and f3) if you want to force the caller to pass closures which don't mutate their environment, but in general, I think, you would want to use FnMut. This code uses unboxed closure sugar and overloaded calls.
As you can see, all this function does is call its argument with a fixed value, and return the result. Here's how we might use it: Given a pre-defined a function that does some arbitrary calculation, passing it into our first function above is trivial. You can see the complete example in the Rust playground here, and even run it in your browser!
I want to write an
i32
-returning function that accepts a closure taking zero arguments, a closure taking one argument, and a closure taking two arguments, where all closure arguments are of typei32
and every closure returnsf32
.What is that function's signature going to look like?
It looks like this:
fn closures<F1, F2, F3>(mut f1: F1, mut f2: F2, mut f3: F3) -> i32
where
F1: FnMut() -> f32,
F2: FnMut(i32) -> f32,
F3: FnMut(i32, i32) -> f32,
{
(f1() + f2(10) + f3(20, 30)) as i32
}
fn main() {
let x = closures(|| 0.1, |x| (2 * x) as f32, |x, y| (x + y) as f32);
println!("{}", x);
}
You can use Fn
instead of FnMut
(and remove mut
before f1
, f2
and f3
) if you want to force the caller to pass closures which don't mutate their environment, but in general, I think, you would want to use FnMut
.
This code uses unboxed closure sugar and overloaded calls. Without them, it would look like this:
#![feature(unboxed_closures, fn_traits)]
fn closures<F1, F2, F3>(mut f1: F1, mut f2: F2, mut f3: F3) -> i32
where
F1: FnMut<(), Output = f32>,
F2: FnMut<(i32,), Output = f32>,
F3: FnMut<(i32, i32), Output = f32>,
{
(f1.call_mut(()) + f2.call_mut((10,)) + f3.call_mut((20, 30))) as i32
}
fn main() {
let x = closures(|| 0.1, |x| (2 * x) as f32, |x, y| (x + y) as f32);
println!("{}", x);
}
The sugar is used to prettify closure type syntax, and the overloaded calls feature allows to omit explicit call_*
methods.
Editor's note This question was asked before Rust 1.0, and this section only applies to the changes that happened between then and 1.0.
As for what's going to change in future, then it is likely that closure construction syntax would be simplified (when current closures are dropped), so the main()
bit will change from this:
fn main() {
let x = closures(
|&mut:| 0.1,
|&mut: x: int| (2*x) as f32,
|&mut: x: int, y: int| (x + y) as f32
);
println!("{}", x);
}
to look like this:
fn main() {
let x = closures(
|| 0.1,
|x| (2*x) as f32,
|x, y| (x + y) as f32
);
println!("{}", x);
}
The actual type of the closure (FnMut
, Fn
or FnOnce
) is going to be inferred.
There will also be other changes, like the move
keyword for closures which are returned from functions (move
affects variable capturing semantics). This is covered by this accepted RFC.
In general, unboxed closures are outlined in this RFC. It is not updated, however, with new closures sugar syntax and with other subtle changes; it may be better to follow Rust issue tracker to find out more on this. For example, a lot of issues with unboxed closures are aggregated in this bug.
Fn
, FnMut
and FnOnce
are the three trait types that were introduced with unboxed closures. The difference between these traits, besides the name of their single method, is that the self
parameter on these methods is passed differently:
Fn
: &self
(by reference, can't mutate the closure's environment)FnMut
: &mut self
(by reference, can mutate the closure's environment)FnOnce
: self
(by value, consumes the closure, so the closure cannot be called more than once)These traits have one type parameter, Args
, which is a tuple type that represents the closure's parameters (or ()
if the closure takes no parameters). FnOnce
has the associated type Result
, which is the closure's return type. Fn
is a subtrait of FnMut
, and FnMut
is a subtrait of FnOnce
, which means that Fn
and FnMut
"inherit" Result
from FnOnce
. Unboxed closures automatically implement the applicable traits.
fn foo<F: Fn() -> f32>(closure: F) -> i32 {
0
}
fn foo<F: Fn(i32) -> f32>(closure: F) -> i32 {
0
}
fn foo<F: Fn(i32, i32) -> f32>(closure: F) -> i32 {
0
}
where
clauseEach of these can also use the where
syntax:
fn foo<F>(closure: F) -> i32
where
F: Fn() -> f32,
{
0
}
See also:
impl trait
syntax:fn foo_impl(closure: impl Fn() -> f32) -> i32 {
0
}
See also:
This format is unstable, and each of these examples requires using the feature gate #![feature(unboxed_closures)]
. You can also use the where
or impl trait
syntax.
See also:
fn foo<F: Fn<(), Output = f32>>(closure: F) -> i32 {
0
}
fn foo<F: Fn<(i32,), Output = f32>>(closure: F) -> i32 {
0
}
fn foo<F: Fn<(i32, i32), Output = f32>>(closure: F) -> i32 {
0
}
"Boxed" closures existed at the time this question was asked, but they were removed before Rust 1.0.
This metabug tracked the development of unboxed closures.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With