arraysstringbufferslicezig

String buffers and slices in Zig


I'm aware that Zig for now is not ideal for amateurs, but all the same I've been trying to write a simple console program that asks the user his first name, then his last name, and finally prints "Your name is x and your last name is y" just to see if I could.

I've come up with this mostly by copying and pasting examples:

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    const stdin = std.io.getStdIn().reader();

    var buf: [100]u8 = undefined;

    print("What's your first name? ", .{});
    var fname = (try stdin.readUntilDelimiterOrEof(buf[0..], '\n')).?;

    print("What's your last name? ", .{});
    var lname = (try stdin.readUntilDelimiterOrEof(buf[0..], '\n')).?;

    print("Your first name is {s} and your last name is {s}\n", .{fname, lname});
}

Now, this compiles but it doesn't do what I thought. That's because readUntilDelimiterOrEof() returns a slice, so the second call overwrites the contents of the variable fname.

I can create a variable pos that keeps track of the position in the buffer (i.e. fname.len) and then store the following input in buf[pos..] instead of buf[0..], but it's clunky and I don't think that's what a buffer should do.

Instead, I'm trying to get fname and lname to contain their copies of the slice in some form (an array?) and keep storing each user input at buf[0..], but with no great success. I've read a couple of suggestions. Assigning a new variable with fname[0..(fname.len)].* doesn't compile. It only works with a number literal, say fname[0..5].*. std.mem.copy works with (fname.len) but the string doesn't seem to be null-terminated, because the print at the end of the program prints the name and then question marks up to the size assigned to this new variable.

What would be the sane, idiomatic way of implementing this elementary program? Thank you.


Solution

  • Using an allocator would be more idiomatic:

    const std = @import("std");
    const print = std.debug.print;
    
    pub fn main() !void {
        // It can be any allocator, not just FBA
        var buffer: [128]u8 = undefined;
        var fba = std.heap.FixedBufferAllocator.init(&buffer);
        const allocator = fba.allocator();
    
        const stdin = std.io.getStdIn().reader();
        const max_size = 16;
    
        print("What's your first name? ", .{});
        var fname = (try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', max_size)).?;
        defer allocator.free(fname);
        
        print("What's your last name? ", .{});
        var lname = (try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', max_size)).?;
        defer allocator.free(lname);
        
        print("Your first name is {s} and your last name is {s}\n", .{fname, lname});
    }
    

    However, your attempt is not all that bad and has its uses. You can fix it by defining the buffer as a slice and updating it every time you write something into it:

    var memory: [100]u8 = undefined;
    var buffer: []u8 = &memory;
    
    print("What's your first name? ", .{});
    var fname = (try stdin.readUntilDelimiterOrEof(buffer, '\n')).?;
    buffer = buffer[fname.len..];
    
    print("What's your last name? ", .{});
    var lname = (try stdin.readUntilDelimiterOrEof(buffer, '\n')).?;
    buffer = buffer[lname.len..];
    
    print("Your first name is {s} and your last name is {s}\n", .{fname, lname});