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

Deserialize lifetime in generic context #890

Closed
jimmycuadra opened this issue Apr 21, 2017 · 6 comments
Closed

Deserialize lifetime in generic context #890

jimmycuadra opened this issue Apr 21, 2017 · 6 comments
Labels

Comments

@jimmycuadra
Copy link
Contributor

I'm working on upgrading a library to Serde 1.0 and came to a situation I'm not sure how best to solve. There is a type that previously looked like this:

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StrippedStateContent<C> where C: Deserialize + Serialize {
    pub content: C,
}

Now that Deserialize has a lifetime, it's unclear how to declare the lifetime parameter. The lifetimes chapter in the docs suggests not to include the 'de lifetime as part of the target type, but it only shows an example of how to avoid it when implementing Deserialize for a type directly, rather than a generic type within another, like in my example above.

In my case, there is no borrowed data in C, so it seems wrong to introduce a lifetime parameter into the signature of StrippedStateContent. Just to see how it would go, I tried this:

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StrippedStateContent<'de, C> where C: Deserialize<'de> + Serialize {
    pub content: C,
}

But that results in three copies of this error in the compiler output:

error[E0263]: lifetime name `'de` declared twice in the same scope
   |
67 | #[derive(Clone, Debug, Deserialize, Serialize)]
   |                        ^^^^^^^^^^^
   |                        |
   |                        previous declaration here
   |                        declared twice

Renaming the lifetime variable (e.g. to 'a) gets around this, but of course now StrippedStateContent must always be written with a lifetime parameter even though it doesn't borrow anything.

What's the intended approach here? Thanks!

@lawliet89
Copy link

lawliet89 commented Apr 21, 2017

I have the same question as you. See how I got around it in #891, although I'm not sure if that's the right way.

To add on, the three copies of the error are due to the automatically derived code. If you use cargo expand you can see where they come from.

@jimmycuadra
Copy link
Contributor Author

Thanks, @lawliet89! I tried using an HRTB, but ran into:

error[E0283]: type annotations required: cannot resolve `C: serde::Deserialize<'de>`
   |
67 | #[derive(Clone, Debug, Deserialize, Serialize)]
   |                        ^^^^^^^^^^^
   |
   = note: required by `serde::Deserialize`

Adding #[serde(bound(deserialize = ""))] fixed that, but that feels way too magical/hacky for my taste, so I'm hoping there's another approach. Or perhaps this is a case that needs to be addressed in the next Serde release. It's certainly not ergonomic, or even clear, as it stands.

@lawliet89
Copy link

I agree. Adding the bound fixed it because the code gen automatically adds the bound mentioned in the error message for you. Only reason I realised I had to use the attribute was from digging around the output from cargo expand.

@dtolnay
Copy link
Member

dtolnay commented Apr 21, 2017

Just get rid of the bound?

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StrippedStateContent<C> {
    pub content: C,
}

@dtolnay
Copy link
Member

dtolnay commented Apr 21, 2017

In general, trait bounds should go on behavior and not on data structures.

impl<T> Jimmy<T> where T: MyTrait { /* behavior, ok */ }

fn jimmy<T>() where T: MyTrait { /* behavior, also ok */ }

struct Jimmy<T> where T: MyTrait { /* data, in general try not to do this */ }

The one exception is if a trait bound is necessary on the data structure because it refers to associated types.

enum Cow<'a, B> where B: 'a + ToOwned + ?Sized {
    Borrowed(&'a B),
    Owned(B::Owned), // <- needs the associated type of ToOwned
}

@dtolnay
Copy link
Member

dtolnay commented Apr 22, 2017

Thanks for raising this issue!

  1. I added a section called Trait bounds to the website explaining the two main ways to write Deserialize trait bounds.

  2. I opened a Clippy issue https://github.com/Manishearth/rust-clippy/issues/1689 to warn about trait bounds on structs like StrippedStateContent. (Trait bounds in general, not just Serde traits. This is never what you want.)

  3. I opened Better error if a struct defines its own 'de lifetime #893 to follow up on the "lifetime declared twice" error.

@dtolnay dtolnay closed this as completed Apr 22, 2017
andrewjstone added a commit to andrewjstone/rabble that referenced this issue May 9, 2017
Derive `Serialize` and `Deserialize` for messages instead of
`RustcEncodable` and `RustcDecodable`.

Remove unnecessary trait bounds from types. These tend to transitively
'infect' any usage and require documenting the same bounds even if not
used in a given function, type or implementation. The reason I decided
to do this in this commit was because the change to Deserialize would
require a lifetime bound, which isn't even currently used in structs
deriving Deserialize. This led to a lot of unnecessary boilerplate
showing up all over the code base. Removing the trait bounds altogether
and only using them in functions and impl blocks cleaned this up.
See this issue for more information:
serde-rs/serde#890

For further details:
https://github.com/Manishearth/rust-clippy/issues/1689#issuecomment-296396580

Simplify the Process definition by just using a single parameter instead
of an associated type.
@serde-rs serde-rs locked and limited conversation to collaborators May 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Development

No branches or pull requests

3 participants