2

I am trying to write a function that takes a collection of closures of type Fn() -> (), i.e. each closure takes no args, returns nothing (and I want them to be FnOnce actually, so as to move all its environment into the closure object).

I've tried many things (like using Box<Fn() -> ()> and using &'static) but I just can't get this working.

I created a gist in the Rust Playground to show what I am trying to do, approximately.

Here's the simplified code:

fn run_all_tests<I>(tests: I)
where
    I: IntoIterator<Item = Box<FnOnce() -> ()>>,
{
}

fn main() {
    let examples = [1, 2, 3];

    run_all_tests(examples.iter().map(
        |ex| Box::new(move |ex| assert!(ex > 0)),
    ));
}

The error:

error[E0271]: type mismatch resolving `<[closure@src/main.rs:11:9: 11:49] as std::ops::FnOnce<(&{integer},)>>::Output == std::boxed::Box<std::ops::FnOnce() + 'static>`
  --> src/main.rs:10:5
   |
10 |     run_all_tests(examples.iter().map(
   |     ^^^^^^^^^^^^^ expected closure, found trait std::ops::FnOnce
   |
   = note: expected type `std::boxed::Box<[closure@src/main.rs:11:23: 11:48]>`
              found type `std::boxed::Box<std::ops::FnOnce() + 'static>`
   = note: required because of the requirements on the impl of `std::iter::Iterator` for `std::iter::Map<std::slice::Iter<'_, {integer}>, [closure@src/main.rs:11:9: 11:49]>`
   = note: required by `run_all_tests`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Renato
  • 12,940
  • 3
  • 54
  • 85

1 Answers1

2

There are several issues with the code:

  1. Your boxed closure takes a parameter ex, but the trait FnOnce() takes no parameters. The parameter ex also shadows the parameter ex from the outer closure, so I assume you meant the inner closure to take no parameters: move || assert!(ex > 0).

  2. Type mismatch in ex > 0 because of comparing reference to non-reference. Can be fixed by dereferencing the outer closure parameter during pattern matching: |&ex| ....

  3. Type inference isn't strong enough to discover that the iterator returned by map should be over Box<FnOnce()> rather than Box<unique closure object>. You can add an explicit cast to fix this: Box::new(move || assert!(ex > 0)) as Box<FnOnce()>

  4. At this point, the code will compile, but you will get a compilation error when you add a call to the boxed FnOnce() due to a language limitation. See "cannot move a value of type FnOnce" when moving a boxed function. On nightly Rust, you can change FnOnce to FnBox. Otherwise, you can use FnMut instead or use one of the workarounds from that question. There's another workaround based on defining an extra trait given in the Rust book (see section between listing 20-20 and listing 20-21).

Here is the fixed code using FnBox:

#![feature(fnbox)]
use std::boxed::FnBox;

fn run_all_tests<I>(tests: I)
where
    I: IntoIterator<Item = Box<FnBox()>>,
{
    for t in tests {
        t();
    }
}

fn main() {
    let examples = [1, 2, 3];

    run_all_tests(examples.iter().map(|&ex| {
        Box::new(move || assert!(ex > 0)) as Box<FnBox()>
    }));
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
interjay
  • 107,303
  • 21
  • 270
  • 254