The Quick Answer
I believe the following is what you’re after:
const std = @import("std");
const DataStruct = packed struct { a: u8 = 0, b: u8 = 0, c: u8 = 0, d: u8 = 0 };
pub fn main() !void {
var binf = try std.fs.cwd().openFile("someData.bin", .{});
defer binf.close();
const binf_reader = binf.reader();
const data_struct = try binf_reader.readStruct(DataStruct);
std.debug.print("My struct: {any}\n", .{data_struct});
}
Where someData.bin
is a binary file containing 4 bytes in this order:
0x1 0x2 0x3 0x4
You will see (depending on your system’s endianness!!) the following output:
My struct: main.DataStruct{ .a = 1, .b = 2, .c = 3, .d = 4 }
If you have weird endianness, check out the equivalent:
binf_reader.readStructEndian()
The Longer Answer
The reason what you’re after is “so easy” in C is because C doesn’t care in the slightest what the memory location you’re reading things into is, it will just do exactly as you ask. This can be problematic because:
- Maybe you accidentally gave it the wrong pointer offset (off by 1 error!)
- You forgot to specify the struct was packed/has specific alignment using
__attribute__((packed, aligned(4)))
, so crucial data ends up in padding bytes and your actual struct members are garbage
The nice thing about Zig is it will still absolutely let you do this if that’s what you’re after, but if you’re using a standard library function it generally has some more guard rails around it. For instance, take away the packed
specifier from the struct in this example and look at the compiler error:
/usr/local/bin/lib/std/debug.zig:412:14: error: reached unreachable code
if (!ok) unreachable; // assertion failure
^~~~~~~~~~~
/usr/local/bin/lib/std/io/Reader.zig:329:20: note: called from here
comptime assert(@typeInfo(T).Struct.layout != .auto);
~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Amazing! Structs in Zig, by default, do not have guaranteed memory layout so you can’t know what order in memory the fields are going to be in. But, through the magic of comptime, the standard library guarded against this footgun for you!
Check out the source code for readStruct
in the standard library for a good example of the low level calls required to read bytes directly into an arbitrary data object. A related function relevant to your use case you might want to check out is std.mem.asBytes()
.
A General Word of Warning
Many, many, people post on Stack Overflow for a grab-bag of languages essentially asking what comes down to a serialization/deserialization problem:
“How do I read/write bytes into/from a data structure in my language?”
This is a weirdly non-trivial problem as it depends on your system’s endianness, the endianness of the byte source (file, network?), how your particular language lays out data structures in memory, and what the alignment requirements are of your system.
So, you absolutely can do what you’re after, just make sure you know exactly how bytes are handled on both ends of the process.