1

Given an array of structs in Rust consisting of POD types, how do I write it to a disk file that can then be fread() in C code?

This would need to take into account any padding and packing necessary to allow fread() to succeed.

Michael Labbé
  • 12,017
  • 4
  • 27
  • 36
  • 1
    Not sure how or what Rust does in terms of structure packing but, though it may be inefficient, the *safest* way would be to write/read each structure member individually (a function to do so would be trivial). Note that, even between different C builds and/or platforms, structure packing may vary. – Adrian Mole Mar 06 '22 at 16:13
  • ... and there will potentially be issues with different endiannesses. – Adrian Mole Mar 06 '22 at 16:17
  • @AdrianMole: If you are careful to avoid any unnamed padding byte between your fields, and you declare your struct as `repr(C)` and you write in in Rust and read it in C in the _same architecture_, then maybe you can cast a slice of POD into a slice of u8 and write that to the file. – rodrigo Mar 06 '22 at 16:18
  • Can you expand on what went wrong with your starting attempt at solving this problem yourself? – Shepmaster Mar 07 '22 at 21:47
  • It looks like your question might be answered by the answers of [How to convert 'struct' to '&\[u8\]'?](https://stackoverflow.com/q/28127165/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Mar 07 '22 at 21:52
  • While converting a struct to a &[u8] is a meaningful part of the solution, it doesn't address the packing issues that would occur from writing an AOS, nor does it talk about writing the result to the file. – Michael Labbé Mar 07 '22 at 22:16

1 Answers1

1
  1. Define your struct using the C representation.
  2. Create an array of the structs.
  3. Convert the array value to a &[u8] using unsafe code.
  4. Write the &[u8] to a file.
use std::{fs, mem, slice};

#[repr(C)]
struct Datum {
    age: u8,
    height: i32,
}

fn main() {
    let data = [
        Datum { age: 0, height: 0 },
        Datum { age: 42, height: 99 },
    ];

    let p = data.as_ptr().cast();
    let l = data.len() * mem::size_of::<Datum>();
    // I copied this code from Stack Overflow and forgot to
    // document why it's actually safe and I probably shouldn't
    // use this code until I explain it.
    let d = unsafe { slice::from_raw_parts(p, l) };

    fs::write("data.bin", d).unwrap();
}

main.c

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

const size_t ARRAY_LEN = 2;

struct Datum {
  uint8_t age;
  int32_t height;
};

int main() {
  FILE *file = fopen("data.bin", "rb");
  if (!file) {
    perror("Unable to open file");
    return EXIT_FAILURE;
  }

  struct Datum data[ARRAY_LEN];
  size_t count = fread(&data, sizeof(struct Datum), ARRAY_LEN, file);
  if (count != ARRAY_LEN) {
    fprintf(stderr, "Could not read the entire array\n");
    return EXIT_FAILURE;
  }

  for (int i = 0; i < ARRAY_LEN; i++) {
    struct Datum *datum = data + i;
    fprintf(stderr, "age: %d\n", datum->age);
    fprintf(stderr, "height: %d\n", datum->height);
  }

  if (0 != fclose(file)) {
    perror("Unable to close file");
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
$ cargo run -q
$ cc -Wall -pedantic main.c -o main && ./main
age: 0
height: 0
age: 42
height: 99

See also:

consisting of POD types

This term has no well-defined meaning in Rust. You will want to search for alternative terms.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • good explanation: https://adventures.michaelfbryan.com/posts/deserializing-binary-data-files/ – sealor Jul 29 '23 at 07:49