Rust 中的编译时包构建

dav*_*mac 5 metaprogramming rust

我有一个 C++ 程序,我在其中使用模板元编程来生成要“通过网络”发送的小型二进制格式数据包,与分配固定大小缓冲区和复制各种数据的更幼稚的方法相比,它提供了更好的安全性和清晰度使用手工计算的偏移量将项目放入其中。

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());
}
Run Code Online (Sandbox Code Playgroud)

这将发送一个由两个字节0xAA和组成的数据包0xBB,(例如)来自 的 8 个字节、来自 的data14 个字节f1和形成 的 4 个字节555。(等的实际大小int当然取决于编译器/架构细节,但我也可以使用例如uint64_t类型进行精确控制)。

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

本案的重要特征是:

  • 不涉及堆分配,并且
  • 在编译时计算数据包的大小和每个值的偏移量
  • 都是标准的 C++,没有扩展,没有实验性的特性(实际上是 C++11)

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

foo(int, long long, float):
        subq    $40, %rsp
        movl    $-17494, %eax
        movl    $18, %edx
        movq    %rsi, 2(%rsp)
        movq    %rsp, %rsi
        movw    %ax, (%rsp)
        movl    $555, 14(%rsp)
        movd    %xmm0, 10(%rsp)
        call    write
        addq    $40, %rsp
        ret
Run Code Online (Sandbox Code Playgroud)

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

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

  • 泛型类型似乎无济于事,因为它们更像是原始意义上的“模板”,而不是 C++ 意义上的“模板”。除了类型之外,它们似乎也不允许参数化。
  • 宏似乎是 Rust 中首选的元编程工具,但除非我没有正确理解它们对令牌流的操作,并且除非有我遗漏的方法,否则它们无法完成membuf示例所做的那种事情。

本质上:我想要一个由缓冲区大小参数化的泛型类型,它可以接受一个值并返回一个更大的、固定大小的缓冲区,并在末尾附加数据。但也许该规范过于以 C++ 为中心,而且 Rust 还可以采用另一种方法——我只需要弄清楚它是什么!

use*_*968 4

用实际代码稍微放大我的评论:restruct-crate 可以做你要求的事情;然而,到目前为止,它确实需要每晚一次,因为打包和解包是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);
}
Run Code Online (Sandbox Code Playgroud)