Stdout.write does nothing if buffer is too small in first write

I am a bit confused if the following is intended or a bug.
I have the following code:

const std = @import("std");
var buffer: [5]u8 = undefined;
var writer = std.fs.File.stdout().writer(&buffer);
const stdout = &writer.interface;
pub fn main() !void {
    const nfirst = try stdout.write("first\n");
    std.debug.print("{}\n", .{nfirst});
    try stdout.flush();
    const nsecond = try stdout.write("second #######\n");
    std.debug.print("{}\n", .{nsecond});
    try stdout.flush();
    const nthird = try stdout.write("third ########\n");
    std.debug.print("{}\n", .{nthird});
    try stdout.flush();
}

The output of it is:

0
second #######
15
third ########
15

But I would have thought it should be:

6
first
second #######
15
third ########
15

Once I make the buffer size big enough to hold for the first write, the right output is produced, but it seems that the first write is lost if the buffer is too small.

The std.Io.Writer.write function is simply allowed to only write a portion of the provided bytes for whatever reason the underlying implementation sees fit, as long as it reports the number of bytes actually written, even if that number is zero. It is the caller’s responsibility to make sure the output stream is not corrupted by this.

If you want to make sure that the whole slice is written before the function returns, you need to use std.Io.Writer.writeAll (which—when you look at the implementation—actually only calls std.Io.Writer.write in a while loop until all bytes are written).

3 Likes

@spiffyk explained that the contract of the interface allows this behaviour.

But the reason std.File.Writer does this is that by default File.Reader/Writer are in a mode that does positional read/writes with an internally tracked position. However, stdio are fifo files, meaning they do not allow positional reads/writes which the reader/writer detects via an error from the syscall and changes modes, but it does not reattempt the read/write and instead aborts.

With that it should still print “first\n”, the reason it doesn’t is that the buffer is too small, text is 6 bytes because of ‘\n’, but the buffer is only 5, so it skips putting it into the buffer and goes straight to the implementation, but that doesn’t do anything for reasons already explained, and it’s not in the buffer because it’s too large so it is lost.

you can create a File.Reader/Writer in streaming mode with File.[reader/writer]Streaming(buffer) which will solve this issue.

6 Likes

Ah ok thanks :blush: