Consider the following code:
const std = @import("std");
const expect = std.testing.expect;
// Standin function for one which
// sometimes returns slice-literals (instantiated with `&.{...}` syntax),
// and sometimes slices built with an allocator
// (which can thus be `free`'d)
//
// Thanks to https://stackoverflow.com/a/77248553 for showing me the
// slice-literal approach
fn buildSlice(n: u8, allocator: std.mem.Allocator) []const u8 {
return switch (n) {
0 => &.{ 72, 69, 76, 76, 79 },
else => {
var op = std.ArrayList(u8).init(allocator);
for (0..n) |i| {
const value: u8 = @truncate(i);
op.append(64 + value) catch unreachable;
}
return op.toOwnedSlice() catch unreachable;
},
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var slices = std.ArrayList([]const u8).init(allocator);
for (0..7) |i| {
const index: u8 = @truncate(i);
slices.append(buildSlice(6 - index, allocator)) catch unreachable;
}
const slice_of_slices = slices.toOwnedSlice() catch unreachable;
for (slice_of_slices) |slice| {
std.debug.print("Received slice {s}", .{slice});
allocator.free(slice); // Will fail on the last one
std.debug.print(", and freed it\n", .{});
}
}
which gives this output when run:
Received slice @ABCDE, and freed it
Received slice @ABCD, and freed it
Received slice @ABC, and freed it
Received slice @AB, and freed it
Received slice @A, and freed it
Received slice @, and freed it
Received slice HELLOBus error at address 0x106f62900
???:?:?: 0x106f61470 in ___atomic_compare_exchange_16 (???)
Unwind error at address `scratch:0x106f61470` (error.InvalidUnwindInfo), trace may be incomplete
I’m reasonably sure that the Bus error
is due to the attempt to free the slice literal "HELLO" / &.{ 72, 69, 76, 76, 79 }
. Fair enough - if a slice was not created with an allocator, I shouldn’t expect to be able to free
it with that same allocator.
I expect that the idiomatic way to achieve what I’ve done here would be to return not a []const u8
from buildSlice
, but some custom struct
which exposes a .deinit
method - but, for the purposes of learning, how could I implement this approach? Is there a way to do something like:
...
for (slice_of_slices) |slice| {
std.debug.print("Received slice {s}", .{slice});
if (was_object_created_with_this_allocator(allocator, slice)) {
allocator.free(slice);
} else {
free_slice_literal(slice)
}
std.debug.print(", and freed it\n", .{});
}
or
for (slice_of_slices) |slice| {
std.debug.print("Received slice {s}", .{slice});
if (!(is_slice_literal(slice)) {
allocator.free(slice);
} else {
free_slice_literal(slice)
}
std.debug.print(", and freed it\n", .{});
}
?