Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Serialize and Deserialize for core::convert::Infaillible #2740

Open
ia0 opened this issue May 13, 2024 · 4 comments
Open

Implement Serialize and Deserialize for core::convert::Infaillible #2740

ia0 opened this issue May 13, 2024 · 4 comments

Comments

@ia0
Copy link

ia0 commented May 13, 2024

In generic context (where there is a type parameter T: Serialize + Deserialize<'a>), it may sometimes happen that one wants to instantiate with Infaillible (or some nested occurrence of Infaillible). It would be convenient if serde would provide such implementations.

There is a simple work-around when the user doesn't need to use Infaillible directly and can use their own Impossible type:

#[derive(Serialize, Deserialize)]
pub enum Impossible {}

But this feels more like a work-around than a proper solution. In my opinion, a proper solution would be for serde to support core::convert::Infaillible, as it does other standard types.

Would this be something that could be supported?

@bitdivine
Copy link

Interesting. Could you provide a small toy example to illustrate the problem? (Note: I am not a maintainer but write a lot of tests that use serde. At the moment I can't imagine a realistic use case. A toy example would potentially open my eyes to a range of possibilities I hadn't considered before.)

@ia0
Copy link
Author

ia0 commented May 22, 2024

I'm implementing an RPC protocol from a host to a device using an top-level enum whose discriminant indicates the RPC function:

enum Api<'a, T: Direction> {
    Error(T::Type<'a, Error>),
    PlatformVersion(T::Type<'a, PlatformVersion>),
    RebootPlatform(T::Type<'a, RebootPlatform>),
    UpdatePlatform<T::Type<'a, UpdatePlatform>),
    InstallApplet<T::Type<'a, InstallApplet>),
    // etc... but always Foo<T::Type<'a, Foo>)
}

This API is parametrized by the "direction" which is either Request (host to device) or Response (device to host):

pub trait Direction {
    type Type<'a, T: Service>: Wire<'a>; // think of Wire<'a> as Serialize + Deserialize<'a>
}
pub trait Service {
    type Request<'a>: Wire<'a>;
    type Response<'a>: Wire<'a>;
}
pub enum Request {}
impl Direction for Request {
    type Type<'a, T: Service> = T::Request<'a>;
}
pub enum Response {}
impl Direction for Response {
    type Type<'a, T: Service> = T::Response<'a>;
}

The Error variant is special, in the sense that the host should never send such request and the device is always allowed to reply with this variant instead of the same variant as the request, to indicate an error. To avoid mistakes, the Error service is implemented using Infallible for the request:

pub enum DeviceError {}
impl Service for DeviceError {
    type Request<'a> = Infallible;
    type Response<'a> = Error;
}

The code is still in progress. I'm still experimenting with it and in particular I decided to stop using serde and use my own Wire<'a> trait for the following reasons:

  • I want a compact and canonical format (like postcard, except for the canonical part).
  • I want a simple data model (no field/variant names, no special treatment for options, maps, etc).
  • I want to inspect the schema of a type during testing (to make sure it is always backward compatible).
  • I want to control the wire lifetime (wire types are views into wire data thus covariant, and not data types, see Why "The 'de lifetime should not appear in the type to which the Deserialize impl applies"? #2190).
  • I want to control the variant tags (some variants may have #[cfg(feature = "host")] to deprecate them, keeping new devices small, but letting the host talk with old devices).

So I probably won't need this issue to be fixed for this particular problem, but I thought it might still be useful in general (i.e. anytime there is a generic enum, one of its variant may occasionally be Infallible to disable it).

@bitdivine
Copy link

Interesting. Thank you for the details.

I want a compact and canonical format (like postcard, except for the canonical part).

Have you considered bincode? It is a postcard-like format used by e.g. google/tarpc

@ia0
Copy link
Author

ia0 commented May 22, 2024

Have you considered bincode?

No I did not. Thanks for the link! From what I can see it has the same issues as serde:

  • You can't choose the tag for enum variants, which means you can't have a stable format when feature-gating variants on one side of the wire (in my case, I want the device to only deserialize what it supports, while the host must be able to communicate with all devices and thus deserialize every variant).
  • There is no programmatic schema support for testing (making sure the API is monotonous on host and "stable" on device, i.e. variants can be added or removed but not modified, and removed is for life).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants