-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
errdefer
should be a compile error when used inside a function that doesn't return an error
#2654
Comments
I once had the same thought, but it came in useful when coding defensively: fn myfunc() Foo {
const foo = Foo.init();
errdefer foo.deinit()
foo.bar = Bar.init();
return foo;
} And then at some point later, |
On the other hand, I ran into this when I was refactoring and ended up with this stale code (the errdefers) that I needed to change to regular defers. (My refactor was something like taking a large init function, which returned errors, and which was called by my main function - and inlining it into my main function which doesn't return errors. The errdefers were previously in the init function) |
What about functions that do return errors, but don't in the paths following the fn dothing() !void {
if (badthing()) {
return error.AProblemHappened;
}
errdefer cleanup();
} I think if you wanted to complain about Though, I do think the point @daurnimator is important. Because Zig doesn't have any "automatic" resource cleanup (such as RAII), it seems like it would be good style to always pair resource acquisition with an var r1 = try R1.acquire();
errdefer r1.cleanup();
var r2 = try R2.acquire();
errdefer r2.cleanup();
var r3 = try R3.acquire();
// conspicuously missing errdefer
return consume(r1, r2, r3); If someone were to modify this code to acquire a fourth resource, they probably wouldn't know whether or not |
I see the arguments of the people saying that it can be a clean style to have dead code, but it's still dead code. You may as well have
This relates to the Zig compiler's overall lazy approach to evaluation, where if it doesn't know that an expression is needed in this build it won't try to compile it. Untested code paths are buggy, and if you've got an Anyway, I'll try to sum up the debate with examples of bugs from both sides. Either untested code is silently enabled or cleanup isn't done properly. In the case where a function with no error in its type errdefers:
This is not an unlikely scenario (by my estimation) and when In the proposed case:
When Bar is changed to return There's another situation, which is how I ended up here: incorrect use of errdefer.
Whoops, leaked some memory because All of the above scenarios are bad, and none are unlikely. If I'm already running valgrind on my tests, it will catch all of these situations, but that's only true if the leaked resource is memory. On principle, I prefer the proposal, since I don't like dead code in my codebase. I totally forgot @dbandstra's case of refactoring that needs to change a return type from |
Thank you @nmichaels for your well-considered and detailed response here. I agree with your reasoning, as well as your suggestion for what idiomatic zig code would look like when "coding defensively": errdefer unreachable; This will be allowed, even when there is no possible error returned from the function, for the same reason that The idea here is when the programmer gets the compile error for returning a compile error through Code reviewers can look for the simple pattern of: - errdefer unreachable; And understand that the entire function's resource management with respect to errors, needs to be double-checked. I also want to note that much like the other issues @hryx noted, multibuilds (#3028) is required to enable this compile error, because the return type and reachability in general could depend on build options. |
I fail to see any material upsides to this proposal, which seems to me like it is inspired by a dogmatic view against dead code without considering why dead code is harmful. I first want to point out that I don't see any value in telling a future human to add needed defer statements if anything changes, when we already have a perfectly functional way to tell the compiler to add the defer statement if needed. Why involve error prone humans more than necessary? I am not convinced by the example cases given by @nmichaels. So not only does this proposal increase the odds of forgetting a defer, it does not reduce the chances of making that error, all while keeping the dead code around, just commented out and written in English instead of code. I also want to point out reliance on tools like valgrind to catch missing errdefers only works in a limited number of cases. Not all errdefers manage resources monitored by valgrid, and in real time apps, such as games, valgrind is not a option (even debug builds are often not used either, even during development). nor will valgrind catch coding errors that did not happen during prod or QA. Live issues are very common and retail crash dumps are opaque at best. |
This point has already been addressed. Zig allows, and will allow, an So it's a load-bearing part of the code, because it turns "happens to be unreachable" into "must be unreachable". Consider: fn doNumberThing(number: isize) void {
if (number < 0) {
doFirstThing(number);
} else {
doSecondThing(number);
}
unreachable;
} By adding the That's the purpose of |
This code is currently allowed, but from reading the docs I don't think it's ever possible for that errdefer to be hit.
The text was updated successfully, but these errors were encountered: