Struggling to simply read from a file?

Hi,

Allow me to start by saying that I’m by no means an inexperienced programmer. I’m coming to Zig from a C++ and Rust background. I’m really liking the way of thinking and how explicit all the code is, but I have to admit that IO is currently tripping me up a lot. I cannot work out for the life of me how to read the contents of a file into a buffer. I know from trying master that this is going to change even more in an upcoming Zig version, but this is with 0.15.2. This code panics, and I simply cannot get it to work no matter what reader methods I try it seems :frowning: Any help is most certainly appreciated, I’m certain I’m missing something small. My first ever experience with zig was me making a memory corruption in 10 lines by trying to return a local []const u8 from a function, but now I fully get how strings work and love them.

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("test.txt", .{ .mode = .read_only });
    defer file.close();
    var buffer: [1024]u8 = undefined;
    const file_reader = file.reader(&buffer);
    var reader = file_reader.interface;
    try reader.readSliceAll(&buffer);
    std.debug.print("{s}\n", .{buffer});
}

Thanks in advance!

1 Like

It would be helpful to see the panic message, othewise I will be guessing just based on the code.

My guess is that you are getting an assert triggered because of aliasing. You are mixing up the uses of the different buffers.
In the code example you provided, you are using the same buffer for both the reader interface and the destination buffer. This is likely the cause of your problem.
There are a few ways around this:

Use the internal buffer directly

You can access the reader’s internal buffer and fill it using methods on the Reader. Calling fillMore will fill the buffer and then you can access the buffer from the Reader with buffered

Read into another buffer

You can create 2 buffers and use them:

    var buffer: [1024]u8 = undefined;
    const file_reader = file.reader(&buffer);
    var reader = file_reader.interface;
    var copy_buffer = [1024]u8 = undefined;
    try reader.readSliceAll(&copy_buffer);
    std.debug.print("{s}\n", .{&copy_buffer});

Note: The buffers can be different sizes

Stream into a Writer

This is a more special use case, but if you are wanting to read the whole file into a new allocated buffer, you can use Writer.Allocating and stream from the reader using Reader.stream. You could also used a Fixed Allocator as well if you don’t want allocation but want to fill a bigger buffer.

1 Like

Hi,

Sorry about that, I definitely should’ve posted the panic. I tried updating my code to use .buffered, but it still doesn’t work and gives the same panic:

thread 663050 panic: switch on corrupt value
/home/quin/.zvm/0.15.2/lib/std/fs/File.zig:1344:17: 0x1141f78 in readVec (std.zig)
        switch (r.mode) {
                ^
/home/quin/.zvm/0.15.2/lib/std/Io/Reader.zig:1074:29: 0x113efbc in fillMore (std.zig)
    _ = try r.vtable.readVec(r, &bufs);
                            ^
/home/quin/test.zig:9:24: 0x113d514 in main (test.zig)
    try reader.fillMore();
                       ^
/home/quin/.zvm/0.15.2/lib/std/start.zig:627:37: 0x113de59 in posixCallMainAndExit (std.zig)
            const result = root.main() catch |err| {
                                    ^
/home/quin/.zvm/0.15.2/lib/std/start.zig:232:5: 0x113d351 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)

Code:

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("test.txt", .{ .mode = .read_only });
    defer file.close();
    var buffer: [1024]u8 = undefined;
    const file_reader = file.reader(&buffer);
    var reader = file_reader.interface;
    try reader.fillMore();
    std.debug.print("{s}\n", .{reader.buffered()});
}

And reader should be a pointer, not a copy.

var reader = &file_reader.interface;
1 Like

Hey, your issue is with how you use the file_reader.interface field. You have to store it as a pointer, as it used @fieldParentPtr to access the implementation for each method in the interface.

So if you change your code to this, it works:

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("test.txt", .{ .mode = .read_only });
    defer file.close();
    var buffer: [1024]u8 = undefined;
    var file_reader = file.reader(&buffer);
    var reader = &file_reader.interface;
    try reader.fillMore();
    std.debug.print("{s}\n", .{reader.buffered()});
}

Notice the & infront of file_reader.interface?

1 Like

file_reader should be a var because it will be mutated (for example indirectly through using its .interface).

The second line copies the Io.Reader value out from the file_reader instance, doing this breaks its invariant of being located at the memory that is accessible via file_reader.interface, Io.Reader has a vtable and the functions within that vtable use @fieldParentPtr to convert the pointer to the file_reader.interface to a pointer to file_reader, this doesn’t work when you make a copy of .interface. You have to take the reference to the field instead.

Overall those two lines should look like this:

var file_reader = file.reader(&buffer);
const reader: *std.Io.Reader = &file_reader.interface;
1 Like

Ah, I see! This was the solution that fixed all of the problems for me, the missing & was a part of the problem for sure but I had to make file_reader var and reader const and it all works.

Thanks again! And just FYI, I don’t need the type hint here for some reason :slight_smile:

The explicit type isn’t needed but it helps keeping people from making the file_reader into a const, because if they do they will get a compile error, without the explicit type you can make file_reader a const and you don’t get the compile error directly there (instead you get it later when trying to call methods that expect a *Io.Reader, but get a *const Io.Reader instead).

So basically adding the explicit type is helpful to avoid typos or misuse, like for example forgetting the & in front of file_reader.interface.

1 Like

Ah, makes sense! I see that. Thanks a ton for all your help, I’m loving the Zig community so far :slight_smile:

1 Like