W Ablout
W Ablout

Reputation: 41

Cannot return closure from function

I'm having trouble writing a trait function that returns an Fn(&bool,&bool) -> bool.

One of the functions I want to code is this:

fn foo(op: &Operation) -> impl Fn(&bool,&bool) -> bool {
    let one_of_four = match (op.2.0[0], op.2.0[1]) {
        (false, false) => |&x: &bool,&y: &bool| !x && y,
        (false, true) => |&x: &bool,&y: &bool| x && !y,
        (true, false) => |&x: &bool,&y: &bool| x != y,
        (true, true) => |&x: &bool,&y: &bool| x && y
    };
    match op.2.0[2] {
        false => |x: &bool, y: &bool| one_of_four(x, y),
        true => |x: &bool, y: &bool| !one_of_four(x, y),
    } 
} 

This part works outside of a trait:

    fn foo_part_1(op: &Operation) -> impl Fn(&bool,&bool) -> bool {
        match (op.2.0[0], op.2.0[1]) {
            (false, false) => |&x: &bool,&y: &bool| !x && y,
            (false, true) => |&x: &bool,&y: &bool| x && !y,
            (true, false) => |&x: &bool,&y: &bool| x != y,
            (true, true) => |&x: &bool,&y: &bool| x && y
        }
    }

I want to make "foo" a trait function for "OpTrait" and impl OpTrait for Negation and impl OpTrait for Operation.

However, when I put this code inside a trait, impl is not allowed. (dyn is also not allowed, because it has to be Sized.) So I change the return value to a generic:

trait OpTrait { ...
    fn foo<F>(&self) -> F where F: Fn(&bool,&bool) -> bool;
}

So far it still compiles. But when I try to implement it for Negation:

impl OpTrait for Negation {...
    fn foo<F>(&self) -> F where F: Fn(&bool,&bool) -> bool {
        |&x: &bool,&y: &bool| !x
    } 
} 

I get this error:

error[E0308]: mismatched types|
251 |      fn foo<F>(&self) -> F where F: Fn(&bool,&bool) -> bool {
expected F because of return type|
                  |this type parameter
252 |         |&x: &bool,&y: &bool| !x
expected type parameter F, found closure|
note: expected type parameter F
found closure [closure@src/...]
help: every closure has a distinct type and so could not always match the caller-chosen type of parameter F

Apparently I can't return a function (Fn closure), because the type is distinct.

After failing with Fn, I turned to fn. I was able to make progress thus:

fn negate_two(&x: &bool,&y: &bool) -> bool { 
    !x 
}...
fn not_x_and_y(x: &bool, y: &bool) -> bool { !*x && *y }
fn x_and_not_y(x: &bool, y: &bool) -> bool { *x && !*y }
fn xor(x: &bool, y: &bool) -> bool { *x != *y }
fn and(x: &bool, y: &bool) -> bool { *x && *y }

And then choosing one of these to return:

impl OpTrait for Negation { ...
    fn foo(&self) -> fn(&bool,&bool) -> bool {
        negate_two
    }
}

But I still can't invert a function as I want to do with one_of_four:

fn negate_function(f: fn(&bool,&bool) -> bool) -> fn(&bool,&bool) -> bool {
    !f
}
Error:error[E0600]: cannot apply unary operator ! to type for<'a, 'b> fn(&'a bool, &'b bool) -> bool

Is there any way to return an Fn from a trait function? Or to return an fn from another fn?

Edit: I accidentally wrote that fn foo works outside a trait, when I should've written fn foo_part_1. Corrected.

Thanks to kmdreko pointing this out and the compiler's advice, I was able to get this working:

fn foo(op: &Operation) -> Box<dyn Fn(&bool,&bool) -> bool> {
    let one_of_four = match (op.2.0[0], op.2.0[1]) {
        (false, false) => |&x: &bool,&y: &bool| !x && y,
        (false, true) => |&x: &bool,&y: &bool| x && !y,
        (true, false) => |&x: &bool,&y: &bool| x != y,
        (true, true) => |&x: &bool,&y: &bool| x && y
    };
    match op.2.0[2] {
        false => Box::new(move |x: &bool, y: &bool| one_of_four(x, y)),
        true => Box::new(move |x: &bool, y: &bool| !one_of_four(x, y)),
    } 
}

Then I put it in the trait

trait OpTrait { ...
    fn foo(&self) -> Box<dyn Fn(&bool, &bool) -> bool>
}

impl OpTrait for Operation {
    fn foo(&self) -> Box<dyn Fn(&bool, &bool) -> bool> {
        let one_of_four = 
        match (self.2.0[0], self.2.0[1]) {
            (false, false) => Box::new(|x: &bool, y: &bool| !*x && *y),
            (false, true) => Box::new(|x: &bool, y: &bool| *x && !*y),
            (true, false) => Box::new(|x: &bool, y: &bool| *x != *y),
            (true, true) => Box::new(|x: &bool, y: &bool| *x && *y),
        };
        match self.2.0[2] {
            false => move |x: &bool, y: &bool| one_of_four(x,y),
            true => move |x: &bool,y: &bool| !one_of_four(x,y)
        }
    }
} 

Unfortunately, I get the following error:

error[E0308]: `match` arms have incompatible types
    |
325 | /         match (self.2.0[0], self.2.0[1]) {
326 | |             (false, false) => Box::new(|x: &bool, y: &bool| !*x && *y),
    | |                               ----------------------------------------
    | |                               |        |
    | |                               |        the expected closure
    | |                               this is found to be of type `Box<[closure@src/...1]>`
327 | |             (false, true) => Box::new(|x: &bool, y: &bool| *x && !*y),
    | |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
328 | |             (true, false) => Box::new(|x: &bool, y: &bool| *x != *y), // aka xor
329 | |             (true, true) => Box::new(|x: &bool, y: &bool| *x && *y),
330 | |         };
    | |_________- `match` arms have incompatible types
    |
    = note: expected struct `Box<[closure@src/...]>`
               found struct `Box<[closure@src/...]>`
    = note: no two closures, even if identical, have the same type
    = help: consider boxing your closure and/or using it as a trait object

Rust playground link to check: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=15526934b939f8bfa0062fb31f193528

Upvotes: 3

Views: 131

Answers (2)

BallpointBen
BallpointBen

Reputation: 13820

Returning a Box<dyn Fn> is certainly possible, but I don't know how much that gains over having the eager foo(&Operation, bool, bool) -> bool. Just hang onto the Operation and call foo whenever you would've called the dyn Fn. This would probably read even better as a method taking &self on Operation.

struct Operation((), (), ([bool; 2],));

// or, impl Operation { fn foo(&self, x: bool, y: bool) -> bool {...} }
fn foo(op: &Operation, x: bool, y: bool) -> bool {
    let one_of_four = match (op.2.0[0], op.2.0[1]) {
        (false, false) => !x && y,
        (false, true) => x && !y,
        (true, false) => x != y,
        (true, true) => x && y,
    };

    if op.2.0[2] {
        !one_of_four
    } else {
        one_of_four
    }
}

If you really need the closure, at least you don't need it as a dyn Fn:

// this is the closure you were originally returning from foo,
// although it does capture op
let f = |x, y| foo(op, x, y);

Upvotes: 0

W Ablout
W Ablout

Reputation: 41

Answer by OP. The problem has been resolved.

impl OpTrait for Operation {
    fn foo(&self) -> Box<dyn Fn(&bool, &bool) -> bool> {
        let one_of_four = 
        match (self.2.0[0], self.2.0[1]) {
            (false, false) => |x: &bool, y: &bool| !*x && *y,
            (false, true) => |x: &bool, y: &bool| *x && !*y,
            (true, false) => |x: &bool, y: &bool| *x != *y,
            (true, true) => |x: &bool, y: &bool| *x && *y,
        };
        match self.2.0[2] {
            false => Box::new(move |x: &bool, y: &bool| one_of_four(x,y)),
            true => Box::new(move |x: &bool,y: &bool| !one_of_four(x,y)),
        }
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=064903723747cb39b20c6eae9bceedd3

Match was not allowed to assign one out of different closures to the same variable, even within Box. But a function can output a trait object (dyn). Box solved the problem.

Upvotes: 1

Related Questions