59

Note: This question was asked before Rust's first stable release. There have been lots of changes since and the syntax used in the function is not even valid anymore. Still, Shepmaster's answer is excellent and makes this question worth keeping.


Finally unboxed closures have landed, so I am experimenting with them to see what you can do.

I have this simple function:

fn make_adder(a: int, b: int) -> || -> int {
    || a + b
}

However, I get a missing lifetime specifier [E0106] error. I have tried to fix this by changing the return type to ||: 'static -> int, but then I get another error cannot infer an appropriate lifetime due to conflicting requirements.

If I understand correctly, the closure is unboxed so it owns a and b. It seems very strange to me that it needs a lifetime. How can I fix this?

aochagavia
  • 5,887
  • 5
  • 34
  • 53

4 Answers4

72

As of Rust 1.26, you can use impl trait:

fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
    move |b| a + b
}

fn main() {
    println!("{}", make_adder(1)(2));
}

This allows returning an unboxed closure even though it is impossible to specify the exact type of the closure.

This will not help you if any of these are true:

  1. You are targeting Rust before this version

  2. You have any kind of conditional in your function:

    fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
        if a > 0 {
            move |b| a + b
        } else {
            move |b| a - b
        }
    }
    

    Here, there isn't a single return type; each closure has a unique, un-namable type.

  3. You need to be able to name the returned type for any reason:

    struct Example<F>(F);
    
    fn make_it() -> Example<impl Fn()> {
        Example(|| println!("Hello"))
    }
    
    fn main() {
        let unnamed_type_ok = make_it();
        let named_type_bad: /* No valid type here */ = make_it();
    }
    

    You cannot (yet) use impl SomeTrait as a variable type.

In these cases, you need to use indirection. The common solution is a trait object, as described in the other answer.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
31

It is possible to return closures inside Boxes, that is, as trait objects implementing certain trait:

fn make_adder(a: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |b| a + b)
}

fn main() {
    println!("{}", make_adder(1)(2));
}

(try it here)

There is also an RFC (its tracking issue) on adding unboxed abstract return types which would allow returning closures by value, without boxes, but this RFC was postponed. According to discussion in that RFC, it seems that some work is done on it recently, so it is possible that unboxed abstract return types will be available relatively soon.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Is the boxing still going to be necessary with the new `move` keyword? Doesn't that make the closure independent of the stack frame, and its size known at compile time? – Renato Zannon Oct 03 '14 at 05:20
  • 1
    @RenatoZannon, the problem with returning unboxed closures is not their dependency on the stack frame (they already *are* independent unless they capture local references) or unknown size. Because they're anonymous structs implementing certain traits, there is just no way to specify their type in return position without boxing. – Vladimir Matveev Oct 03 '14 at 05:25
  • 1
    Oh, I see, so it's a type system/syntax issue. Thanks! – Renato Zannon Oct 03 '14 at 05:34
  • The linked code sample (http://is.gd/d2DANn) as of the time of this comment reports errors about conflicting lifetime requirements, and I'm unsure how to interpret the error. (preemption to those unfamiliar with play-rust: reproducing the error can be done by going to the link and simply clicking 'evaluate') – user Oct 26 '14 at 22:28
  • @user, this is known problem, there is a bug on that on Rust issue tracker, but I can't provide a link now, I'm on mobile phone. – Vladimir Matveev Oct 26 '14 at 22:30
  • @VladimirMatveev Just found it after commenting: https://github.com/rust-lang/rust/issues/11740 – user Oct 26 '14 at 22:31
  • @VladimirMatveev I just updated your first code example, please double check that it still conveys your intention. – Shepmaster Dec 29 '14 at 04:03
  • << very noob, just trying my first lines of Rust: When I add a semi colon to the `Box::new` line, where the closure is returned, the compiler suddenly says: `error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]` - What difference does the semi colon make? – Zelphir Kaltstahl Apr 30 '17 at 00:25
  • 1
    @Zelphir, this is a consequence of that Rust is an expression-based language. You can find more [here](https://doc.rust-lang.org/book/functions.html#expressions-vs-statements). – Vladimir Matveev May 02 '17 at 07:56
7

The || syntax is still the old boxed closures, so this doesn't work for the same reason it didn't previously.

And, it won't work even using the correct boxed closure syntax |&:| -> int, since it is literally is just sugar for certain traits. At the moment, the sugar syntax is |X: args...| -> ret, where the X can be &, &mut or nothing, corresponding to the Fn, FnMut, FnOnce traits, you can also write Fn<(args...), ret> etc. for the non-sugared form. The sugar is likely to be changing (possibly something like Fn(args...) -> ret).

Each unboxed closure has a unique, unnameable type generated internally by the compiler: the only way to talk about unboxed closures is via generics and trait bounds. In particular, writing

fn make_adder(a: int, b: int) -> |&:| -> int {
    |&:| a + b
}

is like writing

fn make_adder(a: int, b: int) -> Fn<(), int> {
    |&:| a + b
}

i.e. saying that make_adder returns an unboxed trait value; which doesn't make much sense at the moment. The first thing to try would be

fn make_adder<F: Fn<(), int>>(a: int, b: int) -> F {
    |&:| a + b
}

but this is saying that make_adder is returning any F that the caller chooses, while we want to say it returns some fixed (but "hidden") type. This required abstract return types, which says, basically, "the return value implements this trait" while still being unboxed and statically resolved. In the language of that (temporarily closed) RFC,

fn make_adder(a: int, b: int) -> impl Fn<(), int> {
    |&:| a + b
}

Or with the closure sugar.

(Another minor point: I'm not 100% sure about unboxed closures, but the old closures certainly still capture things by-reference which is another thing that sinks the code as proposed in the issue. This is being rectified in #16610.)

huon
  • 94,605
  • 21
  • 231
  • 225
2

Here's how to implement a closure based counter:

fn counter() -> impl FnMut() -> i32 {
    let mut value = 0;

    move || -> i32 {
        value += 1;
        return value;
    }
}

fn main() {
    let mut incre = counter();
    println!("Count 1: {}", incre());
    println!("Count 2: {}", incre());
}
Arunoda Susiripala
  • 2,516
  • 1
  • 25
  • 30