「Rust 中方便和习惯性的转换」example2

「这是我参与11月更文挑战的第 19 天,活动详情查看:2021最后一次更文挑战


Example: PacketType

让我们举一个不同的例子。假设我们现在正在为一个网络协议实现一个库,其中数据包头的第一个字节告诉我们数据包的类型。一个合理的解决方案是用一个枚举来表示数据包类型,其中每个变量都映射到一个数据包类型。比如说:

/// Represents a packet type.
/// Associated with each variant is its raw numeric representation.
enum PacketType {
    Data  = 0, // packet carries a data payload
    Fin   = 1, // signals the end of a connection
    State = 2, // signals acknowledgment of a packet
    Reset = 3, // forcibly terminates a connection
    Syn   = 4, // initiates a new connection with a peer
}
复制代码

考虑到这种表示法,我们应该如何转换为字节和从字节转换出去?

传统的方法,在C和C++程序中非常常见,就是简单地将数值从一种类型转换为另一种类型。这在Rust中也可以做到;例如,将 PacketType::Data 转换为字节,就像 PacketType::Data 转换为u8一样简单。这似乎已经解决了将PacketType编码为字节表示的问题,但我们还没有完成。

你注意到每个PacketType变量都有一个相关的值吗?它们定义了变体在生成的代码中的表示。如果我们遵循通常的Rust风格,不给变量分配任何值,那么每个变体的数字表示将取决于它们被声明的顺序,如果我们简单地将枚举变体转换成数字类型,就会导致错误。将枚举变体转换为正确值的一个更好的方法是显式匹配。

impl From<PacketType> for u8 {
    fn from(original: PacketType) -> u8 {
        match original {
            PacketType::Data  => 0,
            PacketType::Fin   => 1,
            PacketType::State => 2,
            PacketType::Reset => 3,
            PacketType::Syn   => 4,
        }
    }
}
复制代码

很直接,对吧!由于从PacketType到u8的映射包含在From的实现中,我们可以删除分配给PacketType变体的值,从而得到一个更简洁的枚举定义。

相反的转换?

根据常见问题,将枚举转换为整数可以通过cast实现,正如我们看到的那样。然而,相反的转换可以(而且我认为,在很多情况下,应该)用匹配语句来实现。为了便于使用和更好的工程学,为两个(对称)方向的转换实现 From<T> 通常是个好主意。

将一个 PacketType 转换成 u8 通常是安全和正确的,除了我们之前看到的注意事项,因为对于每个 PacketType 变量,都有一个与 u8 兼容的对应表示。然而,反过来说就不对了:在没有相应的 PacketType 变量的情况下转换u8值是未定义的行为。

尽管我们可以将任何 PacketType 变量映射到u8值中,但我们不能反过来将任何u8映射到PacketType中:u8太多,而 PacketType 不够用!所以对于u8到PacketType的转换,我们不能简单地匹配u8值,并返回相应的PacketType变量。

因此,对于u8到PacketType的转换,我们不能简单地匹配u8值并返回适当的PacketType变体,就像我们对反向转换所做的那样。我们需要一种方法来提示转换失败,但调用panic!()并不是一个可接受的选择。我们需要一个易犯错误的From。

猜你喜欢

转载自juejin.im/post/7034852062156292109