Callable struct on stable

I'm using stable Rust. I'd like to create a value that has these properties:

  1. It can be called with normal function call syntax.
  2. Its type can be named, so you can store it in an ordinary variable without impl syntax or generics.

There are of course the Fn* traits, but they might never be stabilized. Is there any workaround for stable users?

fn main() {
    let func: Functionish;
    func();
}

// How do I define Functionish?
struct Functionish { /* ... */ }

Not that I know of. The foo(some_arg) syntax is rewritten to call the call() method from the relevant Fn trait, so unless you figure out some voodoo magic to generate a callable, nameable closure, it won't be possible.

Much fun, very UB :upside_down_face:

3 Likes

I knew there'd be something from dtolnay which addresses this!

3 Likes

There's RFC 2515 (tracking issue) but it isn't stable yet.

It seems to me you would be better off relaxing requirement 1 – you can still do everything by calling a regular method that you could do by relying on () sugar.

1 Like

I didn't think of using Deref. That makes it pretty easy. No need for all that unsafe, you can just allocate instead!

fn main() {
    let func: Functionish = make_func();
    func();
}

fn make_func() -> Functionish {
    Functionish {
        f: Box::new(|| println!("printing")),
    }
}

struct Functionish {
    f: Box<dyn Fn()>,
}

impl std::ops::Deref for Functionish {
    type Target = dyn Fn();

    fn deref(&self) -> &Self::Target {
        &self.f
    }
}
3 Likes

Here's a way of doing it without heap-allocation (unfortunately, requires nightly):

#![feature(unboxed_closures)]
#![feature(fn_traits)]

struct Foo;

impl Fn<()> for Foo {
    extern "rust-call" fn call(&self, _args: ()) {
        println!("Call (Fn) for Foo");
    }
}

impl FnMut<()> for Foo {
    extern "rust-call" fn call_mut(&mut self, _args: ()) {
        println!("Call (FnMut) for Foo");
    }
}

impl FnOnce<()> for Foo {
    type Output = ();

    extern "rust-call" fn call_once(self, _args: ()) {
        println!("Call (FnOnce) for Foo");
    }
}

fn main() {
    let x = Foo;
    x();
}

source: rust - How do I make a struct callable? - Stack Overflow

If you want to add parameters:

#![feature(unboxed_closures)]
#![feature(fn_traits)]

struct Foo;

impl FnOnce<(u32, u32)> for Foo {
    type Output = u32;
    extern "rust-call" fn call_once(self, args: (u32, u32)) -> Self::Output {
        println!("Call (Fn) for Foo {} {}", args.0, args.1);
        args.0 + args.1
    }
}

impl FnMut<(u32, u32)> for Foo {
    extern "rust-call" fn call_mut(&mut self, args: (u32, u32)) -> Self::Output {
        println!("Call (Fn) for Foo {} {}", args.0, args.1);
        args.0 + args.1
    }
}


fn main() {
    let mut x = Foo;
    let (a, b) = (100, 200);
    let out = x(a, b);
    assert_eq!(a + b, out);
}

Oh, if your struct has a field which is a closure, you can simply deref to it indeed!

mod lib {
    pub
    struct FunctionIsh<ImplFn> {
        f: ImplFn,
        /* … */
    }
    
    impl<ImplFn> ::core::ops::Deref for FunctionIsh<ImplFn> {
        type Target = ImplFn;
    
        fn deref (self: &'_ Self)
          -> &'_ Self::Target
        {
            &self.f
        }
    }
    
    #[allow(dead_code)]
    mod private { pub enum ImplFn {} }
    
    impl FunctionIsh<private::ImplFn> { // <- this could be using any type, but we use a hidden one named `ImplFn` to make it look nicer in the documentation
        pub
        fn new ()
          -> FunctionIsh<impl Fn()> // The key hack is that this ≠ Self
        {
            FunctionIsh {
                f: || println!("printing"), // No alloc!
            }
        }
    }
}

fn main ()
{
    use lib::FunctionIsh;

    let f: FunctionIsh<_> = FunctionIsh::new();
    f();
}

But obviously the cost to pay for that is that every usage of Functionish will need to be generic / of the form FunctionIsh<impl Fn()>, so in practice you won't see this pattern that much. We'd need, at the very least, the existential types feature to make this ergonomic (the good news is that that feature ought to be stabilized way faster than the Fn traits):

#![feature(type_alias_impl_trait)]
mod lib {
    pub
    struct FunctionIsh {
        f: ImplFn,
        /* … */
    }
    // where:
    pub type ImplFn = impl Fn();
    // defined by:
    fn make_function () -> ImplFn
    {
        || println!("printing")
    }

    impl FunctionIsh {
        pub
        fn new ()
          -> Self
        {
            FunctionIsh {
                f: make_function(), // No alloc!
            }
        }
    }
    
    impl ::core::ops::Deref for FunctionIsh {
        type Target = ImplFn;
    
        fn deref (self: &'_ Self)
          -> &'_ Self::Target
        {
            &self.f
        }
    }
}

fn main ()
{
    use lib::FunctionIsh;

    let f: FunctionIsh = FunctionIsh::new();
    f();
}

But neither solution allows to have a closure whose captured state can be mutated by something different than its own () call. For that, you either need the fn_traits / unboxed_closures features as @nologik showed, or you'll need to let go of the () sugar, as @H2CO3 suggested, which is way simpler and cleaner than most of these hacky-or-nightly-only patterns:

  • if you want the sugar to type less, you can have a one-long method: .c();

  • if you want the sugar to have a consistent syntax to call both closures and FunctionIsh-like entities, and/or to have a generic function accepting both kind of parameters, you can actually make closures use your own calling syntax / implement your own trait:

    trait MyFn {
        fn c (self: &'_ Self);
    }
    impl<F : Fn()> MyFn for F {
        fn c (self: &'_ Self)
        {
            self()
        }
    }
    impl MyFn for FunctionIsh { … }
    
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.