In the code below, I'd like to avoid needing a short-lived heap allocation for payload_generator, but I cannot find an alternative that compiles. Option<dyn FnOnce(...)> doesn't work at all because dyn FnOnce isn't Sized. Option<&'a dyn FnOnce(...)> lets me put the closure into the builder (if I write &|v| ... in the caller of payload() and adjust payload's signature to match) but then it won't let me call the closure because it can't be moved out from behind a shared reference.
The closure in example doesn't need to be addressed as FnOnce, but in the real program this has been cut down from, some of them do.
Open to any suggestion that keeps the API of Builder essentially as is (e.g. I don't want to make payload_generator an argument to build, because in the real program it would almost always be passed as None, defeating the point of a builder struct).
struct Builder<'a> {
// other fields
// // E0277 size for values of type `...` cannot be known at compilation time
//payload_generator: Option<dyn FnOnce(&mut Vec<u8>) + 'a>,
// // E0507 cannot move out of `*gen` which is behind a shared ref (build())
//payload_generator: Option<&'a dyn FnOnce(&mut Vec<u8>)>,
payload_generator: Option<Box<dyn FnOnce(&mut Vec<u8>) + 'a>>,
}
impl<'a> Builder<'a> {
pub fn new() -> Self {
Self { payload_generator: None }
}
pub fn payload(mut self, gen: impl FnOnce(&mut Vec<u8>) + 'a) -> Self {
self.payload_generator = Some(Box::new(gen));
self
}
pub fn build(mut self) -> Vec<u8> {
match self.payload_generator.take() {
None => vec![],
Some(gen) => {
let mut v: Vec<u8> = Vec::new();
gen(&mut v);
v
}
}
}
}
pub fn example(s: &str) -> Vec<u8> {
Builder::new()
.payload(|v| v.extend_from_slice(s.as_bytes()))
.build()
}
If you closure does not have any state, you can use function pointers (i.e. fn(&mut Vec<u8>)). But if it does, your only choice is to make the builder struct generic over closure type:
Think about the closure as any other generic type. The struct field stores the closure state with its size dependent on the closure type. Technically, we could've used unsized types, but their support in Rust is rudimentary, to say the least.
Unfortunately, this code has an issue of not being able to name the "default" closure type which will be used for when the field has value of None. Manual implementation of FnOnce unfortunately is currently unstable.
Yeah, unfortunately some of the closures do have state.
your only choice is to make the builder struct generic over closure type ... issue of not being able to name the "default" closure type
I tried that too and tripped over the problem you mention, and also that it means .payload() takes a value of type Builder<F1> and returns a value of type Builder<F2> ... which, now I think about it, might actually offer a way out. Lemme go tinker a bit more...
This is a great way to make builders more flexible and statically checked. When used to add more checking (e.g. that the builder isn't missing any required items) it is called type-state.
Aha, that was the missing piece I needed. I'd gotten it working with two different concrete types, Builder and BuilderWithPayload<F>, but that was going to require me to duplicate all the unrelated methods, and then I thought of having Builder::new somehow return Builder<F> where the concrete F is a no-op function? but I was stuck on impl<F> Builder<F> requiring the caller of Builder::new to say what F is.
With a second impl block just for the new, specifying your suggested default type, this works. (I added another field to make it more realistic.)
I think I'd prefer the fn(...) solution as well, but I was curious and came up with this idea of splitting the impl without the downsides of duplicating the methods (not sure if it's fully functional though) Rust Playground: