Home > Mobile >  Deserializing a struct with custom data types
Deserializing a struct with custom data types

Time:05-06

I am trying to #[derive(Deserialize, Serialize)] some structs that involve other custom structs, so I can transform them in and out of JSON, for example:

#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct Exercise {
    #[serde(borrow)]
    pub name: &'static str,
    pub muscle_sub_groups: [MuscleSubGroup; 2],
    pub recommended_rep_range: [u32; 2],
    pub equipment: EquipmentType,
}

#[derive(Debug, Clone, Deserialize)]
pub struct SetEntry {
    pub exercise: Exercise,
    pub reps: u32,
    pub weight: Weight, // another struct with two: &'static str
    pub reps_in_reserve: f32,
}

…but I am running into this:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'de due to conflicting requirements

I've tried multiple different solutions online, including defining lifetimes and I've all but succeeded.

All of my code is here (sorry for spaghetti). The file in question is exercises.rs.

CodePudding user response:

Minimized example which yields the same error:

use serde::Deserialize;

#[derive(Deserialize)]
pub struct Inner {
    #[serde(borrow)]
    pub name: &'static str,
}

#[derive(Deserialize)]
pub struct Outer {
    pub inner: Inner,
}

Playground

To see what's wrong, let's look at the expanded code. It's rather large and not very readable, but even the signatures can help:

impl Deserialize<'static> for Inner {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'static>,
    {
        todo!()
    }
}

impl<'de> Deserialize<'de> for Outer {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        todo!()
    }
}

As you can see, the Inner struct (the Exersize in original code) can be deserialized only when the deserializer (i.e. the source data) is 'static, but the Outer struct (the SetEntry in original code) has its deserialization implemented for every deserializer lifetime - essentially, it implements DeserializeOwned, i.e. it doesn't need to borrow its data from anywhere.

Now you might ask, why this restricton? Why the Deserializer<'static> in the first case? The answer is - you asked for that, when placed serde(borrow) over &'static str.
When deserializing struct containing references, Serde has to prove that these references will never be dangling. Therefore, the lifetime of deserialized data (that is, the parameter to Deserialize trait) must be tied to the lifetime of original data - that is, to the parameter of Deserializer. And, if the struct has any restrictions on the lifetime of its contents, these restrictions are automatically transferred to the Deserializer.
In this case, you've asked for the strictest restriction possible - you've asked that the data being deserialized into Inner be available until the end of program. However, there's no such restriction on Outer - in fact, it can treat Inner as owned data, since this struct doesn't have any lifetime parameters at all, - so Serde asks for the most generic deserializer possible and then chokes when Inner requires it to be 'static.


Now, what to do in this case?

First of all, you definitely don't want to use &'static str in any runtime-generated data. This is the type of string literals, i.e. of strings baked into the executable itself, not of the common-case strings.

The simplest and probably most correct way would be to replace any &'static str with the owned String. This will eliminate the need for serde(borrow) and make you struct deserializable from anything.

If, however, you want to use references (e.g. to eliminate unnecessary copies), you have to treat the whole structs tree as a temporary borrow into the deserializer - that is, you'll have the lifetime parameter tied to the &str in every struct which directly or indirectly contains that &str:

use serde::Deserialize;

#[derive(Deserialize)]
pub struct Inner<'a> {
    #[serde(borrow)]
    pub name: &'a str,
}

#[derive(Deserialize)]
pub struct Outer<'a> {
    #[serde(borrow)]
    pub inner: Inner<'a>,
}

And then, when creating these structs manually with string literals inside, you can just substitute 'static for 'a.

  • Related