-
Notifications
You must be signed in to change notification settings - Fork 0
Description
So I think I've had an idea to make possible having the deserializer be built-in into RubyType that sidesteps the double mutable borrow issues I had with it (see https://discord.com/channels/273534239310479360/1058530213585236019/1058758259428839425 on the Rust community Discord server).
Really the problem with the approach I took before is two-fold:
- On one hand, there's value in providing an ergonomic API to deserialize elements taking advantage of lifetimes, so having the
RubyTypebe bound to'de: 'deser,'deseris not without merit - On the other hand, the internal facilities (such as
RubyArrayIter,RubyMapIterthat use it (by having a&'deser mut Deserializer<'de>field) cannot really use any function that returnsRubyType<'de, 'deser>without binding the lifetime of& (mut) selfto'deser, which makes a lot of design space just impossible to explore; for example,Dropimplementations.
However, this week I thought about it and I think there's a way to solve that particular problem. For this to work, I think you need both a RubyType<'de, 'deser> and a RubyType<'de> we will call the latter "RawRubyType<'de>".
RubyType<'de, 'deser>is what end-users use. There is no footguns here, and this would be the most ergonomic way to perform deserialization.RawRubyType<'de>is what internal facilities use. There are some footguns to keep in mind when using this, but it allows the most fine control. ARawRubyTypecan be upgraded to aRubyTypeby providing a deserializer.
What this implies is that you have two ways of getting the next element to deserialize:
-
fn next_element<'deser>(&'deser mut self) -> RubyType<'de, 'deser>
-
fn next_raw_element(&mut self) -> RawRubyType<'de>
And implementations can mix and match depending on what they do. Consider RubyArrayIter for example:
- The public API to advance to the next element would be a call to
Deserializer#next_element, because here it's acceptable to bind the lifetime of the returned element to its parentRubyArrayIter. - Its
Dropimplementation would involve callingDeserializer#next_raw_elementin a loop until the iterator is exhausted. I mean, you aren't really passing those elements up to the end-user; in fact the whole point is to drop them!
I think that with this I'd be able to solve all footguns the library has so far:
- Having a
Dropimplementation forRubyArrayIter/RubyMapIterthat safely skips the iterator on drop would be possible. - You could go so far as to "make the
Framebuilt-in" into the returnedRubyType(probably through a wrapper and someDeref/DerefMutmagic), and therefore get rid oflet mut deserializer = deserializer.prepare()?entirely, which right now I believe is a footgun because for it to work correctly you really want to call it every time you take the next element.