2个不稳定版本
0.2.0 | 2021年6月29日 |
---|---|
0.1.0 | 2020年10月15日 |
#1939 在 编码
每月 208 次下载
42KB
661 行
declio
声明式I/O序列化库。
declio
提供了一对特质, [Encode
] 和 [Decode
],它们简化了二进制数据流(std::io
特质)和任意数据类型之间的双向转换。
这些特质为 std
中的许多类型实现了;整数和浮点原始数据可以编码和解码为其大端或小端二进制表示,集合和其他容器类型则编码和解码它们包含的数据。
然而,也有一些值得注意的例外;例如,没有为 bool
和 str
/String
提供实现。这是因为这些类型有几种常见的表示方式,为了避免意外误用,您需要明确声明它们的表示方式。一些常见的表示方式在 [util
] 模块中提供,既作为包装类型也作为辅助模块实现。
此crate还提供了一对 derive 宏,通过默认功能 derive
,可以实现任意复合数据类型的 Encode
和 Decode
。默认情况下,它将按顺序编码和解码所有字段,但它高度可配置,旨在针对二进制格式中发现的许多不同模式。
此crate的灵感主要来自 deku
,但根据我的个人观点和偏好进行了一些修改。例如,declio
使用 std::io
中的字节数据流,而不是 deku
中使用的位 BitVec
。
示例
让我们从一个简单的例子开始 - 将单个整数编码到一个字节数据缓冲区中
use declio::Encode;
use declio::ctx::Endian;
let mut buf: Vec<u8> = Vec::new();
u32::encode(&0xdeadbeef, Endian::Big, &mut buf)
.expect("encode failed");
assert_eq!(buf, [0xde, 0xad, 0xbe, 0xef]);
在这个例子中,[Endian::Big
] 是一个“上下文”值。《code>declio 以一种简单的方式提供这些上下文,以便在运行时配置或修改 Encode
或 Decode
的实现,而不是使用包装类型或辅助函数。在这种情况下,它告诉 Encode
的实现对于 u32
按大端顺序编码字节。它也可以设置为 [Endian::Little
] 来反转字节顺序,并且一般来说, [Endian
] 可以传递给任何整数和浮点原始类型以产生类似的效果。
上下文还可以用来抽象一些必要的字段,用于编码和解码某些类型。例如,像 Vec
这样的可变长度容器在解码时接受一个 [Len
] 上下文值,它告诉 Decode
实现应该解码多少个值。这可以是一个编译时常量,如 Len(1024)
,或者它可以在运行时从另一个值创建,如下所示
use declio::Decode;
use declio::ctx::{Len, Endian};
let mut bytes = &[
// len
0x00, 0x02,
// words[0]
0xde, 0xad,
// words[1]
0xbe, 0xef,
][..];
let len = u16::decode(Endian::Big, &mut bytes)
.expect("decode len failed");
let words: Vec<u16> = Vec::decode((Len(len as usize), Endian::Big), &mut bytes)
.expect("decode bytes failed");
assert!(bytes.is_empty()); // did we consume the whole buffer?
assert_eq!(words, [0xdead, 0xbeef]);
这是因为 Vec
的 Decode
实现不知道如何读取长度值。它不知道二进制格式使用什么整数大小或字节顺序来编码长度;它甚至不知道长度是否被编码!它可能是一部分格式定义的固定长度。
Vec::decode
还可以接受一个额外的上下文值传递给元素解码器,使用一个 2-元组,如 (Len(len as usize), Endian::Big)
。然而,在这个例子中,只传递了一个 Len
,这也是有效的,并且会将 ()
作为上下文传递给元素解码器。
派生
以下是一个示例,它使用 derive 宏来编码和解码用户定义的数据类型。这不是 derive 宏功能的完整演示;有关更完整的参考,请参阅 [mod@derive
] 模块文档。
use declio::{Encode, Decode};
use declio::ctx::{Endian, Len};
use std::convert::TryInto;
#[derive(Debug, PartialEq, Encode, Decode)]
struct WithLength {
// Context can be passed to the field decoder with a `ctx` attribute.
#[declio(ctx = "Endian::Little")]
len: u16,
// Context may be different for encode and decode,
// though they should generally be as symmetric as possible.
// For example, `Vec` requires a `Len` when decoding, but it is
// optional for encoding. However, it should be provided anyway
// because it will be used to check that the encoded length is
// the same as the actual length.
//
// Fields declared before this one can be accessed by name
// (or by `field_0`, `field_1`, etc for tuple structs):
#[declio(ctx = "Len((*len).try_into()?)")]
bytes: Vec<u8>,
}
let bytes: Vec<u8> = vec![0xde, 0xad, 0xbe, 0xef];
let with_length = WithLength {
len: bytes.len().try_into().expect("length out of range"),
bytes,
};
let encoded: Vec<u8> = declio::to_bytes(&with_length)
.expect("encode failed");
assert_eq!(encoded, [0x04, 0x00, 0xde, 0xad, 0xbe, 0xef]);
let decoded: WithLength = declio::from_bytes(&encoded)
.expect("decode failed");
assert_eq!(decoded, with_length);
依赖关系
~0–270KB