I’m converting a bunch of data in structs from one type to another; more or less a functional map operation. I have a list of users from the network (NetworkUser) with a first and last name. I loop over them to create a plain User who only has a full_name.
Problem is, I’m getting a memory leak when I format the first and last names into a full name. Since I’m using an allocator to format inside the loop, I’m not sure how I can free it:
// zig 0.14.1
const std = @import("std");
const Allocator = std.mem.Allocator;
const NetworkPerson = struct { first_name: []const u8, last_name: []const u8 };
const Person = struct { full_name: []const u8 };
fn genPeople(allocator: Allocator, network_peeps: []NetworkPerson) ![]Person {
var people = try std.ArrayListUnmanaged(Person).initCapacity(allocator, network_peeps.len);
for (network_peeps) |np| {
const full_name = try std.fmt.allocPrint(
allocator,
"{s} {s}",
.{ np.first_name, np.last_name },
);
try people.append(allocator, Person{ .full_name = full_name });
}
return people.toOwnedSlice(allocator);
}
test "generates people from first and last names" {
const allocator = std.testing.allocator;
const network_peeps: [4]NetworkPerson = .{
NetworkPerson{ .first_name = "John", .last_name = "Lennon" },
NetworkPerson{ .first_name = "Paul", .last_name = "McCartney" },
NetworkPerson{ .first_name = "George", .last_name = "Harrison" },
NetworkPerson{ .first_name = "Ringo", .last_name = "Starr" },
};
const people = try genPeople(
allocator,
@constCast(network_peeps[0..]),
);
defer allocator.free(people);
try std.testing.expectEqualStrings(people[0].full_name, "John Lennon");
try std.testing.expectEqualStrings(people[1].full_name, "Paul McCartney");
try std.testing.expectEqualStrings(people[2].full_name, "George Harrison");
try std.testing.expectEqualStrings(people[3].full_name, "Ringo Starr");
}
I’ve tried deferring the free (learned from Allocating memory in a loop) but that doesn’t work because toOwnedSlice empties out the ArrayList:
);
try people.append(allocator, Person{ .full_name = full_name });
}
+ defer {
+ for (people.items) |person| {
+ // nothing here to free
+ allocator.free(person.full_name);
+ }
+ }
return people.toOwnedSlice(allocator);
}
I’ve also tried returning people.items instead with and without the defer block with the same results.
If I free the full_name inside the loop then I get a segmentation fault.
I might be able to pass in an ArrayList to the function, but I don’t love that the caller has to know implementation details about what the function does.
How can I allocate memory in a loop and free it?