2个不稳定版本
0.2.0 | 2021年6月29日 |
---|---|
0.1.0 | 2020年10月15日 |
#44 在 #bidirectional
192 每月下载量
在 declio 中使用
27KB
709 行
declio
一个声明式I/O序列化库。
declio
提供了一对特质,Encode
和 Decode
,这些特质促进了二进制数据流(std::io
特质)和任意数据类型之间的双向转换。
这些特质实现了std
中的许多类型;整数和浮点原始类型可以编码和解码为其大端或小端二进制表示,而集合和其他容器类型编码和解码它们包含的数据。
但是,也有一些值得注意的例外;例如,没有为bool
和str
/String
提供实现。这是因为这些类型有几种常见的表示形式,为了避免意外误用,您需要显式声明它们的表示形式。一些常见的表示形式在util
模块中提供,作为包装类型和帮助模块实现。
此crate还提供了一对派生宏,通过默认功能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
是一个“上下文”值。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
,这也是有效的,并将()
作为上下文传递给元素解码器。
推导
以下是一个使用推导宏来编码和解码用户定义的数据类型的例子。这不是推导宏功能的完整演示;有关更完整的参考,请参阅[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);
依赖关系
~2MB
~42K SLoC