New to Zig, and systems programming. What am I doing wrong here?

This is actually quite complicated to explain:

  • When you append to an ArrayList, the value you append is allocated
  • If the value you append to an ArrayList is a pointer or slice, the pointer/slice itself is what’s being allocated, not the memory it points to
  • When you call stdin.takeDelimiter(), the memory returned is guaranteed to be valid until the next time a take* function is called, but will often become undefined after one is called
  • When you append the returned memory from takeDelimiter() to your ArrayList([]const u8), what the ArrayList() holds is a pointer to memory that will later become undefined
  • This can be fixed by using std.mem.Allocator.dupe() on the memory returned by takeDelimiter(), transferring the ownership to the allocator - but it also means you’ll need to free it with the same allocator later

Here’s a complete working example:

const std = @import("std");
const Io = std.Io;

pub fn main(init: std.process.Init) !void {
	const alc = init.gpa;
	const io = init.io;

	var stdout_buffer: [1024]u8 = undefined;
	var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
	const stdout: *std.Io.Writer = &stdout_writer.interface;

	var stdin_buffer: [1024]u8 = undefined;
	var stdin_reader = Io.File.stdin().reader(init.io, &stdin_buffer);
	const stdin: *std.Io.Reader = &stdin_reader.interface;

	try stdout.print("What are you grateful for?\n", .{});
	try stdout.flush();
	// 1, declare a 5 item array.
	// 2, loop through said array until it has 5 items.
	// 3, after user input, append the string to the array
	// 4, once the length is 5, stop the loop.
	//
	var list_of_gratitudes: std.ArrayList([]const u8) = .empty;
// Now that all the sub-strings held by list_of_gratitudes are allocated,
// we must ensure we loop over them and free them before deinitting the ArrayList().
defer{
	for(list_of_gratitudes.items) |substring| alc.free(substring);
	list_of_gratitudes.deinit(alc);
}

	for(0..5) |i| {
		try stdout.print("{d}: ", .{i});
		try stdout.flush();
		const raw_line = try stdin.takeDelimiter('\n') orelse unreachable;
		const line = std.mem.trim(u8, raw_line, "\r");
		// Duplicate the line so that it'll still be valid when we access it later.
		const line_dupe = try alc.dupe(u8, line);
		try list_of_gratitudes.append(alc, line_dupe);
	}
	for (list_of_gratitudes.items, 0..) |gratitude_string, index| {
		try stdout.print("{d}: {s}\n", .{index + 1, gratitude_string});
		try stdout.flush();
	}
	try stdout.print("{s}\n", .{"Thank you, Have a nice day!"});
	try stdout.flush();
}

And here it is, condensed down to just my additions:

var list_of_gratitudes: std.ArrayList([]const u8) = .empty;
// Now that all the sub-strings held by list_of_gratitudes are allocated,
// we must ensure we loop over them and free them before deinitting the ArrayList().
defer{
	for(list_of_gratitudes.items) |substring| alc.free(substring);
	list_of_gratitudes.deinit(alc);
}
// Duplicate the line so that it'll still be valid when we access it later.
const line_dupe = try alc.dupe(u8, line);
try list_of_gratitudes.append(alc, line_dupe);