我一直在使用二进制代码序列化Rust中的枚举,但我面临着一个问题,即我接收的是枚举变量的索引,而不是分配给它的判别式.下面是我试图序列化的枚举的一个示例:

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

在此代码中,X、Y和Z表示 struct .

当我try 使用二进制代码序列化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进行正确的序列化.

在二进制代码中序列化(和反序列化)这种类型的枚举的正确方法是什么,以便我接收枚举变量判别式而不是它的索引?

推荐答案

更复杂的是,serde的枚举反序列化只允许对枚举标记使用字符串、字节或u32(每种格式都从这三种格式中 Select 一种).这被硬编码到每种格式中,例如here in bincode.就serde而言,具有u64标记的枚举本质上不是枚举.

因此,考虑到这一点,您必须将枚举序列化和反序列化为枚举以外的其他对象.我 Select 使用元组,它可能是您所能得到的最接近枚举的元组.序列化非常简单,因为我们知道所有东西都是什么类型.

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作为判别式,然后根据该判别式使用内部数据.

Methods that don't work

您不能通过将虚拟字段添加到枚举来序列化它,因为这仍将使用u32个标记.您可以try 的另一件事是反序列化(u64, UntaggedEnum),其中UntaggedEnum是:

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

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

Notes

您可能在不知道serde枚举需要为u32的情况下添加了#[repr(u64)]个枚举,实际上您也可以使用#[repr(u32)]个枚举.如果是这样的话,似乎可以使用serde的枚举反序列化,而且会稍微简单一些(非常类似于Deserialize宏生成的内容).据我所知,您需要将这些区别项映射到它们各自的变体.

值得注意的是,我编写的代码从未引用枚举定义中给出的实际判别式.这对于 rust 病来说是非常标准的,因为枚举判别器的功能很少.您甚至需要执行a pointer cast次才能读取它们,并且它们在有条件地反序列化XYZ时完全没有用处.如果移除它们,将不会对序列化产生影响.

如果您打算修改此枚举,或者如果它有大量变体,那么将其转换为宏将是一个很好的利用时间.作为声明性宏,这不会太难,因为您需要的所有值都是像0x01这样的字面值,而且重复很明显.

我还没有看过bincode2.0,它包含了自己的不使用serde的Decode特征.可能可以解码u64个枚举标签,但其 struct 与serde完全不同,所以我没有太深入地研究它.

这可能与您想要的格式不匹配.从u64epr判断,您试图反序列化的任何内容都不是由serde枚举生成的,所以我不可能知道您的格式是否与我使用的元组格式匹配.

Rust相关问答推荐

在一个tauri协议处理程序中调用一个rectuc函数的推荐技术是什么?

返回的future 不是`发送`

有没有办法避免在While循环中多次borrow `*分支`

将PathBuf转换为字符串

异步函数返回的future 生存期

需要哪些编译器优化来优化此递归调用?

try 从标准输入获取用户名和密码并删除 \r\n

在发布中包含 Rust DLL

为什么Rust编译器会忽略模板参数应具有静态生命周期?

在多核嵌入式 Rust 中,我可以使用静态 mut 进行单向数据共享吗?

有没有办法通过命令获取 Rust crate 的可安装版本列表?

如何使用泛型满足 tokio 异步任务中的生命周期界限

是否可以在 Rust 中的特定字符上实现特征?

n 个范围的笛卡尔积

为什么具有 Vec 变体的枚举没有内存开销?

使用方法、关联函数和自由函数在 Rust 中初始化函数指针之间的区别

将一片字节复制到一个大小不匹配的数组中

如何从 many0 传播 Nom 失败上下文?

如何在没有 `make_contiguous()` 的情况下对 VecDeque 进行排序或反转?

当值是新类型包装器时,对键的奇怪 HashMap 生命周期要求