serde
serde copied to clipboard
Implement Serialize and Deserialize for core::convert::Infaillible
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?
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.)
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 #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).
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
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).
Just ran into this when writing a Rocket server with an API where successful requests return JSON of the form {"Ok": ..} and errors, handled by a different function, return JSON of the form {"Err": ..}
The signatures are as follows:
fn clean(bulk_job: Json<BulkJob>) -> Json<Result<CleaningSuccess, ()>> {..}
fn clean_error(status: Status, _request: &Request) -> Json<Result<(), CleaningError>> {..}
Naturally I'd like to use Infallible instead of () given the respective functions never return the other variants.
My main concern is the eventual type Infallibe = !; update would make then-old versions of Serde implementing the traits for Infallibe and ! suddenly not compile. Is there any known way around that or is this blocked on type Infallible = !;?
Facing this too. Serialize part only.
Suppose I just put this in mod.rs
impl Serialize for Unfailable {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {}
}
}
Would this be acceptable? Would it solve the serialize half of people's problems?
(It would solve mine.)