Self-defined callback?

So I have something kinda like this:

struct Foo<F: Fn()> {
    pub cb: F   
}

impl <F: Fn()> Foo <F> {
    pub fn new() -> Self {
        Self {
            cb: || {}
        }
    }
}

That doesn't work... but is there any way to do something like this without boxing / trait objects?

1 Like

Well, it's complicated...

You generally can't name most types that implement the Fn* traits, so in general you can't do this (and putting a type parameter which is constrained on one of those traits is therefore I'll advised). There are, however, two types which you can name: fn() and Box<dyn Fn()>, the latter of which you've already decided against. The former seems like the better option, however it has some downsides too:

  • You can't capture anything in the closure.
  • You miss out on function inlining as is done when the type is a definite function (look up function items vs function pointers).
1 Like

You can do something like

struct Foo<F: Fn()> {
    pub cb: F,
}

impl<F: Fn()> Foo<F> {
    pub fn new() -> Foo<impl Fn()> {
        Foo { cb: || {} }
    }
}

It’s “cheating” a bit, since this adds a new method to all possible Fn<F> types while only returning a particular Foo<{type of the “|| {}” closure}> type.

I’m guessing that your working on some actual use case and that use-case might require multiple Foo structs with different cb functions to have the same type. If this is the case you might need trait objects or at least function pointers anyways.

1 Like

This will cause type-inference issues, though. But it is indeed the starting point of the only non-dyn-based solution in stable Rust until we get min_type_alias_impl_trait.

I've discussed about this very pattern (impl Fn and returning such from an impl … { fn new() constructor) in this other post:

I have also written a more general-purpose post (impl ArbitraryTrait from a function yielding an opaque type, such as -> impl Iterator…), listing all the current solutions to the problem (nightly feature, constructor with pseudo impl_Trait hidden type, dyn-erasure):

1 Like

Good point. I guess one could “cheat” even more badly and just use a concrete type different from what’s actually being returned

struct Foo<F: Fn()> {
    pub cb: F,
}

impl Foo<fn()> {
    pub fn new() -> Foo<impl Fn()> {
        Foo { cb: || {} }
    }
}

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

I admit that this is pretty bad (as in: potentially wildly confusing), though. Maybe a standalone function would be nicer.

struct Foo<F: Fn()> {
    pub cb: F,
}

pub fn default_foo() -> Foo<impl Fn()> {
    Foo { cb: || {} }
}

fn main() {
    let x = default_foo();
}
3 Likes

wow, a lot of helpful info here, thanks y'all!

By "naming" here, do you mean being able to literally type out the name in an assignment?

e.g.

fn foo() -> impl Fn() {
    || {}
}

fn main() {
    //fine
    let a = foo();
    
    //not fine
    let b: ??? = foo();
}

Yes, sometimes also referred to as an opaque type or Voldemort type.

4 Likes