I am guessing that usizeToStr(idx)
returns a []const u8
?
This makes me question what is going on here (your code doesn’t show us) either usizeToStr
uses some kind of buffer or allocator that has a longer lifetime than the call to usizeToStr
or you are using memory that is local to usizeToStr
which means that the returned string is always invalid by the time it is received (plus minus some “luck” where the memory isn’t immediately overwritten by somebody else using it for something new)
[]const u8
is a slice, which is essentially a pointer and a length, my guess is you end up with const ridx
where ridx.ptr
is pointing to stack memory that no longer belongs to you.
I think you should pass a buffer or an allocator to usizeToStr
and change the function to store the result of the conversion in the buffer or allocate memory for it with the allocator, or better just use std.fmt.bufPrint
or std.fmt.allocPrint
instead.
However you still need something to take ownership of the resulting string, judging from l.name
and l.text
just being passed to addRows
your grd
rows probably only contain string slices that are either constants embedded into your executable for example string literals or other referenced strings that often just happen to have a long enough lifetime by being owned by some other datastructure.
One way would be to add an ArrayList to grd
that holds all the strings that are dynamically allocated and then add allocated strings to that list when you allocate one and free those when the grd
is destroyed, but that may be a lot of manual work.
Another way would be to make the addRows function copy every string given to it for example into an arena and then free that when it is destroyed, that way you don’t have to distinguish between static/global lifetimes, however I am unsure about the arena, because if you have trouble managing lifetimes it could make things to appear like they are working, when in reality you are just leaking memory and haven’t noticed.
I think until you are comfortable managing memory manually you probably should avoid arenas and instead make sure to use the GeneralPurposeAllocator
to make sure you detect leaks.
Anyways with the info you have given, we are missing a lot of context, it is always easier to help if you provide us with a small program that illustrates what you are doing, because then it is way easier to point out simple errors like use of a temporary value outside its scope.
So here I made up a minimal example, with your code rewritten:
const std = @import("std");
const Label = struct {
name: []const u8,
text: []const u8,
};
const SomeDataStructure = struct {
label: std.ArrayList(Label),
fn init(allocator: std.mem.Allocator) SomeDataStructure {
return .{
.label = std.ArrayList(Label).init(allocator),
};
}
fn deinit(self: *SomeDataStructure) void {
self.label.deinit();
}
};
const Strings = std.ArrayList([]const u8);
fn Grid(comptime Columns: comptime_int) type {
return struct {
const Self = @This();
pub const Row = [Columns][]const u8;
pub const Rows = std.ArrayList(Row);
columns: Row,
rows: Rows,
dynamic_strings: Strings,
pub fn init(allocator: std.mem.Allocator, columns: Row) Self {
return .{
.columns = columns,
.rows = Rows.init(allocator),
.dynamic_strings = Strings.init(allocator),
};
}
pub fn deinit(self: *Self) void {
self.rows.deinit();
for (self.dynamic_strings.items) |string| {
self.dynamic_strings.allocator.free(string);
}
self.dynamic_strings.deinit();
}
pub fn addRow(self: *Self, row: Row) !void {
try self.rows.append(row);
}
// helper method
fn printRow(row: Row) void {
for (row) |str| {
std.debug.print("| ", .{});
const short_str = if (str.len > 40) str[0..40] else str;
std.debug.print("{s: <40} ", .{short_str});
}
std.debug.print("|\n", .{});
}
pub fn print(self: *const Self) void {
printRow(self.columns);
for (self.rows.items) |row| printRow(row);
}
};
}
fn populateGrid(allocator: std.mem.Allocator, grid: *Grid(3), vpnl: SomeDataStructure) !void {
for (vpnl.label.items, 0..) |l, idx| {
const ridx = try std.fmt.allocPrint(allocator, "{d}", .{idx});
errdefer allocator.free(ridx);
try grid.dynamic_strings.append(ridx);
// const short_text = if (l.text.len > 40) l.text[0..39] else l.text;
// 0..40 is a range [0 to 40) where 0 is included and 40 is excluded
// so there is no need to subtract 1
const short_text = if (l.text.len > 40) l.text[0..40] else l.text;
try grid.addRow(.{ ridx, l.name, short_text });
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer {
switch (gpa.deinit()) {
.leak => @panic("leaked memory"),
else => {},
}
}
var vpnl = SomeDataStructure.init(allocator);
defer vpnl.deinit();
try vpnl.label.append(.{ .name = "Oolong Tea", .text = "Half fermented green tea giving it a rich flavor..." });
try vpnl.label.append(.{ .name = "Cheap Supermarket Tea bags", .text = "this is the crap powder full with dirt and other stuff that remains when the good tea is cleaned from dirt" });
try vpnl.label.append(.{ .name = "short", .text = "this is short" });
var grid = Grid(3).init(allocator, [_][]const u8{ "index", "name", "short text" });
defer grid.deinit();
try populateGrid(allocator, &grid, vpnl);
grid.print();
}
Output:
| index | name | short text |
| 0 | Oolong Tea | Half fermented green tea giving it a ric |
| 1 | Cheap Supermarket Tea bags | this is the crap powder full with dirt a |
| 2 | short | this is short |