Reputation: 41
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
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
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)),
}
}
}
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