I often find myself making variables to just be able to free them. That adds a lot of unnecessary boilerplate to the code and makes it less readable/understandable. I think something like deferExpression would be really nice as a keyword or something like that. Which could just generate the variable and defer statement. A short time solution could be to setup zig fmt to make the defer statement behind the variable statement. Example:
const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn expressionToFree(allocator: Allocator) ![]const u8 {
var list = std.ArrayList(u8).init(allocator);
try list.appendSlice("hello");
return try list.toOwnedSlice();
}
const testing = std.testing;
test "much boilerplate" {
const allocator = testing.allocator;
const result = try expressionToFree(allocator);
defer allocator.free(result);
try testing.expectEqualStrings("hello", result);
}
test "less boilerplate" {
const allocator = testing.allocator;
const result = try expressionToFree(allocator); defer allocator.free(result);
try testing.expectEqualStrings("hello", result);
}
fn deferExpression(allocator: Allocator, expr: []const u8) []const u8 {
allocator.free(expr);
return expr;
}
test "deferExpression" {
const allocator = testing.allocator;
// This leaks memory since the value is freed before it is used
try testing.expectEqualStrings("hello", deferExpression(allocator, try expressionToFree(allocator)));
}
What do you think about this? Is there already something similar?
You can use an allocator that automatically frees the memory for you, like an Arena allocator:
// You have the boilerPlate only once per function/test block
const arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
try testing.expectEqualStrings("hello", try expressionToFree(arena.allocator()));
I think this is not good possible with procedures, then the explicit defer is definitely better. Maybe as a keyword than everything knows what is going on.
I don’t think it’s a good idea to specialize a syntax for one specific standard library struct.
Like what if expressionToFree returns an ArrayList, or a nullterminated C string, or what if I want to use my own Allocator interface (I do)?
Furthermore this is really different from Zig’s usual syntax.
Maybe something like this could be better?
But I don’t think it really is worth it. It does save two lines, but at the cost of making one line significantly more complex and adding yet another new syntax you have to know about.
In my experience, “saving lines” is not something Zig is particularly interested in. It very much embraces the “just write the X” way of programming (just write the loop, just wrap it in a block, just assign the temporary to a const, etc).
I’m also not in favor of this idea, because when stuff is crammed together on one line like was demonstrated I can very easily skip over the extra stuff by accident and then I go “but where is memory being freed?” or similar.
Joke aside, I think it’s more legible to have defer on it’s own line, I understand the frustration of typing it over and over again, but I believe it’s better than trying to figure out later on what the hell is wrong with some code and nothing make sense, until you remember that some overloaded definition of a function behaves slightly differently now that you have changed the type of your parameter. My point is simplicity has a lot of values, there are tons of features that could (emphasis on the could) improve Zig, but there is always a price to pay. Happy new year btw
Why do we always get frustrated about having to type when we could make a tool that inserts the line we want automatically or we could make a special command that does it or we could program it into our keyboard? We’re programmers, right? Just use or make a tool to make it easier.