iozig

Basics of Zig Console I/O


I am trying to figure out if it is possible to use Zig's IO library to read in and print out unbuffered content.

So, I know zig has std.debug.print which handles formatted output in debug execution. std.io.getStdIn().reader() which can be used to read a "buffered" input, and std.io.getStdOut().write() to print a non-formatted output string.

The code that I am running during these experiments:

const std = @import("std");

pub fn main() !void {
    // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
    var input: [5]u8 = undefined;
    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
    const stdin = std.io.getStdIn().reader();
    const op = try stdin.readUntilDelimiter(&input, '\n');
    for (op) |c| {
        if (c == '\r') {
            _ = try std.io.getStdOut().write("]]\\r[[");
        } else {
            std.debug.print("]]{c}[[\n", .{c});
        }
    }
    if (op[4] == 0) { // Program breaks here because zig doesn't use '\0'.
        _ = try std.io.getStdOut().write("End of line character detected!");
    }
}

Solution

  • I mean this is a bunch of questions at once but:

    Is there an easier way to print just a string without the usage of try or formatting?

    without formatting, yes. That's std.io.getStdOut().write as you've already discovered. You don't need to use try specifically but write is a function that can fail and zig forces you to handle errors or explicitly discard them if you don't care about your program breaking/are entirely certain that in your situation nothing can possibly go wrong. That's by design.

    Is there also a way to accept an unbuffered input from the command line, such as using *u8 rather than a fixed size string? The reason behind this question is that using limited size buffers can lead to buffer overflows which can break your program and create security issues where bad actors can put arbitrary characters into the buffer to force writing malicious bytes to other areas on the program's heap

    You have this backwards apparently. First of all, read is already unbuffered. And no, this can not lead to buffer overflows. The standard read will never read more than the size of the buffer you pass in, it's not possible to cause a buffer overflow using this unless you manually construct an invalid slice.

    Finally, with the code above, I can input goo but not goog and googl. This is expected due to the buffer also containing \r\n from when the user presses enter in the console. I was wondering if there is a way to discard both \r and \n? This makes the buffer's size require a +2 to size on Windows-based OS to account for these characters.

    Just make a bigger buffer. Also, when reading input that's not guaranteed to follow some specific protocol, you always check the length of what read returns; If it's the full size of the buffer chances are there's more input. There's I believe no function in the standard library that checks for all possible combinations of line ending characters; there are more than just \r and \n anyway, and it's somewhat a matter of opinion (or rather, it's somewhat situational) which ones you should care about / interpret as newlines (for example \r on its own is not typically considered a line ending in Windows or Linux; On Mac's traditionally it would be, even though since OS X the underlying system is a BSD there are still some old programs that create or expect \r line endings).