如何在Rust中使用Bincode序列化枚举,同时保留枚举判别式而不是索引?

jxct1oxe  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(141)

我一直在使用bincode在Rust中序列化枚举,但我面临一个问题,我收到的是枚举变量的索引,而不是其分配的判别式。下面是我尝试序列化的枚举的示例:

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
    #[repr(u64)]
    pub enum StreamOutputFormat {
        A(X) = 0x01,
        B(Y) = 0x02,
        C(Z) = 0x03,
    }

字符串
在此代码中,X、Y和Z表示结构。
当我尝试使用bincode序列化StreamOutputFormat::C(Z {some_z_stuff...})的示例时,如下所示:

let sof = StreamOutputFormat::C(Z {some_z_stuff...});
println!("sof: {:?}", bincode::serialize(&sof));


我得到的输出是:

[2, 0, 0, 0, 0, 0, 0, 0, ...]


这是有问题的,因为由于与其他组件的互操作性要求,我需要序列化的输出是枚举变量(在本例中为0x03)的判别式,而不是索引。为了比较,我的代码库中的其他单元枚举使用(De)Serialize_repr正确序列化。
在bincode中序列化(和反序列化)这种类型的枚举的正确方法是什么,以便我接收枚举变体判别式而不是它的索引?

zsbz8rwp

zsbz8rwp1#

复杂的是serde的enum反序列化只允许字符串、字节或u32作为enum标记(每种格式选择这三种格式之一)。这是硬编码到每一种格式,例如在这里的bincode。就serde而言,带有u64标记的枚举本质上不是枚举。
因此,考虑到这一点,您必须将枚举序列化和反序列化为枚举以外的东西。我选择使用一个元组,它可能是最接近枚举的。序列化是非常简单的,因为我们知道一切都是什么类型。

impl Serialize for StreamOutputFormat {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            StreamOutputFormat::A(x) => {
                let mut tuple = serializer.serialize_tuple(2)?;
                tuple.serialize_element(&0x01u64)?;
                tuple.serialize_element(x)?;
                tuple.end()
            }
            StreamOutputFormat::B(y) => {
                let mut tuple = serializer.serialize_tuple(2)?;
                tuple.serialize_element(&0x02u64)?;
                tuple.serialize_element(y)?;
                tuple.end()
            }
            StreamOutputFormat::C(z) => {
                let mut tuple = serializer.serialize_tuple(2)?;
                tuple.serialize_element(&0x03u64)?;
                tuple.serialize_element(z)?;
                tuple.end()
            }
        }
    }
}

字符串
为了清楚起见,我一直在重复这个。如果你有一个包含多个字段的变量,你需要增加传递给serialize_tuple的数字。也不要忘记判别式需要是u64
现在是反序列化。

impl<'de> Deserialize<'de> for StreamOutputFormat {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::Error;

        struct StreamOutputFormatVisitor;

        impl<'de> Visitor<'de> for StreamOutputFormatVisitor {
            type Value = StreamOutputFormat;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(
                    formatter,
                    "a tuple of size 2 consisting of a u64 discriminant and a value"
                )
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
                let discriminant: u64 = seq
                    .next_element()?
                    .ok_or_else(|| A::Error::invalid_length(0, &self))?;
                match discriminant {
                    0x01 => {
                        let x = seq
                            .next_element()?
                            .ok_or_else(|| A::Error::invalid_length(1, &self))?;
                        Ok(StreamOutputFormat::A(x))
                    }
                    0x02 => {
                        let y = seq
                            .next_element()?
                            .ok_or_else(|| A::Error::invalid_length(1, &self))?;
                        Ok(StreamOutputFormat::B(y))
                    }
                    0x03 => {
                        let z = seq
                            .next_element()?
                            .ok_or_else(|| A::Error::invalid_length(1, &self))?;
                        Ok(StreamOutputFormat::C(z))
                    }
                    d => Err(A::Error::invalid_value(
                        serde::de::Unexpected::Unsigned(d),
                        &"0x01, 0x02, or 0x03",
                    )),
                }
            }
        }

        deserializer.deserialize_tuple(2, StreamOutputFormatVisitor)
    }
}


通过样板文件,我们调用了deserialize_tuple,它将在访问者上调用visit_seq。在该方法中,访问者使用u64作为判别式,然后使用基于该判别式的内部数据。

不起作用的方法

您不能通过向枚举添加伪字段来序列化它,因为这仍然会使用u32标记。你可以尝试的另一件事是通过(u64, UntaggedEnum)进行反序列化,其中UntaggedEnum是:

#[derive(Deserialize)]
#[serde(untagged)]
UntaggedEnum {
    A(X),
    B(Y),
    C(Z),
}


这不起作用,因为非自描述格式不能处理未标记的枚举。最重要的是,如果数据对多个变量有效,即使是自描述格式也可能失败,因为没有简单的方法来根据第一个元素有条件地反序列化元组的第二个元素。这也是低效的,因为即使u64无效,它也会尝试反序列化枚举。

注意事项

您可能已经添加了#[repr(u64)],但不知道serde枚举需要是u32,实际上您也可以使用#[repr(u32)]枚举。如果是这样的话,似乎可以使用serde的enum反序列化,它会稍微简单一些(非常类似于Deserialize宏生成的内容)。据我所知,你需要将判别式Map到它们各自的变体。
值得注意的是,我编写的代码从未引用枚举定义中给出的实际判别式。这对于rust来说是非常标准的,因为枚举判别式几乎没有什么功能。您甚至需要执行指针转换来读取它们,并且它们在有条件地反序列化XYZ时绝对没有用处。如果删除它们,则对序列化没有任何影响。
如果你想修改这个枚举,或者它有大量的变量,那么把它变成一个宏是一个很好的利用时间的方法。作为一个声明性宏,这不会太难,因为你需要的所有值都是像0x01这样的文字,重复是显而易见的。
我还没有检查bincode2.0,它包含了自己的Decode trait,不使用serde。也许可以解码u64枚举标记,但其结构与serde完全不同,所以我没有过多研究它。
这可能与您想要的格式不匹配。根据u64 repr判断,无论您尝试反序列化的是什么,都不是由serde枚举生成的,因此我不可能知道您的格式是否与我使用的元组格式匹配。

相关问题