When is flushing STDOUT required?

Hey there!

I know that flushing forces the process to send all remaining buffer data, but I wonder when it is actually required.

I have the following code for testing right now:

pub fn main(init: std.process.Init) !void {
    std.debug.print("Debug print works\n", .{});

    var stdout_buf: [2048]u8 = undefined;

    var stdout_writer = IO.File.Writer.init(IO.File.stdout(), init.io, &stdout_buf);

    const out = &stdout_writer.interface;

    std.debug.print("{}\n", .{out.end});

    try out.print("Hello World!\n", .{});
    // try out.flush();
    std.debug.print("{}\n", .{out.end});

    return;
}

Running this gives the following output:

> zig run src/main.zig
Debug print works
0
13

Uncommenting the try out.flush() actually gives the expected output:

> zig run src/main.zig
Debug print works
0
Hello World!
0

Now I can’t remember having to flush for any output to appear in either any previous Zig release, nor in other languages like C, C++ etc.

Thus, my question: When is flushing actually required?

And some additional questions:

  • Why does the debug print work even without flushing? Does it print to STDERR?
  • Is there any issue with the provided code that causes this behaviour, or is that intended?

Thanks in advance!

1 Like

If you don’t control the file descriptor (you don’t open and close it) then flush before exiting.
If you want the written contents displayed, transmitted or saved.

Yes it prints to STDERR.
There are actually more than buffering and the descriptor output; see this video about printing and logging:

It is intended, because there is a buffer.

2 Likes

… unless you used std.io.BufferedWriter.

… which have hidden control flow that flush it for you at the end of the line.

It does not buffer.

Yes.

No.

Yes.

3 Likes

Thanks for the help so far!

Now what’s the “good practice” of doing prints? Using a buffer and flushing every time, or doing it without a buffer? Also, how would I do it without a buffer? I’ve only ever come across the implementation that I showed above.

1 Like

It does buffer, or did, now it is an implementation detail of Io, all the implementations currently buffer, excluding kqueue which has not implemented debug printing[1] at all.


  1. debug printing is not implemented directly, instead it is the locking and unlocking of stderr. ↩︎

Yes, that’s best practice. Flush whenever you’ve finished writing and want the user to see what you’ve written. If you’re writing multiple lines at once you only need to flush after the last one.

4 Likes

Alright, thanks much!

I assume I should also definitely flush before the buffer runs full, or how is this handled?

1 Like

The write functions will handle any intermediate flushes for a full buffer automatically. You only need to worry about the final flush.

5 Likes

Okay. Both of us are correct. At least if we are talking about 0.16.

You are technically[1] correct. std.debug.print() does define buffer variable in the local scope.

I am ordinarily correct. The buffer does not live longer then std.debug.print() call. unlockStderr() calls flush().

What does this documentation mean then:

This is a low-level debugging primitive that bypasses the Io interface


  1. Worst Kind of Correct ↩︎

1 Like

if you read the source, it uses the global std.Options.debug_io, instead of taking it as a parameter. You are still able to override that, see std.Options for how.

That io is what implements most of the logic behind locking/unlocking stderr.

2 Likes