14

I'm following this blog post on 'comptime' in Zig.

The following line no longer compiles in Zig 0.6.0.

const user_input = try io.readLineSlice(buf[0..]);

Below is the full function:

fn ask_user() !i64 {
    var buf: [10]u8 = undefined;
    std.debug.warn("A number please: ");
    const user_input = try io.readLineSlice(buf[0..]);
    return fmt.parseInt(i64, user_input, 10);
}

What is the equivalent in the current version (of getting user input)?

3 Answers 3

18

Post edited for Zig 0.15.1.


We can read the input line with the method streamDelimiterLimit of stdin.reader(buf) .interface.

// On the top of your file
const std = @import("std");

// Inside your function

// Initialize the stdin
// There is no global variables, so you need to do it 
var stdin_buffer: [1024]u8 = undefined;
var stdin = std.fs.File.stdin().reader(&stdin_buffer);

// A fix size is fine:
// you don't always want the user to exhaust all the system memory
var line_buffer: [1024]u8 = undefined;
var w: std.io.Writer = .fixed(&line_buffer);

// Read an input until "\n" or end of file, and write it to the buffer
const line_length = try stdin.interface.streamDelimiterLimit(&w, "\n", .unlimited);

// Your input line:
const input_line = line_buffer[0..line_length];

If you want to work on windows as well, you can use the following function:

const std = @import("std");
const builtin = @import("builtin");

fn read_line(line_buffer: []u8, input: *std.io.Reader) ![]u8 {    
    var w: std.Io.Writer = .fixed(line_buffer);

    var line_length = try input.streamDelimiterLimit(&w, '\n', .unlimited);
    std.debug.assert(line_length <= line_buffer.len);

    // Consume the '\n' with takeByte and throw it away
    var next_byte: ?u8 = null;
    if (input.takeByte()) |value| {
        next_byte = value;
    } else |err| switch (err) {
        error.EndOfStream => {
            std.debug.assert(next_byte == null);
        },
        else => return err,
    }
    std.debug.assert(next_byte == '\n' or next_byte == null);

    // Trim \r on windows
    // @see @Sawcce's answer: https://stackoverflow.com/a/75912768/9959510
    // @see https://en.wikipedia.org/wiki/Newline#Representation
    if (builtin.os.tag == .windows) {
        if (line_length > 0) {
            if (next_byte == '\n' and line_buffer[line_length - 1] == '\r') {
                line_length -= 1;
            }
        }
    }

    return line_buffer[0..line_length];
}

Example

fn ask_number(line_buffer: []u8, input: *std.io.Reader, output: *std.io.Writer) !i64 {
    try output.writeAll("A number please: ");
    // Flush to write all the message before read the line
    try output.flush();

    const input_line = try read_line(line_buffer, input);

    // Attempt to parse the line into an i64 in base 10.
    // If parsing fails (not a valid number or overflow),
    // propagates the parsing error.
    return std.fmt.parseInt(i64, input_line, 10);
}

pub fn main() !void {
    var stdin_buffer: [1024]u8 = undefined;
    var stdout_buffer: [1024]u8 = undefined;
    var stdin = std.fs.File.stdin().reader(&stdin_buffer);
    var stdout = std.fs.File.stdout().writer(&stdout_buffer);

    // A capacity to hold any i64 + '\r'
    var line_buffer: [1024]u8 = undefined;

    const num = try ask_number(line_buffer[0..], &stdin.interface, &stdout.interface);

    try stdout.interface.print("Your number: {}.", .{num});

    // Flush only in success path, no defer
    try stdout.interface.flush();
}

For the new/upcoming IO API, see Don't Forget To Flush by Andrew Kelley.

For the CRLF, see @Sawcce's answer.

See also: Zig 0.15.1 documentation, std.fs.File and std.io.Reader.

Sign up to request clarification or add additional context in comments.

6 Comments

this is really complicated for a simple use such as reading console input. Is there a better way of doing this keeping in mind Zig is a drop in replacement for C which has Scanf?
There's no scanf at the moment, see issue 12161. I would also recommend taking a look at Q: Disadvantages of scanf.
Does 'parseInt' consumes the buffer (user_input in that case)?
Since the latest version 0.15.0, this getStdIn is removed from zig, so once again, what is the new way to read a user input?
the code crashed when i try to read in a second line. i'm not smart enough to know if this is the right thing to do but i added input.tossBuffered(); to the end of the read line function in an attempt to clear out whatever is still there and that works 🤷.
You're right, read_line is unable to parse more than one line because the '\n' is still there. I fix it with input.takeByte().
4

Even though Cristobal Montecino's answer might work, because windows uses CR/LF line endings, you might also need to trim the end of the string:

// We can read any arbitrary number type with number_type
fn get_number(comptime number_type: type) !number_type {
    const stdin = std.io.getStdIn().reader();

    // Adjust the buffer size depending on what length the input
    // will be or use "readUntilDelimiterOrEofAlloc"
    var buffer: [8]u8 = undefined;

    // Read until the '\n' char and capture the value if there's no error
    if (try stdin.readUntilDelimiterOrEof(buffer[0..], '\n')) |value| {
        // We trim the line's contents to remove any trailing '\r' chars 
        const line = std.mem.trimRight(u8, value[0..value.len - 1], "\r");
        return try std.fmt.parseInt(number_type, line, 10);
    } else {
        return @as(number_type, 0);
    }
}

Comments

0

Here's a common stdin/stdout setup on linux for Zig 0.15.2 (should apply to all 0.15.X versions):

const std = @import("std");

var input_buf: [1024]u8 = undefined;
var stdin_reader = std.fs.File.stdin().reader(&input_buf);

// This is what you pass around to functions that take a std.Io.Reader
const stdin = &stdin_reader.interface;

// Same goes for stdout
var output_buf: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&output_buf);
const stdout = &stdout_writer.interface;

fn ask_user(reader: *std.Io.Reader, writer: *std.Io.Writer) !i64 {
    try writer.print("A number please: ", .{});
    // Use flush to ensure output is written immediately
    // Otherwise, output is only written if output_buf is full.
    try writer.flush();

    // Reads data into input_buf and returns a slice to it
    // This slice is only valid until the next "peek" operation (take does a peek)
    const line = try reader.takeDelimiterExclusive('\n');
    return std.fmt.parseInt(i64, line, 10);
}

pub fn main() !void {
    const value = try ask_user(stdin, stdout);

    try stdout.print("You wrote: {d}\n", .{value});
    // don't forget to flush
    try stdout.flush()
}

This code is good for general applications. It should be noted that this code does not handle error.StreamTooLong. This will occur if any single input line is longer than the size of stdin_buf, which is 1024 in this code. This is perfectly fine for applications where you control the input buffer size.

2 Comments

I've have had to change try writer.print("A number please: "); to try writer.print("A number please: ", .{}); to compile it with zig-0.15.2
Good catch. Updated the answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.