serde icon indicating copy to clipboard operation
serde copied to clipboard

Wrong `'static` bound on derived `Deserialize`

Open CheaterCodes opened this issue 1 year ago • 2 comments

I've been trying to deserialize a file into a Tree, with its contents leaked to be static.

#[derive(Deserialize)]
struct Tree {
    #[serde(deserialize_with = "deserialize_leaked")]
    node: &'static str,
    #[serde(deserialize_with = "deserialize_leaked")]
    children: &'static [Tree],
}

fn deserialize_leaked<'de, D, T>(d: D) -> Result<&'static T, D::Error>
where
    T: ?Sized,
    Box<T>: Deserialize<'de>,
    D: Deserializer<'de>,
{
    Box::<T>::deserialize(d).map(|b| Box::leak(b) as &'static _)
}

This works fine in principle, but the macro code then generates a

impl Deserialize<'static> for Tree { ... }

but I need it to be

impl<'de> Deserialize<'de> for Tree { ... }

I have found a workaround by using a type alias

type StaticStr = &'static str;

#[derive(Deserialize)]
struct Tree {
    #[serde(deserialize_with = "deserialize_leaked")]
    node: StaticStr,
    #[serde(deserialize_with = "deserialize_leaked")]
    children: &'static [Tree],
}

fn deserialize_leaked<'de, D, T>(d: D) -> Result<&'static T, D::Error>
where
    T: ?Sized,
    Box<T>: Deserialize<'de>,
    D: Deserializer<'de>,
{
    Box::<T>::deserialize(d).map(|b| Box::leak(b) as &'static _)
}

which has the expected behavior, but it's not very nice.

While browsing the the internet, I found the borrow attribute, but unfortunately this doesn't allow me to specify zero lifetime arguments. While that seems like a reasonable restriction, in this case that is precisely what I want:

#[derive(Deserialize)]
struct Tree {
    #[serde(borrow="", deserialize_with = "deserialize_leaked")]
    node: &'static str,
    #[serde(deserialize_with = "deserialize_leaked")]
    children: &'static [Tree],
}

fn deserialize_leaked<'de, D, T>(d: D) -> Result<&'static T, D::Error>
where
    T: ?Sized,
    Box<T>: Deserialize<'de>,
    D: Deserializer<'de>,
{
    Box::<T>::deserialize(d).map(|b| Box::leak(b) as &'static _)
}

Though it's also interesting, why is the type alias only required on the &'static str but not the &'static [Tree]? I wanted to simply suggest to allow #[serde(borrow = ""), but something else feels off here.

CheaterCodes avatar Dec 19 '23 13:12 CheaterCodes

serde(bound = ""), either on the node field or on the whole struct, should do the trick. That supersedes all built-in inference of trait bounds and lifetime bounds.

There is a difference between &'static str and &'static [Tree] due to the first sentence in https://serde.rs/lifetimes.html#borrowing-data-in-a-derived-impl: only &'a str and &'a [u8] result in 'de: 'a lifetime bound implicitly being added by serde to the derived impl. Other field types add no lifetime bound implicitly and one must use a borrow or bound serde attribute to get a lifetime bound.

dtolnay avatar Dec 19 '23 15:12 dtolnay

Thank you for the explanation. Using serde(bound = "") does indeed work. I suppose if more complex (generic) types don't have this inferred borrow anyway, then this is equivalent to the serde(borrow=""). Though I'd think the borrow attribute would be more intuitive.

Feel free to close this if you think using bound is good enough!

CheaterCodes avatar Dec 22 '23 14:12 CheaterCodes