serde-rs / serde

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

Add lifetime container attribute for user-provided lifetime #2744

Open ia0 opened 1 month ago

ia0 commented 1 month ago

This PR is for discussion and related to #2190. As discussed there, the benefit is not obvious (except for providing more control to the user).

With this PR, a user can use #[serde(lifetime = 'a)] to control the lifetime of the implementation of Deserialize. In particular, the user is now able to control all the implementation parameters when using #[serde(bound = "", lifetime = 'a)].

See https://github.com/ia0/wasefire/blob/ccf7bc30ac8440b30981b2f39b734de10d1a037c/crates/protocol/src/lib.rs#L65 for an example in real code which gives impl<'a, T: Direction<'a>> _serde::Deserialize<'a> for Api<'a, T> when looking with cargo expand. The preceding commented line is the solution without this PR.

ia0 commented 1 month ago

Actually, I think I have a concrete example where it is beneficial (and not just more expressive) to be able to choose the Deserialize lifetime parameter from the impl parameters. When having a very large enum Api<'a, 'de: 'a, T: Flavor> where each variant is of the form Foo(T::Type<'a, 'de, Foo>) because otherwise serde will add a bound T::<'a, 'de, Foo>: Deserialize<'x> for each variant Foo (possibly increasing compilation time).

#[derive(Deserialize)]
#[serde(bound = "", lifetime = 'de)]
enum Api<'a, 'de: 'a, T: Flavor> {
    Foo(T::Type<'a, 'de, Foo>),
    // many similar lines replacing Foo with something else
}

trait Flavor {
    type Type<'a, 'de: 'a, T: Variant>: 'a + Deserialize<'de>;
}

I'll try to come up with concrete code today. The idea is that Api is only meant to be a view into serialized data. So it doesn't need any bound, it is statically always deserializable.

ia0 commented 1 month ago

Here's a concrete example: https://github.com/ia0/wasefire/blob/96c8e4e29d554346b71b297668b2b7337dbf3455/crates/protocol/src/lib.rs#L64

That said, I'm not convinced it's something I like. Ideally I want to use for<'de: 'a> Deserialize<'de> as a bound but it's not possible in Rust, which forces to have 'de explicit everywhere.