matanmarkind
matanmarkind

Reputation: 247

Create a struct which implements a specific Fn interface

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.

Upvotes: 1

Views: 898

Answers (1)

Cerberus
Cerberus

Reputation: 10218

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

Upvotes: 3

Related Questions