我正在将一个低延迟的实时应用程序移植到Rust.从本质上讲,我希望在不复制的情况下尽可能多地执行操作.目前内存分配的一个主要来源是网络堆栈.每次接收或发送包时,都会在幕后进行大量复制,以强制执行Rust struct 中的字节并从中检索字节.我不想再这么做了.

从本质上讲,我希望能够将包从/转换为字节并传输它.下面是一个非工作的示例代码,它说明了我想要做的事情.

#[repr(C, align(4))]
#[derive(Debug)]
struct Content {
    first: u8,
    padding: [u8; 3]
}

#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
    header_id: u16,
    header_padding: [u8; 2],
    // This is another pointer I want it to be located after header_padding
    content: Box<[Content]>,
}

#[derive(Debug)]
struct ParsedPacket {
    content_length: usize,
    // The box below should directly reference the original u8 bytes without copying
    packet: Box<Packet>,
}

unsafe fn buf_to_packet(bs: Box<[u8]>) -> Option<Box<ParsedPacket>> {
    // How to implement this
    // First 4 bytes are the header
    // The rest must be 4 byte multiple and each 4 byte is a Content struct
    // So from the length of the boxed slice we can infer the content_length
    todo!()
}

unsafe fn packet_to_buf(bs: ParsedPacket) -> Box<[u8]> {
    todo!()
}

fn main() {
    let bytes = vec![
        // Header
        0, 23, 0, 0,
        // Content 0
        0, 0, 0, 0,
        // Content 1
        1, 0, 0, 0,
        // Content 2
        0, 0, 0, 0
    ].into_boxed_slice();

    let packet = unsafe { buf_to_packet(bytes) };

    println!("{:?}", content);
}

这有两个问题.铁 rust 要求内容是盒装的切片.这主要是因为它不一定位于 struct 本身,而可能位于其他地方.此外,盒装切片还包含它自己的关于长度的簿记.我想我需要自己做长度的记账.但我不知道如何在不使用盒装切片的情况下添加具有动态长度的 struct 成员.

我当然愿意使用不安全来做这件事.但我没有使用铁 rust 的经验,所以我不知道如何才能真正实现这一点.

从本质上说,我在Rust中寻找的是来自C的Flexible array members.

推荐答案

在Rust中,这被称为dynamically sized type(DST),通常包括特征对象和切片,但您可以自定义DST,尽管充其量支持零星(通过使用unsafe和夜间功能来实现这样的功能).

要使您的 struct 成为一个定制的DST(并且行为类似于C中的灵活数组成员),只需将最后一个成员变成一个裸片,如下所示:

#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
    header_id: u16,
    header_padding: [u8; 2],
    content: [Content],
}

要将字节"转换"为Packet,必须首先将字节对齐.在许多现代系统上,这通常是8字节对齐的,但如果这不可靠,您可以在这里使用这个技巧:How do I allocate a Vec that is aligned to the size of the cache line?由于对齐和分配限制,创建&Packet比创建Box<Packet>容易得多.

然后,为了正确地操作用于向DST表示间接的"胖指针",我们将需要依赖不稳定特性ptr_metadata.将这一点付诸实践会产生这样的结果:

#![feature(ptr_metadata, pointer_is_aligned)]

#[repr(C, align(4))]
#[derive(Debug)]
struct Content {
    first: u8,
    padding: [u8; 3]
}

#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
    header_id: u16,
    header_padding: [u8; 2],
    content: [Content],
}

fn buf_to_packet(bytes: &[u8]) -> Option<&Packet> {
    const PACKET_HEADER_LEN: usize = 4;

    let bytes_len = bytes.len();
    let bytes_ptr = bytes.as_ptr();
    if bytes_len < PACKET_HEADER_LEN || !bytes_ptr.is_aligned_to(4) {
        return None;
    }

    let content_len = (bytes_len - PACKET_HEADER_LEN) / std::mem::size_of::<Content>();
    let packet_raw: *const Packet = std::ptr::from_raw_parts(bytes_ptr.cast(), content_len);

    // SAFETY: the pointer is aligned and initialized
    unsafe { packet_raw.as_ref() }
}

另见:

Rust相关问答推荐

为什么是!为Rust中的RwLockReadGuard和RwLockWriteGuard实现的发送特征?

在特征中使用Async时,如何解决不透明类型`impl Future<;out=self>;`不满足其关联的类型边界和警告?

Rust TcpStream不能在读取后写入,但可以在不读取的情况下写入.为什么?

MutexGuard中的过滤载体不需要克隆

如何使用syn插入 comments ?

限制未使用的泛型导致编译错误

如何实现泛型枚举的`Serde::Desialize`特性

在Rust中,在实现特征`Display`时,如何获取调用方指定的格式?

我可以在不收集或克隆的情况下,将一个带有Item=(key,val)的迭代器拆分成单独的key iter和val iter吗?

AXUM一路由多个不包括URL的参数类型

Rust 中什么时候可以返回函数生成的字符串切片&str?

在 Rust 中,在第一个空格上分割字符串一次

仅在使用 &mut 或线程时borrow 的数据在闭包之外转义?

在1.5n次比较中找到整数向量中的最大和次大整数

在构建器模式中捕获 &str 时如何使用生命周期?

如何在 use_effect_with_deps 中设置监听器内的状态?

改变不实现克隆的 dioxus UseState struct

编写 TOML 文件以反序列化为 struct 中的枚举

如何在 Rust 中构建一个 str

有什么办法可以用 Rust 访问 Windows 最近的文件夹吗?