从文件中读取原始结构的最佳方法

Best way to read a raw struct from a file

背景(可跳过)

在 linux 上,文件 /var/run/utmp 包含几个 utmp 结构,每个结构都是原始二进制格式,在文件中一个接一个。 utmp本身就是一个比较大的(在我的机器上是384字节)。我正在尝试将此文件读取为原始数据,并且他们在数据有意义之后实施检查。我对 Rust 并不陌生,但这是我第一次真正体验到不安全的一面。

问题陈述

我有一个包含几个 c sturct utmps (docs) 的文件。在 Rust 中,我想将整个文件读入 Vec<libc::utmpx> 的数组中。更具体地说,给定一个 reader 打开这个文件,我怎么能读一个 struct utmp?

到目前为止我有什么

下面是 read_raw 的三种不同实现,它接受 reader 和 returns 和 RawEntry(我对 struct utmp 的别名)。哪种方法最正确?我正在尝试编写尽可能高效的代码,而且我担心 read_raw0 如果涉及 memcpys,可能会比其他代码慢。 best/fastest 完成此行为的方法是什么?

use std::io::Read;
use libc::utmpx as RawEntry;

const RawEntrySize = std::mem::size_of::<RawEntry>();
type RawEntryBuffer = [u8; RawEntrySize];

/// Read a raw utmpx struct
// After testing, this method doesn't work
pub fn read_raw0<R: Read>(reader: &mut R) -> RawEntry {
    let mut entry: RawEntry = unsafe { std::mem::zeroed() };
    unsafe {
        let mut entry_buf = std::mem::transmute::<RawEntry, RawEntryBuffer>(entry);
        reader.read_exact(&mut entry_buf[..]);
    }
    return entry;
}

/// Read a raw utmpx struct
pub fn read_raw1<R: Read>(reader: &mut R) -> RawEntry {
    // Worried this could cause alignment issues, or maybe it's okay 
    // because transmute copies
    let mut buffer: RawEntryBuffer = [0; RawEntrySize];
    reader.read_exact(&mut buffer[..]);
    let entry = unsafe {
        std::mem::transmute::<RawEntryBuffer, RawEntry>(buffer)
    };
    return entry;
}

/// Read a raw utmpx struct
pub fn read_raw2<R: Read>(reader: &mut R) -> RawEntry {
    let mut entry: RawEntry = unsafe { std::mem::zeroed() };
    unsafe {
        let entry_ptr = std::mem::transmute::<&mut RawEntry, *mut u8>(&mut entry);
        let entry_slice = std::slice::from_raw_parts_mut(entry_ptr, RawEntrySize);
        reader.read_exact(entry_slice);
    }
    return entry;
}

注意:经过更多测试,似乎 read_raw0 不起作用。我相信这是因为 transmute 创建了一个新缓冲区而不是引用结构。

这是我想出的,我想它应该和读取单个条目一样快。它遵循您上一个条目的精神,但避免了转化(将 &mut T 转化为 *mut u8 可以通过两次转换来完成:t as *mut T as *mut u8)。它还使用 MaybeUninit 而不是 zeroed 更明确一点(程序集在优化后可能是相同的)。最后,该函数无论哪种方式都是不安全的,因此我们不妨将其标记为不安全,并取消 unsafe 块。

use std::io::{self, Read};
use std::slice::from_raw_parts_mut;
use std::mem::{MaybeUninit, size_of};

pub unsafe fn read_raw_struct<R: Read, T: Sized>(src: &mut R) -> io::Result<T> {
    let mut buffer = MaybeUninit::uninit();
    let buffer_slice = from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, size_of::<T>());
    
    src.read_exact(buffer_slice)?;
    Ok(buffer.assume_init())
}