Is it possible to determine if a string is literal or if it has to be freed?

96 Views Asked by At

Suppose I have a struct like

const Fruit = struct {
  name: []const u8,
};

This struct may be created programmatically, in which case the name string will have to be free at some point -- so suppose we keep track of an allocator and have a deinit method:

const Fruit = struct {
  name: []const u8,
  allocator: std.mem.Allocator,

  pub fn deinit(self: *Fruit) void {
    self.allocator.free(self.name);
    self.* = undefined;
  }
};

This works fine in most cases. However if at any point someone decides to hardcode an instance of this struct

var banana = Fruit{
  .name = "banana",
  .allocator = std.testing.allocator,
};
defer banana.deinit();

Then the call to allocator.free(self.name) above will fail, because self.name wasn't allocated by allocator (I guess).

Is it possible to detect that inside deinit() time so I don't try to free that string?

Or perhaps there is a magic way to turn the string literal into an allocated string?

What is the recommended approach here? (Please don't tell me to just not call deinit(), assume there are other things that must be de-initialized and that is not an option.)

1

There are 1 best solutions below

1
On BEST ANSWER

the "magic way" is just duping the string with an allocator when you assign it .name = alloc.dupe(u8, name), or just letting the user manage all of the strings and dont free them yourself.

you could also make your own data structure to carry that information on whether it was allocated or not:

const String = union(enum) {
  static: []const u8,
  alloced: []const u8,
};

For your case, I'd recommend doing something like this:

const Fruit = struct {
  name: []const u8,
  allocator: std.mem.Allocator,

  pub fn init(allocator: std.mem.Allocator, name: []const u8) !Fruit {
    return .{
      .name = try allocator.dupe(u8, name),
      .allocator = allocator,
    };
  }

  pub fn deinit(self: *Fruit) void {
    self.allocator.free(self.name);
    self.* = undefined;
  }
};

This way you can use Fruit like this:

var b = try Fruit.init(allocator, "banana");
defer b.deinit();

And it will always be freed correctly