如何将'struct'转换为"&[u8]"?

  • 本文关键字:u8 struct 转换 rust
  • 更新时间 :
  • 英文 :


我想通过TcpStream发送我的结构。我可以发送Stringu8,但我不能发送任意结构。例如:

struct MyStruct {
    id: u8,
    data: [u8; 1024],
}
let my_struct = MyStruct { id: 0, data: [1; 1024] };
let bytes: &[u8] = convert_struct(my_struct); // how??
tcp_stream.write(bytes);

收到数据后,我想将&[u8]转换回MyStruct。如何在这两种表示形式之间进行转换?

我知道 Rust 有一个用于序列化数据的 JSON 模块,但我不想使用 JSON,因为我想尽可能快和尽可能小地发送数据,所以我希望没有或非常小的开销。

可以使用

stdlib和泛型函数将正确大小的结构作为零复制字节来完成。

在下面的示例中,有一个名为 any_as_u8_slice 而不是 convert_struct 的可重用函数,因为这是一个包装铸造和切片创建的实用程序。

请注意,问题询问的是转换,此示例创建一个只读切片,因此具有不需要复制内存的优点。

这是一个基于该问题的工作示例:

unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
    ::core::slice::from_raw_parts(
        (p as *const T) as *const u8,
        ::core::mem::size_of::<T>(),
    )
}
fn main() {
    struct MyStruct {
        id: u8,
        data: [u8; 1024],
    }
    let my_struct = MyStruct { id: 0, data: [1; 1024] };
    let bytes: &[u8] = unsafe { any_as_u8_slice(&my_struct) };
    // tcp_stream.write(bytes);
    println!("{:?}", bytes);
}
<小时 />

注意 1) 尽管在某些情况下第三方 crate 可能更好,但这是一个如此原始的操作,知道如何在 Rust 中执行操作很有用。

注 2) 在撰写本文时(Rust 1.15),不支持const函数。一旦有,就可以投射到固定大小的数组而不是切片中。

注3)any_as_u8_slice函数被标记为unsafe,因为struct中的任何填充字节都可能是未初始化的内存(给出未定义的行为)。如果有一种方法可以确保输入参数只使用#[repr(packed)]的结构,那么它可能是安全的。

否则,该函数相当安全,因为它可以防止缓冲区溢出,因为输出是只读的,固定的字节数,并且其生存期与输入绑定。
如果您想要一个返回&mut [u8]的版本,那将非常危险,因为修改很容易创建不一致/损坏的数据。

(无耻地窃取并改编自Renato Zannon对类似问题的评论)

也许像bincode这样的解决方案适合您的情况?以下是工作摘录:

Cargo.toml

[package]
name = "foo"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"
[dependencies]
bincode = "1.0"
serde = { version = "1.0", features = ["derive"] }

main.rs

use serde::{Deserialize, Serialize};
use std::fs::File;
#[derive(Serialize, Deserialize)]
struct A {
    id: i8,
    key: i16,
    name: String,
    values: Vec<String>,
}
fn main() {
    let a = A {
        id: 42,
        key: 1337,
        name: "Hello world".to_string(),
        values: vec!["alpha".to_string(), "beta".to_string()],
    };
    // Encode to something implementing `Write`
    let mut f = File::create("/tmp/output.bin").unwrap();
    bincode::serialize_into(&mut f, &a).unwrap();
    // Or just to a buffer
    let bytes = bincode::serialize(&a).unwrap();
    println!("{:?}", bytes);
}

然后,您将能够将字节发送到您想要的任何位置。我假设您已经意识到天真地发送字节的问题(例如潜在的字节序问题或版本控制),但我会提到它们以防万一^_^.

您可以使用bytemuck箱安全地执行此操作:

#[derive(bytemuck::NoUninit, Clone, Copy)]
#[repr(C)]
struct MyStruct {
    id: u8,
    data: [u8; 1024],
}
let my_struct = MyStruct { id: 0, data: [1; 1024] };
let bytes: &[u8] = bytemuck::bytes_of(&my_struct);
tcp_stream.write(bytes);

请注意,这要求结构Copy#[repr(C)]#[repr(transparent)]

最新更新