Serde: How to write a generic type bounds for deserialize trait?

Created on 22 Jun 2017  路  3Comments  路  Source: serde-rs/serde

I want to declare a generic struct with de-serialization but I don't know how to correctly require a Deserialize trait to be implemented for a generic type.

#[derive(Debug, Clone, Deserialize)]
pub struct Response<ResponseResult: Deserialize> {
    /// some fields ...
    pub result: ResponseResult,
}

Gives the error:

error[E0106]: missing lifetime specifier
  --> src/model.rs:43:37
   |
43 | pub struct Response<ResponseResult: Deserialize> {
   |                                     ^^^^^^^^^^^^^^ expected lifetime parameter

I understand that Deserialize trait requires a lifetime but I don't know how to specialize it right here. Is there a way?

support

Most helpful comment

If someone else ends up here like I did, where contained types have more complicated bounds for their De/Serialize implementation, the answer is given here: https://serde.rs/attr-bound.html

Example [play]:

extern crate serde;
#[macro_use]
extern crate serde_derive;

use serde::ser::Serialize;
use serde::de::Deserialize;

use std::collections::HashSet;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash};

#[derive(Serialize, Deserialize)]
pub struct MySet<T, S = RandomState>(
    #[serde(bound(serialize = "HashSet<T, S>: Serialize",
                  deserialize = "HashSet<T, S>: Deserialize<'de>"))]
    HashSet<T, S>,
);

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize)]
pub struct MySet_<T: Eq + Hash, S: BuildHasher = RandomState>(
    #[serde(bound(deserialize = "HashSet<T, S>: Deserialize<'de>"))]
    HashSet<T, S>,
);

There is no such behavior currently expressible for regular #[derive]s, so the bounds for HashSet's basic trait impls have to be added to the struct's. I would argue that this is good design as these bounds are required for the type to be of any use, but others disagree.

All 3 comments

In general, you should not write trait bounds on structs. See https://github.com/Manishearth/rust-clippy/issues/1689.

The solution here is not writing a trait bound. You haven't written one for Debug or Clone, so don't write one for Deserialize either.

#[derive(Debug, Clone, Deserialize)]
pub struct Response<ResponseResult> {
    /// some fields ...
    pub result: ResponseResult,
}

This will expand to something like:

impl<'de, ResponseResult> Deserialize<'de> for Response<ResponseResult>
    where ResponseResult: Deserialize<'de>
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        /* ... */
    }
}

@dtolnay Okay! But, I am just being curious now, is there a way to do that with rust syntax at all?

If someone else ends up here like I did, where contained types have more complicated bounds for their De/Serialize implementation, the answer is given here: https://serde.rs/attr-bound.html

Example [play]:

extern crate serde;
#[macro_use]
extern crate serde_derive;

use serde::ser::Serialize;
use serde::de::Deserialize;

use std::collections::HashSet;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash};

#[derive(Serialize, Deserialize)]
pub struct MySet<T, S = RandomState>(
    #[serde(bound(serialize = "HashSet<T, S>: Serialize",
                  deserialize = "HashSet<T, S>: Deserialize<'de>"))]
    HashSet<T, S>,
);

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize)]
pub struct MySet_<T: Eq + Hash, S: BuildHasher = RandomState>(
    #[serde(bound(deserialize = "HashSet<T, S>: Deserialize<'de>"))]
    HashSet<T, S>,
);

There is no such behavior currently expressible for regular #[derive]s, so the bounds for HashSet's basic trait impls have to be added to the struct's. I would argue that this is good design as these bounds are required for the type to be of any use, but others disagree.

Was this page helpful?
0 / 5 - 0 ratings