std.Io.Writer.writeByte not working as I expected it to

Hi there! I’m quite new to Zig so pardon my newbie-ish question.

I have this code over here, where I’m trying to learn the new Io interfaces to read and write file but the behavior is not what I expected. My code is right here:

const std = @import("std");

pub fn main() !void {
    var file: std.fs.File = try std.fs.cwd().openFile("test", .{ .mode = .read_write });
    var buffer: [1024]u8 = undefined;
    var file_reader = file.reader(&buffer);

    var output_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&output_buffer);

    var writer_buffer: [1024]u8 = undefined;
    var file_writer = file.writer(&writer_buffer);

    {
        var i: u32 = 0;
        while (file_reader.interface.takeByte()) |char| {
            try stdout_writer.interface.print("{c}\n", .{char});
            if (i % 2 == 1) {
                try file_writer.interface.writeByte('A');
                // try file_writer.interface.flush();
                try stdout_writer.interface.print("{d}\n", .{i});
            }
            i += 1;
        } else |err| {
            if (err == error.EndOfStream) {
                try stdout_writer.interface.flush();
                try file_writer.interface.flush();
                std.debug.print("Reached end of stream.\n", .{});
            } else {
                return err;
            }
        }
    }
}

so I was expecting that if I have a file containing the string ‘xxxxxx’, running this code would change it to ‘xAxAxA’ but it instead changes to something like ‘AAAxxx…’ maybe I’m not understanding the function properly, can anyone help with this? Thank you so much!.

You don’t advance the writer when not printing A.

adding:

if (i % 2 == 1) {
\\...
} else {
  try file_writer.interface.writeByte(char);
}

should do what you want

1 Like

Writing with functions like writeByte do not write directly to the file, but instead to the buffer. The writer is only guaranteed to write the buffer to the file then you flush the buffer, which you only do at EndOfStream. At this point you write the complete buffer to the start of the file.

1 Like

@vulpesx @rpkak thank you both for the apt response and I’m assuming the while loop will only ensure the reader to advance and not the writer

True, but not the issue. This is only relevant if you rely on the reader and writer sharing a global file position, which is not the case by default. I think they assumed it was based on their code.

yes

fyi you can give while loops a continue expression, which will be executed after each iteration, including when using continue, but excluding break. This is ideal for keeping variables that depend on the iteration in sync with the loop, such as your i.
The syntax is while (...) : (i +=1) {...}.
lang ref for more information

1 Like

but is it possible to share global position for both the reader and writer? I’m having a hard time coming up with the solution just by examples and reading the standard library

Expecting xAxAxA seems like expecting a global file position.

you can use the [reader/writer]Streaming functions to create them in streaming mode, which will use the global position of the file handle instead of their own positions.

But then you will have the issue with the buffers @rpkak described.
The easy fix is to make the buffer sizes 0, but that will prevent you from using the convenient takeByte function as that requires a buffer size of at least 1, and you can’t just have a 1 sized buffer as that will result in xAxxA which is not what you want.
You can still read into a single byte sized buffer.

I recommend doing the fix I already suggested.

yes, but the buffers were not the problem. Unless they switch to using a global position.

2 Likes
const std = @import("std");

pub fn main() !void {
    var file: std.fs.File = try std.fs.cwd().openFile("test", .{ .mode = .read_write });
    var buffer: [1]u8 = undefined;
    var file_reader = file.readerStreaming(&buffer);

    var output_buffer: [1024]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&output_buffer);

    var file_writer = file.writerStreaming(&.{});


    {
        var i: u32 = 0;
        while (file_reader.interface.takeByte()) |char| {
            try stdout_writer.interface.print("{c}\n", .{char});
            if (i % 2 == 1) {
                try file_writer.interface.writeByte('A');
                // try file_writer.interface.flush();
                try stdout_writer.interface.print("{d}\n", .{i});
            }
            i += 1;
        } else |err| {
            if (err == error.EndOfStream) {
                try stdout_writer.interface.flush();
                try file_writer.interface.flush();
                std.debug.print("Reached end of stream.\n", .{});
            } else {
                return err;
            }
        }
    }
}

This changes the output to xxAxxA. The reason it is not xAxAxA is, that it always reads twice and then writes once (which overwrites the third x).

1 Like

thank you for the code example!, I think I sorta understand it now (need more self experimentation for sure)

@vulpesx sorry for the follow up question, but is it possible to move the position back to 0 or possibly other index? or am I just better off using not using takeByte in this case?

The interfaces don’t support seeking, but the file reader/writer implementations do have seeking functions.

Best to avoid the seeking functions on the file handle directly, as that will only work if the reader/writer is in streaming mode.

I do encourage you to try to not use implementation specific functionality, as it reduces the portability and ease of testing your code. Even if you don’t need that here, it’s good practice for when you do.

got it, thanks!