serde-rs / serde

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

"lifetime may not live long enough" when derive Deserialize for an enum with `Vec<&'static str>` #2407

Open Mingun opened 1 year ago

Mingun commented 1 year ago

Deriving Deserialize for that struct:


#[derive(Deserialize)]
enum Enum {
    Variant(Vec<&'static str>), // error due to this
}

#[derive(Deserialize)]
enum Enum2 {
    Variant(&'static str), // there is no error
}

gives the following error:

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
 --> src/lib.rs:5:13
  |
3 | #[derive(Deserialize)]
  |          ----------- lifetime `'de` defined here
4 | enum Enum {
5 |     Variant(Vec<&'static str>),
  |             ^^^ requires that `'de` must outlive `'static`

error: could not compile `playground` due to previous error

At the same time, a variant without Vec compiled ok.

Playground. In comment you can see derived code. Derived code for the Enum2 variant is the same except that all occurrences of 'de is replaced by 'static.

This is a problem also in 1.0.157, but playground did not have it yet at time of writing.

dtolnay commented 1 year ago

I think this is behaving as intended / as documented in https://serde.rs/lifetimes.html#borrowing-data-in-a-derived-impl. You would need to write #[serde(borrow)] Variant(Vec<&'static str>) if the intention is for the vec elements to be borrowed strings.

Mingun commented 1 year ago

Yes, #[serde(borrow)] solves the problem, but if you use Cow instead of Vec the things works without it: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ffacb52410b19952ee12907ca2d4daf8

Is it intended that the behavior will be different in that two cases?

dtolnay commented 1 year ago

Yes.

Mingun commented 1 year ago

Could you please be more rationale about this? I've not seen any special handling of Cows in the serde code, so why it works differently?

dtolnay commented 1 year ago

Deserializing Cow<'static, str> from &'de str does not require 'de to outlive 'static. It can just allocate Strings.

Deserializing Vec<&'static str> from &'de str would presumably require that 'de outlives 'static so you've gotta put the borrow attribute.

Mingun commented 1 year ago

Ok, but this is not encoded in types, right? How serde knows that it will not store references? Here the implementation of FakeCow, which does not store anything. Why it is required that 'de outlives 'static?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6f7a4ae043bfe75b14d0fda103877704

dtolnay commented 1 year ago

Cow<'static, str> and Vec<&'static str> behave differently from one another because their Deserialize impls have different signatures. This is how the different lifetime requirement between output value and input data is encoded in types.

https://github.com/serde-rs/serde/blob/e4a43891773880bd29613f85c8519ec980e2c981/serde/src/de/impls.rs#L623

https://github.com/serde-rs/serde/blob/e4a43891773880bd29613f85c8519ec980e2c981/serde/src/de/impls.rs#L1013-L1015

https://github.com/serde-rs/serde/blob/e4a43891773880bd29613f85c8519ec980e2c981/serde/src/de/impls.rs#L1823-L1826

&'static str implements Deserialize<'static>. Vec<&'static str> implements Deserialize<'static>. Cow<'static, str> implements for<'de> Deserialize<'de>.

dtolnay commented 1 year ago

According to the Deserialize impl that you have written for FakeCow, FakeCow<'static, str> implements Deserialize<'static>. It does not implement for<'de> Deserialize<'de>. So that lifetime behaves more like Vec<&'static str> and less like serde's Cow<'static, str>.

pheki commented 7 months ago

Interestingly enough, deserializing into a Vec<&'static str> does work if there's another &'static field:

use serde::Deserialize; // 1.0.156;

// This compiles just fine
#[derive(Deserialize)]
struct Struct {
    field1: Vec<&'static str>,
    #[serde(default)]
    _fix: &'static str,
}

I guess it's because it's placing a 'static bound on the 'de lifetime, but I got surprised when it started erroring after removing an unrelated field...

On another note, by using this struct in another struct I cannot use #[serde(borrow)] as it doesn't expose any lifetime, so I have to use #[serde(bound = "'de: 'static")] on the container, e.g.:

use serde::Deserialize; // 1.0.197

#[derive(Deserialize)]
#[serde(bound = "'de: 'static")]
struct WrapperStruct {
    inner: Struct,
}

#[derive(Deserialize)]
struct Struct {
    #[serde(borrow)]
    field1: Vec<&'static str>,
}

I don't know if that's intentional or if it should be another bug, but it's a bit strange.