Skip to content

Improvement of FromRubyMarshal ergonomics #1

@Nnubes256

Description

@Nnubes256

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 RubyType be bound to 'de: 'deser, 'deser is not without merit
  • On the other hand, the internal facilities (such as RubyArrayIter, RubyMapIter that 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) self to 'deser, which makes a lot of design space just impossible to explore; for example, Drop implementations.

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. A RawRubyType can be upgraded to a RubyType by 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 parent RubyArrayIter.
  • Its Drop implementation would involve calling Deserializer#next_raw_element in 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 Drop implementation for RubyArrayIter/RubyMapIter that safely skips the iterator on drop would be possible.
  • You could go so far as to "make the Frame built-in" into the returned RubyType (probably through a wrapper and some Deref/DerefMut magic), and therefore get rid of let 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions