Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the Fn traits (closures) in Rust function signatures?

Tags:

rust

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?

like image 431
user Avatar asked Oct 26 '14 19:10

user


People also ask

Why can't I call a function from a closure in rust?

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.

What is the most permissive way to use fnonce in rust?

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.

When to use FN instead of fnmut?

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.

What does a fixed value function do in rust?

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!


2 Answers

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?

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.

Before Rust 1.0

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.

like image 163
Vladimir Matveev Avatar answered Oct 11 '22 22:10

Vladimir Matveev


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.

Sugared form

Closure taking zero arguments

fn foo<F: Fn() -> f32>(closure: F) -> i32 {
    0
}

Closure taking one argument

fn foo<F: Fn(i32) -> f32>(closure: F) -> i32 {
    0
}

Closure taking two arguments

fn foo<F: Fn(i32, i32) -> f32>(closure: F) -> i32 {
    0
}

Using a where clause

Each of these can also use the where syntax:

fn foo<F>(closure: F) -> i32
where
    F: Fn() -> f32,
{
    0
}

See also:

  • What's the difference between `<T: Trait>` and `where T: Trait`?

Using the impl trait syntax:

fn foo_impl(closure: impl Fn() -> f32) -> i32 {
    0
}

See also:

  • What are the differences between an impl trait argument and generic function parameter?
  • What does `impl` mean when used as the argument type or return type of a function?
  • What makes `impl Trait` as an argument "universal" and as a return value "existential"?

Desugared versions

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:

  • How do I implement the Fn trait for one struct for different types of arguments?

Closure taking zero arguments

fn foo<F: Fn<(), Output = f32>>(closure: F) -> i32 {
    0
}

Closure taking one argument

fn foo<F: Fn<(i32,), Output = f32>>(closure: F) -> i32 {
    0
}

Closure taking two arguments

fn foo<F: Fn<(i32, i32), Output = f32>>(closure: F) -> i32 {
    0
}

Old "boxed" closures

"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.

like image 21
Francis Gagné Avatar answered Oct 11 '22 21:10

Francis Gagné