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
ArrayListis 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 atake*function is called, but will often become undefined after one is called - When you append the returned memory from
takeDelimiter()to yourArrayList([]const u8), what theArrayList()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 bytakeDelimiter(), 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);