【问题标题】:How to convert 'struct' to '&[u8]'?如何将'struct'转换为'&[u8]'?
【发布时间】:2015-01-24 15:43:46
【问题描述】:

我想通过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,因为我希望发送数据尽可能快且尽可能小,所以我希望没有或非常小的开销。

【问题讨论】:

    标签: rust


    【解决方案1】:

    可以使用stdlib 和通用函数来完成大小正确的结构作为零复制字节。

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

    注意题主问的是converting,这个例子创建了一个只读的slice,所以好处是不需要复制内存。

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

    unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
        ::std::slice::from_raw_parts(
            (p as *const T) as *const u8,
            ::std::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) 尽管在某些情况下 3rd 方 crate 可能更好,但这是一个非常原始的操作,知道如何在 Rust 中进行操作很有用。

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

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

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

    【讨论】:

    • 此解决方案与使用转换的解决方案有什么区别?有理由偏爱其中一个吗? doc.rust-lang.org/nomicon/transmutes.html
    • 如答案所述:“它可以防止缓冲区溢出,因为输出是只读的、固定的字节数,并且它的生命周期与输入绑定。”
    • 有没有办法反其道而行之,即将字节转换回结构体?
    • @Lev let s: MyStruct = unsafe { std::mem::transmute(*bytes) };
    • 惊人的答案,谢谢!注 3 只有一条评论:该函数不安全,因为您正在使用任意一块内存来构建切片(这就是 from_raw_parts 不安全的原因),而不是因为填充字节。当您拥有字节数组时,填充字节只是普通字节,而当您拥有结构时,它们根本无法访问。
    【解决方案2】:

    (无耻地盗用Renato Zannon's comment类似的问题)

    也许像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"] }
    

    ma​​in.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);
    }
    

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

    【讨论】:

    • 值得注意的是,这不是直接转换,虽然编码/解码使用二进制格式,但这并不是简单地访问结构体的内存(这可能被看作是好事和坏事) ) 根据您的需要,它正在执行一些转换。例如,Bincode 也进行字节序转换。
    【解决方案3】:

    如果你想存储到数据库或文件使用(bincode) 如果你想通过网络发送到另一个程序

    使用快速、安全、经过良好测试、令人惊叹的架构的 gRPC

    【讨论】:

    • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
    猜你喜欢
    • 1970-01-01
    • 2021-05-14
    • 1970-01-01
    • 1970-01-01
    • 2015-09-26
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 2021-03-27
    相关资源
    最近更新 更多