1

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?

  • 2
    I don't really understand the problem with the first approach - the caller doesn't need to explicitly provide the lifetime. – PitaJ Jan 23 '23 at 23:04
  • I don't understand, just do it https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bb8ca8d0833c52a442c77f7f08d94eb0 – Stargateur Jan 23 '23 at 23:05
  • @PitaJ The problem with the having an unbound caller controlled lifetime is that the compiler can't make any assumptions about how long it is, so it (correctly) assumes it's as long as the entire method body. But this means that you cannot both modify the buffer (a mutable borrow) and deserialize `T` (an immutable borrow for the entire method body) from the buffer while inside the method. – UserSpaceMan Jan 25 '23 at 02:31
  • Please show the code that is causing that error of mutable + immutable borrow. – Chayim Friedman Jan 25 '23 at 10:34

0 Answers0