1

I have an interface that I want to make callable like function. I don't need this to be templated on Args and Output like the Fn trait, since I want to define what types the args and return type will be. When I attempt this my code doesn't compile:

#![allow(unused)]
#![feature(unboxed_closures, fn_traits)]

type ActionArgs = (i32, bool);

pub trait Action: Fn(ActionArgs) -> bool {
    extern "rust-call" fn call(&self, args: ActionArgs) -> bool;
}

struct Concrete {}

impl Action for Concrete {
    extern "rust-call" fn call(&self, args: ActionArgs) -> bool {
        args.0  == 2 && args.1
    }
}

fn main() {
    let c = Concrete{};
    c((2, true));
}

This results in (on rust playground with nightly compiler):

error[E0277]: expected a `Fn<((i32, bool),)>` closure, found `Concrete`
  --> src/main.rs:12:6
   |
6  | pub trait Action: Fn(ActionArgs) -> bool {
   |                   ---------------------- required by this bound in `Action`
...
12 | impl Action for Concrete {
   |      ^^^^^^ expected an `Fn<((i32, bool),)>` closure, found `Concrete`
   |
   = help: the trait `Fn<((i32, bool),)>` is not implemented for `Concrete`

error: aborting due to previous error

I have tried following other questions such as this, but with no success.

matanmarkind
  • 219
  • 3
  • 13

1 Answers1

3

It looks like you're misunderstanding the trait Trait: OtherTrait syntax. It doesn't mean "Trait extends OtherTrait". It means "Trait requires OtherTrait to be implemented".

So, this line:

pub trait Action: Fn(ActionArgs) -> bool {
    //...
}

basically means: "for every type implementing Action, compiler, please check that it will implement Fn(ActionArgs) -> bool, too". That's it - only the check.

Furthermore, to use something as a function, you must implement at least FnOnce - otherwise the function call syntax would be simply unavailable. This is not possible to do using any other traits, because Fn* traits are so-called lang items - this basically means that they get special treatment from the compiler, in particular, by allowing otherwise inaccessible syntax elements.

So, the only thing you can do here is to switch from Action to using Fn* traits directly - possibly leaving Action as a method-less, marker trait, like this:

#![allow(unused)]
#![feature(unboxed_closures, fn_traits)]

type ActionArgs = (i32, bool);

// Since we can't deconstruct the tuple in "sugary" syntax,
// we have to fall back to ordinary generics to use ActionArgs.
pub trait Action: Fn<ActionArgs, Output = bool> {}

struct Concrete {}

// These two implementations delegate to the Fn one.
// Of course, they might also be completely separate, if you like.
impl FnOnce<ActionArgs> for Concrete {
    type Output = bool;
    extern "rust-call" fn call_once(self, args: ActionArgs) -> bool {
        self.call(args)
    }
}
impl FnMut<ActionArgs> for Concrete {
    extern "rust-call" fn call_mut(&mut self, args: ActionArgs) -> bool {
        self.call(args)
    }
}

// This implementation is what you used in Action before.
impl Fn<ActionArgs> for Concrete {
    extern "rust-call" fn call(&self, args: ActionArgs) -> bool {
        args.0  == 2 && args.1
    }
}

// Finally, this blanket implementation makes Action available
// on every function-like type with correct signature...
impl<T: Fn<ActionArgs, Output = bool>> Action for T {}
// ...so that we can use it as a trait bound, like here:
fn act(c: impl Action) {
    println!("{}", c(2, true));
}

fn main() {
    let c = Concrete{};
    act(c);
}

Playground

Cerberus
  • 8,879
  • 1
  • 25
  • 40
  • Thanks a lot for that explanation. Is there any way to have a simple trait that just requires operator()? It is trivial for me to say trait has fn_name(args), so can I do this with operator() without implementing the full Fn interface here? – matanmarkind Oct 31 '20 at 15:42
  • In Rust, every operator corresponds to some trait. So, this "simple trait" you're speaking of is the combination of `Fn*` traits anyway. Of course, you can implement only `FnOnce` and not `FnMut` or `Fn`, if this is acceptable - for example, if your callable types are `Copy`. – Cerberus Oct 31 '20 at 17:38