Как читать структуру из файла в Rust?

Есть ли способ, которым я могу читать структуру непосредственно из файла в Rust? Мой код:

use std::fs::File;

struct Configuration {
    item1: u8,
    item2: u16,
    item3: i32,
    item4: [char; 8],
}

fn main() {
    let file = File::open("config_file").unwrap();

    let mut config: Configuration;
    // How to read struct from file?
}

Как я могу прочитать свою конфигурацию непосредственно в config из файла? Возможно ли это?

Ответ 1

Примечание: обновлено для стабильности Rust (1.10 на данный момент).

Здесь вы идете:

use std::io::Read;
use std::mem;
use std::slice;

#[repr(C, packed)]
#[derive(Debug)]
struct Configuration {
   item1: u8,
   item2: u16,
   item3: i32,
   item4: [char; 8]
}

const CONFIG_DATA: &'static [u8] = &[
  0xfd, 0xb4, 0x50, 0x45, 0xcd, 0x3c, 0x15, 0x71, 0x3c, 0x87, 0xff, 0xe8,
  0x5d, 0x20, 0xe7, 0x5f, 0x38, 0x05, 0x4a, 0xc4, 0x58, 0x8f, 0xdc, 0x67,
  0x1d, 0xb4, 0x64, 0xf2, 0xc5, 0x2c, 0x15, 0xd8, 0x9a, 0xae, 0x23, 0x7d,
  0xce, 0x4b, 0xeb
];

fn main() {
    let mut buffer = CONFIG_DATA;

    let mut config: Configuration = unsafe { mem::zeroed() };

    let config_size = mem::size_of::<Configuration>();
    unsafe {
        let config_slice = slice::from_raw_parts_mut(
            &mut config as *mut _ as *mut u8,
            config_size
        );
        // `read_exact()` comes from `Read` impl for `&[u8]`
        buffer.read_exact(config_slice).unwrap();
    }

    println!("Read structure: {:#?}", config);
}

Попробуйте здесь

Однако вам нужно быть осторожным, так как небезопасный код, ну, небезопасный. Здесь, в частности, после вызова slice::from_raw_parts_mut() существуют две изменяемые ручки для одних и тех же данных одновременно, что является нарушением правил псевдонимов Rust. Поэтому вы хотели бы сохранить измененный срез, созданный из структуры, в кратчайшие сроки. Я также предполагаю, что вы знаете о проблемах с контентом - приведенный выше код ни в коем случае не переносится и будет возвращать разные результаты, если они скомпилированы и запущены на разных машинах (например, ARM vs x86).

Если вы можете выбрать формат и хотите компактный двоичный файл, рассмотрите возможность использования bincode. В противном случае, если вам нужно, например, для синтаксического анализа некоторой заранее определенной двоичной структуры, byteorder crate - путь.

Ответ 2

Примечание, что следующий код не принимает во внимание любые endianness или padding и предназначен для использования с типами POD. struct Configuration должен быть безопасным в этом случае.


Вот функция, которая может читать структуру (типа pod) из файла:

use std::io::{self, BufReader, Read};
use std::fs::{self, File};
use std::path::Path;
use std::slice;

fn read_struct<T, R: Read>(mut read: R) -> io::Result<T> {
    let num_bytes = ::std::mem::size_of::<T>();
    unsafe {
        let mut s = ::std::mem::uninitialized();
        let mut buffer = slice::from_raw_parts_mut(&mut s as *mut T as *mut u8, num_bytes);
        match read.read_exact(buffer) {
            Ok(()) => Ok(s),
            Err(e) => {
                ::std::mem::forget(s);
                Err(e)
            }
        }
    }
}

// use
// read_struct::<Configuration>(reader)

Если вы хотите прочитать последовательность структур из файла, вы можете выполнить read_struct несколько раз или сразу прочитать весь файл:

fn read_structs<T, P: AsRef<Path>>(path: P) -> io::Result<Vec<T>> {
    let path = path.as_ref();
    let struct_size = ::std::mem::size_of::<T>();
    let num_bytes = try!(fs::metadata(path)).len() as usize;
    let num_structs = num_bytes / struct_size;
    let mut reader = BufReader::new(try!(File::open(path)));
    let mut r = Vec::<T>::with_capacity(num_structs);
    unsafe {
        let mut buffer = slice::from_raw_parts_mut(r.as_mut_ptr() as *mut u8, num_bytes);
        try!(reader.read_exact(buffer));
        r.set_len(num_structs);
    }
    Ok(r)
}

// use
// read_structs::<StructName, _>("path/to/file"))

Ответ 3

Как отмечает Владимир Матвеев, использование ящика для метеорологических наблюдений часто является лучшим решением. Таким образом, вы учитываете проблемы с порядком байтов, и вам не нужно иметь дело с небезопасными:

use byteorder::{LittleEndian, ReadBytesExt}; // 1.2.7
use std::{
    fs::File,
    io::{self, Read},
};

struct Configuration {
    item1: u8,
    item2: u16,
    item3: i32,
}

impl Configuration {
    fn from_reader(mut rdr: impl Read) -> io::Result<Self> {
        let item1 = rdr.read_u8()?;
        let item2 = rdr.read_u16::<LittleEndian>()?;
        let item3 = rdr.read_i32::<LittleEndian>()?;

        Ok(Configuration {
            item1,
            item2,
            item3,
        })
    }
}

fn main() {
    let file = File::open("/dev/random").unwrap();

    let config = Configuration::from_reader(file);
    // How to read struct from file?
}

Я проигнорировал [char; 8] [char; 8] по нескольким причинам:

  1. Rust char является 32-битным типом, и неясно, имеет ли ваш файл фактические кодовые точки Unicode или 8-битные значения в стиле C.
  2. Вы не можете легко проанализировать массив с помощью byteorder, вам нужно проанализировать N значений и затем создать массив самостоятельно.