Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass a boxed closure to `take_while`?

The iterator method take_while takes as its argument a closure.

For example:

fn main() {
    let s = "hello!";
    let iter = s.chars();
    let s2 = iter.take_while(|x| *x != 'o').collect::<String>();
    //                       ^^^^^^^^^^^^^
    //                          closure

    println!("{}", s2);   // hell
}

playground link

This is fine for simple closures, but if I want a more complicated predicate, I don't want to write it directly in the take_while argument. Rather, I would like to return the closure from a function.

I seem to be having trouble getting this to work. Here is my naive attempt:

fn clos(a: char) -> Box<Fn(char) -> bool> {
    Box::new(move |b| a != b)
}

fn main() {
    // println!("{}", clos('a')('b'));   // <-- true
    //                      ^--- Using the closure here is fine
    let s = "hello!";
    let mut iter = s.chars();
    let s2 = iter.take_while( clos('o') ).collect::<String>();
    //                           ^--- This causes lots of issues

    println!("{}", s2);
}

playground link

However, the error that it causes has proven difficult to understand:

error[E0277]: the trait bound `for<'r> Box<std::ops::Fn(char) -> bool>: std::ops::FnMut<(&'r char,)>` is not satisfied
  --> <anon>:11:23
   |
11 |         let s2 = iter.take_while( clos('o') ).collect::<String>();
   |                       ^^^^^^^^^^ trait `for<'r> Box<std::ops::Fn(char) -> bool>: std::ops::FnMut<(&'r char,)>` not satisfied

error[E0277]: the trait bound `for<'r> Box<std::ops::Fn(char) -> bool>: std::ops::FnOnce<(&'r char,)>` is not satisfied
  --> <anon>:11:23
   |
11 |         let s2 = iter.take_while( clos('o') ).collect::<String>();
   |                       ^^^^^^^^^^ trait `for<'r> Box<std::ops::Fn(char) -> bool>: std::ops::FnOnce<(&'r char,)>` not satisfied
   |
   = help: the following implementations were found:
   = help:   <Box<std::boxed::FnBox<A, Output=R> + 'a> as std::ops::FnOnce<A>>
   = help:   <Box<std::boxed::FnBox<A, Output=R> + Send + 'a> as std::ops::FnOnce<A>>

error: no method named `collect` found for type `std::iter::TakeWhile<std::str::Chars<'_>, Box<std::ops::Fn(char) -> bool>>` in the current scope
  --> <anon>:11:47
   |
11 |         let s2 = iter.take_while( clos('o') ).collect::<String>();
   |                                               ^^^^^^^
   |
   = note: the method `collect` exists but the following trait bounds were not satisfied: `Box<std::ops::Fn(char) -> bool> : std::ops::FnMut<(&char,)>`, `std::iter::TakeWhile<std::str::Chars<'_>, Box<std::ops::Fn(char) -> bool>> : std::iter::Iterator`

error: aborting due to 3 previous errors

I have tried some other things, including using FnBox, but it didn't work. I haven't used closures that much, so I'd really like to understand what is going wrong, as well as how to fix it.

Related

like image 610
Liam Avatar asked Sep 19 '16 00:09

Liam


1 Answers

There are two issues in your code.

First, take_while passes the value by reference to the function (notice the & in where P: FnMut(&Self::Item) -> bool), whereas your closure expects to receive it by value.

fn clos(a: char) -> Box<Fn(&char) -> bool> {
    Box::new(move |&b| a != b)
}

Then there's the issue that Box<Fn(&char) -> bool> does not implement FnMut(&char) -> bool. If we look at the documentation for FnMut, we'll see that the standard library provides these implementations:

impl<'a, A, F> FnMut<A> for &'a F where F: Fn<A> + ?Sized
impl<'a, A, F> FnMut<A> for &'a mut F where F: FnMut<A> + ?Sized

OK, so FnMut is implemented for references to implementations of Fn. We have an Fn trait object in our hands, and it implements Fn, so that's fine. We just need to turn the Box<Fn> into a &Fn. We first need to dereference the box, which produces an lvalue, then take a reference to this lvalue to produce a &Fn.

fn main() {
    let s = "hello!";
    let iter = s.chars();
    let s2 = iter.take_while(&*clos('o')).collect::<String>();
    println!("{}", s2);
}
like image 132
Francis Gagné Avatar answered Nov 25 '22 13:11

Francis Gagné