Reputation: 623
I have a closure, which I construct:
let r = receiver.clone();
let is_channel_empty = move || -> bool { r.is_empty() };
and then inject via:
Adapter::new(is_channel_empty)
The Adapter looks like this:
type ChannelEmptyFn = dyn Fn() -> bool + Send + Sync;
// for Box: type ChannelEmptyFn = Box<dyn Fn() -> bool + Send + Sync + 'static>;
pub struct Adapter {
is_channel_empty: ChannelEmptyFn,
}
impl Adapter {
pub fn new(is_channel_empty: ChannelEmptyFn) -> Self
{
Self { is_channel_empty }
}
}
I'm getting the obvious, size not known at runtime error.
If I pass a reference, it needs a lifetime and 'static isn't going to work.
If I pass a Box, it gets heap allocated, and while I don't like heap allocations, is an easy solution.
Is there another alternative? I can't pass an fn
(function pointer) as the closure has a captured environment. Is there a way to specify a field type as a closure? other than Fn
, FnMut
, or FnOnce
? I'm just wondering if I'm missing something in my limited knowledge of Rust.
One last thing, the Adapter eventually is wrapped in an Arc, ya there's some irony here, which is the other reason I'd like to have it all allocated in a single block.
Upvotes: 1
Views: 238
Reputation: 145
Each closure is its own anonymous type that implements a particular closure trait. This means each closure is implemented differently from all others, and you cannot expect a single size or method. To abstract over this, we have two choices: generics, or trait objects.
Generics are compile-time, while trait objects are dynamic (run-time), which incurs a necessary run-time cost, made explicit by Rust.
The thing is, generics are basically just making a bunch of different types based on the generic one, which means a struct with a generic closure is not the same type as the same struct containing a different closure, even if the two closures have the same closure trait, because the closures have different types.
If you don’t want to exchange one Adapter for another, then generics are perfect for you.
pub struct Adapter<F: Fn() -> bool + Send + Sync> {
is_channel_empty: F,
}
impl<F: Fn() -> bool + Send + Sync> Adapter<F> {
pub fn new(is_channel_empty: F) -> Self
{
Self { is_channel_empty }
}
}
If you want to be able to exchange the closure or the Adapter for another one at run-time, you need to go dynamic, like you have now.
You have to keep the closure behind a layer of indirection, so that its size can be safely exchanged around. You can go the reference route, but as you said, you need a lifetime, and that will not work with Arc, because Arc needs to own its content (static lifetime). The only way to own something of unknown size is a box, because the heap is dynamic. If you go dynamic, you often must commit all the way. There is no way around heap allocation if that is what is needed. Heap allocation is not evil, it is another tool in your toolbox, that does not incur much cost in most closure cases as it only needs to allocate for the dynamic dispatch table and the captured variables, if any.
From the Rust documentation on Arc:
The type Arc provides shared ownership of a value of type T, allocated in the heap.
Upvotes: 2