Is there a good reason why we can't store a move closure in a fn pointer?

example:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5b4e1ddd8f244e7037f2ef11ee8178c7

I would like to take a predicate in a trait method. To keep it object safe, I don't want to use generics. I would also like to avoid using unnecessary boxing. Thus a function pointer seems really awesome.

However currently it's not at all possible to store a closure that captures some environment in a function pointer. I can completely see why we can't borrow data, but why does this not work for a closure that moves something in?

I wonder if it's just not implemented but possible, or can it not reasonably be done? It doesn't even work with things that are Copy, like a bool. Nor with zero sized types. There is basically no way to get information in other than accessing static globals.

If I where to stick with the function pointer, users really cannot make a predicate based on any outside information, which does seem a bit to restrictive.

I feel it can be worked around for example by making the predicate an enum with variants for a boxed closure and a fn pointer. That way people don't need to pay for the boxing if they don't use it, but it's pretty convoluted. The nice thing about closures is that their syntax is concise.

Ok, I see one problem with moved in data. If the closure is called several times, should it get a pristine version of the data every time or should it be able to mutate it over several iterations?

A function pointer is just that: A pointer. It only contains a pointer, and no more. A closure that moves a variable needs to store that variable somewhere, but there is no space in a simple function pointer.

This is why the Fn traits exist: They are implemented on closures that contain more than just a pointer, allowing use of closures with moved data.

To allow usage of the Fn traits without loosing object safety of your trait, you can use a &dyn reference like this.

1 Like

If you only call the predicate once, you should use the FnOnce trait, as it allows passing closures that are only valid to call once.

Thanks for the response. Could I store a bunch of different predicates passed in as &dyn in a vector, without running into lifetime problems and without heap allocations? That would solve my issue. Turns out I will need lifetime parameters to store them. I shall give it a try with static.

I just wonder, couldn't the moved in data be considered like local variables on the stack of the closure?

Well if you put them in a vector, that is a heap allocation, because that's how vectors work. You can use a static array to avoid the heap allocation like this. You don't need lifetimes unless you're trying to store the closures somewhere that outlives the body of the function you passed them to.

A closure doesn't have a stack until you call it. Every time you create a closure, it implicitly creates a struct with a field for every value you move into the closure and implements the appropriate Fn traits for that struct. Even if you use a non-move closures, it still needs to store a reference to the variable it uses, so it is still not a raw function.

Well, yeah, I'm fine with allocating one vector, but not with an allocation per predicate. I just haven't used &dyn references much before, so I'm not entirely familiar with how it's implemented, that's why I asked. I will read up about them some more though.

I'm experimenting with a static bound on the closures, since I keep them stored in self, so yeah, they have to outlive the caller.

A &dyn reference is just like any other reference. If you take a reference to something on the stack, it's a reference to something on the stack. The special thing about them is that it's a fat pointer, so the pointer also contains some information about the actual type behind the reference, which is how the compiler is able to call whatever it points to without knowing the concrete type. This extra information also does not require a heap allocation.

Note that you will not be able to use a static lifetime unless the closure is stored in a global. If you wish to take ownership of the closures, you will have to use a Box, which does cost an allocation per closure.

Yes, if it really just a reference, it seems that this won't solve my issue. I kind of need to take ownership of it, or use a function pointer, hence the question...

I will look into using an enum with a boxed variant. Hope my api doesn't become to bloated.

Yes that is pretty much the only option that avoids a heap allocation if you want to take ownership without allocating.

This is the final result in pharos 0.3:

I'm pretty happy with the result. The enum is an indirection, but it's not to bad.

2 Likes

This is an article I passed by recently as to why closures are as they are:

In essence, a closure is a struct of who's fields are the closed over data. The struct implements a trait that makes it callable. This is why every single closure instance is unique and often very differently sized.