serde-rs / serde

Serialization framework for Rust
https://serde.rs/
Apache License 2.0
8.81k stars 747 forks source link

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

Open ia0 opened 1 month ago

ia0 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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:

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 commented 1 month ago

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 commented 1 month ago

Have you considered bincode?

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