I get “thread 146758 panic: reached unreachable code”. After debugging, it seems that the defer in the if block frees the memory of l which in turn frees the memory of result such that it becomes the empty string "". How can I make the language function return "en"?
edit: I am porting a project to Zig 0.14 for learning purposes, so this is code that was supposed to work on Zig 0.9.1 or 0.10. Please let me know if you spot an older pattern and if you recommend a newer pattern.
You should create the DebugAllocator inside main, and pass it into your function. Then the result can be freed and the allocator deinitialized in main.
There are two problems. The first, as @n0s4 points out, is that the allocator only lives inside the language() function. Memory allocated by a DebugAllocator can’t normally outlive the allocator itself. If it does, you have a memory leak and will fail the assert(debug_allocator.deinit() == .ok) assertion.
Instead, initialize and deinitialize the allocator in your main() function and pass it as an argument to language().
The second problem is that you free the environment variable value before the substring is returned, which means that result will point to freed memory. To fix this, you could use the allocator to dupe the substring and return that duped string instead, while still freeing the original environment variable value.
I would suggest the following changes:
const std = @import("std");
fn language(allocator: std.mem.Allocator) ![]const u8 {
// return the default value directly from the 'catch'
// to make the memory management a little easier to read
const lang_var = std.process.getEnvVarOwned(allocator, "LANG") catch |err| switch (err) {
error.EnvironmentVariableNotFound => {
// it's important that you don't return a string literal
// the program might crash if the caller tries to free a string literal
// instead, dupe it into a new allocation
return allocator.dupe(u8, "en");
},
else => return err,
};
defer allocator.free(lang_var);
// use 'sliceTo()' to slice to a specific character or the end of the string
return try allocator.dupe(u8, std.mem.sliceTo(lang_var, '_'));
}
pub fn main() !void {
// use '= .init'
// initializing 'DebugAllocator' with '= .{}' is deprecated
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer std.debug.assert(debug_allocator.deinit() == .ok);
const allocator = debug_allocator.allocator();
const lang = try language(allocator);
defer allocator.free(lang); // don't forget to free!
std.debug.print("{s}\n", .{lang});
}
There’s one minor optimization you could make by avoiding duplicating the environment variable value if it doesn’t contain a '_', but it’s unlikely to matter and makes the code more complicated. I’ll leave it as an exercise to the reader if you really want to try
@castholm thanks for the suggestions and code example! With it, I found my mistake: I removed the .frees in language, but your examples showed me that they have to stay there.