Rust 中的编译时数据包构造

Compile-time packet construction in Rust

我有一个 C++ 程序,我在其中使用模板元编程生成要发送的小型二进制格式数据包 "over the wire",与分配固定大小的更天真的方法相比,它提供了更好的安全性和清晰度缓冲区并使用手动计算的偏移量将各种数据项复制到其中。

int foo(int fd, long long data1, float f1)
{
    auto m = membuf()
             .append<char>(0xAA).append<char>(0xBB)  // header
             .append(data1)
             .append(f1)
             .append(555); // in the pipe (arbitary extra data)
    return write(fd, m.data(), m.size());
}

这将发送一个包含两个字节 0xAA0xBB 的数据包,(例如)来自 data1 的 8 个字节,来自 f1 的 4 个字节,以及4 个字节组成 555。 (int 等的实际大小当然取决于 compiler/architecture 细节,但我也可以使用例如 uint64_t 类型进行精确控制。

(注意:membuf 的完整实现与问题并不相关,但如果有兴趣,您可以在此处查看:https://godbolt.org/z/sr0Cuu

这种情况下的重要特征是:

碰巧,这被编译成一个非常有效的指令序列,它简单地在堆栈上分配缓冲区并将每个值写入其中的正确位置:

foo(int, long long, float):
        subq    , %rsp
        movl    $-17494, %eax
        movl    , %edx
        movq    %rsi, 2(%rsp)
        movq    %rsp, %rsi
        movw    %ax, (%rsp)
        movl    5, 14(%rsp)
        movd    %xmm0, 10(%rsp)
        call    write
        addq    , %rsp
        ret

我正在寻找的是一个 Rust 解决方案来实现同样的事情。如果 Rust 编译器目前不能生成与上述一样高效的代码,我并不介意,但满足上述要求很重要:没有堆分配,没有数据包大小或数据偏移的动态计算,没有使用实验性/"unstable" 语言功能。

我一直在阅读 the Rust book 并试图了解我是否可以以及如何在 Rust 中做到这一点,但到目前为止我一无所获:

本质上:我想要一个通用类型,由缓冲区大小参数化,它可以接受一个值和 return 一个更大的固定大小的缓冲区,最后附加数据。但也许该规范过于以 C++ 为中心,在 Rust 中可以采取另一种策略——我只需要弄清楚它是什么!

用实际代码稍微放大我的评论:restruct-crate 可以做你要求的;但是,它确实需要 nightly 截至目前,因为打包和解包是 const 函数,还不稳定。

给定您的示例,在将 restructrestruct_derive 添加到依赖项之后:

#![feature(const_int_conversion)]
#![feature(const_fn)]
#![feature(const_slice_len)]
#![feature(const_transmute)]


/// A packer/unpacker for two unsigned bytes, a group of eight unsigned bytes, a group of
/// four unsigned bytes and four padding bytes; all in little endian.
#[derive(restruct_derive::Struct)]
#[fmt="< 2B 8s 4s 4x"]
struct Foo;


fn main() {
    let data = (0xAA, 0xBB, [1,2,3,4,5,6,7,8], [4,3,2,1]);

    println!("The buffer has a size of {}", Foo::SIZE);

    let buf: [u8; Foo::SIZE] = Foo::pack(data);
    println!("Packed as {:?}", buf);

    let unpacked: <Foo as restruct::Struct>::Unpacked = Foo::unpack(buf);
    println!("Unpacked as {:?}", unpacked);
}