使用自定义数据类型反序列化结构

Deserializing a struct with custom data types

我正在尝试 #[derive(Deserialize, Serialize)] 一些涉及其他自定义结构的结构,因此我可以将它们转换成 JSON,例如:

#[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,
}

…但我 运行 喜欢这个:

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

我在网上尝试了多种不同的解决方案,包括定义生命周期,我几乎都成功了。

我所有的代码都是here (sorry for spaghetti). The file in question is exercises.rs

产生相同错误的最小化示例:

use serde::Deserialize;

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

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

Playground

要了解问题所在,让我们看一下展开后的代码。它相当大而且可读性不强,但即使是签名也能提供帮助:

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!()
    }
}

可以看到,Inner结构体(原代码中的Exersize)只有在反序列化器(即源数据)为'static时才能反序列化,但是Outer 结构(原始代码中的 SetEntry)为每个反序列化器生命周期实现了反序列化——本质上,它实现了 DeserializeOwned,即它不需要从任何地方借用它的数据。

现在你可能会问,为什么要这样限制?为什么 Deserializer<'static> 在第一种情况下?答案是 - 当 serde(borrow) 超过 &'static str.
时,你要求这样做 当反序列化包含引用的结构时,Serde 必须证明这些引用永远不会悬空。因此,反序列化数据的生命周期(即 Deserialize 特征的参数)必须与原始数据的生命周期相关联 - 即 Deserializer 的参数。而且,如果结构对其内容的生命周期有任何限制,这些限制会自动转移到 Deserializer.
在这种情况下,您要求尽可能严格的限制 - 您要求反序列化为 Inner 的数据在程序结束之前可用。然而,对 Outer 没有这样的限制 - 事实上,它可以将 Inner 视为拥有的数据,因为这个结构根本没有任何生命周期参数,所以 Serde 要求最通用的反序列化器可能然后在 Inner 要求它是 'static.

时窒息

现在,在这种情况下该怎么办?

首先,你绝对不想在任何runtime-generated数据中使用&'static str。这是字符串文字的类型,即嵌入到可执行文件本身的字符串,而不是 common-case 字符串。

最简单且可能是最正确的方法是将任何 &'static str 替换为拥有的 String。这将消除对 serde(borrow) 的需要,并使您可以从任何东西构造可反序列化的结构。

但是,如果您想使用引用(例如,消除不必要的副本),您必须将整个结构树视为对反序列化器的临时借用——也就是说,您将生命周期参数绑定到每个直接或间接包含 &str:

的结构中的 &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>,
}

然后,当手动创建这些内部有字符串文字的结构时,您只需将 'static 替换为 'a