如何在Rust中将一个u8缓冲区转换为结构体?

14
我有一个大小未知的字节缓冲区,并且我想创建一个指向缓冲区开头内存的本地结构变量。按照在C语言中的做法,我在Rust中尝试了很多不同的方法,但一直遇到错误。这是我最新的尝试:
use std::mem::{size_of, transmute};

#[repr(C, packed)]
struct MyStruct {
    foo: u16,
    bar: u8,
}

fn main() {
    let v: Vec<u8> = vec![1, 2, 3];
    let buffer = v.as_slice();
    let s: MyStruct = unsafe { transmute(buffer[..size_of::<MyStruct>()]) };
}

我遇到一个错误。
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
   --> src/main.rs:12:42
    |
12  |     let s: MyStruct = unsafe { transmute(buffer[..size_of::<MyStruct>()]) };
    |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `std::marker::Sized` is not implemented for `[u8]`
    = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

1
你无法这样做,因为transmute需要在编译时知道大小。使用*mut指针的解决方案看起来就像你必须这样做。 - Peter Hall
4个回答

19
如果您不想将数据复制到结构体中,而是将其保留在原地,则可以使用slice::align_to。这将创建一个&MyStruct
#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
struct MyStruct {
    foo: u16,
    bar: u8,
}

fn main() {
    let v = vec![1u8, 2, 3];

    // I copied this code from Stack Overflow
    // without understanding why this case is safe.
    let (head, body, _tail) = unsafe { v.align_to::<MyStruct>() };
    assert!(head.is_empty(), "Data was not aligned");
    let my_struct = &body[0];

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

在这里,我们可以安全地使用align_to将一些字节转换为MyStruct,因为我们已经使用了repr(C, packed),而MyStruct中的所有类型都可以是任意的字节。
另请参见:

很好,我想这更符合问题。我不记得2017年时我尝试做什么了,大概是与asn1c代码进行接口交互的事情。 - sudo
1
@sudo align_to在2017年并不存在,因此这个答案的等效方法当时会更加丑陋... - Shepmaster
呵呵,那时候我甚至还在使用过时的 Rust 版本。 - sudo
1
这取决于不保证 align_to 实现的行为: "[align_to] 可能 使中间切片成为最大长度 [但] 允许将所有输入数据作为前缀或后缀切片返回。" (尽管至少会导致恐慌,而不是 UB。) :-/ - Søren Løvborg
@SørenLøvborg 这是真的,而且Miri以前甚至已经做出了这个选择(不知道现在是否仍然如此)。但是,我预计这种灵活性在实践中很少被使用。 - Shepmaster

17

谢谢,之前不知道std::ptr中有那些读写函数。我一直在std::mem中寻找类似的东西。我认为将它们分开放在不同模块里有些奇怪。 - sudo
@sudo:我也同意这个分工对我来说不太清楚 :( - Matthieu M.

1
我放弃了转换的东西。在Rust中,*mut(原始指针)与C指针非常相似,所以这很容易:
#[repr(C, packed)] // necessary
#[derive(Debug, Copy, Clone)] // not necessary
struct MyStruct {
    foo: u16,
    bar: u8,
}

fn main() {
    let v: Vec<u8> = vec![1, 2, 3];
    let buffer = v.as_slice();
    let mut s_safe: Option<&MyStruct> = None;
    let c_buf = buffer.as_ptr();
    let s = c_buf as *mut MyStruct;
    unsafe {
        let ref s2 = *s;
        s_safe = Some(s2);
    }
    println!("here is the struct: {:?}", s_safe.unwrap());
}

那里的unsafe标签并不是开玩笑,但我知道我的缓冲区已被填充,并会在后面采取适当的字节序预防措施。

1
unsafe 块内,不需要前两行出现。最好将它们移出来以尽量减少潜在的不安全代码量。此外,由于您不知道缓冲区的大小,您需要确保它足够大,否则可能会引发 panic。这里有一个例子 - Peter Hall
谢谢,这个例子比我的好看多了。我忘记利用 Rust 对向量大小的了解来使我的代码更安全了。 - sudo

1

我目前正在使用:

unsafe { transmute::<[u8; 0x70], Header>(header_data) };

这当然是不安全的,但运行良好。

let mut header_data = [0; 0x70];
reader.seek(SeekFrom::Start(0))?;
reader.read_exact(&mut header_data)?;
let header = unsafe { transmute::<[u8; 0x70], Header>(header_data) };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接