I'm trying to design an API that enables zero-copy processing of deserialized data. My plan is to pass a callback into the struct that's actually doing the buffering data from the network then pass the deserialized struct containing data borrowed from the buffer to the callback. My first attempt resulted in a method signature like the following:
fn next<'de, T, R, F>(&mut self, callback: F) -> R
where
F: FnOnce(T) -> R,
T: Deserialize<'de>;
The problem with this is that the lifetime 'de
is unbound and caller controlled. This means that I can't both modify the buffer and deserialize from it inside of this method. I can hide the lifetime from the caller using a Higher Rank Trait Bound:
fn next<T, R, F>(&mut self, callback: F) -> R
where
F: FnOnce(T) -> R,
T: for<'de> Deserialize<'de>;
but that's not what I want either, because for<'de> Deserialize<'de>
is equivalent to DeserializedOwned
, so structs which borrow from the buffer won't work. I settled on using the recently stabilized Generic Associated Types feature to define a new trait:
trait Callback {
type Arg<'de>: Deserialize<'de>;
type Return;
fn call<'de>(self, arg: Self::Arg<'de>) -> Self::Return;
}
Then I was able to write the next
method using a simple trait bound:
fn next<F: Callback>(&mut self, callback: F) -> F::Return
This works, but it's not super ergonomic for the consumers of this API. I would like for them to be able to pass a closure into this method. So my question is this: Is it possible to achieve the same result using only the standard Fn*
traits?