To be honest, I personally consider goto approach to cleanup code
to be very nice, simple and readable. But this is the only case
where explicit goto is really needed.
Zig’s way to do cleanup in a function requires a bit more thinking,
whereas in C I do it automatically.
I guess in a sense you are right that goto cleanup is probably more subjective, I personally don’t like it I find it very messy but I guess it’s not the worst, I just find that having an idiom that tells you “ok if you get an error don’t worry we will execute your cleanup code” is better than to keep track of 10 different label.
This comes off as more of an experience thing than an actual good language feature. Obviously people who have years of experience in C are going to gravitate towards C ways of doing things.
Most newbie C programmers absolutely abuse goto. I feel like defer/errdefer is easier to grasp and can be mentally carried over from higher level languages like Go and D.
Zig was very natural when I came from Go, C on the other hand is a different world.
That’s true, and to be fair goto cleanup is not the worst, but I think it is still very error prone, in the sense that, you have to make the goto statement, so you better not forget to close a ressource. Whereas errdefer or defer, they take care of that for you, and if you forget something you can get warnings from the compiler, which you don’t get in C if you forgot a statement in your goto label.
I disagree. Once you’ve seen goto based cleanup pattern you grasp it immediately, no explanations is needed at all. defer/errdefer, on the contrary, require thorough explanations because they are implicit jumps akin try/catch/finally.
Moreover, cleanup operations should be run in reverse order of resource allocations. defer/errdefer do it automatically. In C you have to manually maintain correct order.
__in_case_of_failure:
if (NULL != m) {
if (NULL != m->a) free(m->a);
if (-1 != m->fd) {
int res = 0;
res = close(m->fd);
if (-1 == res) {
printf (
"%s: oooops, something went really wrong - close(%d)\n",
__func__, m->fd
);
}
}
free(m);
}
For whatever it is worth, I use this pattern in C a lot:
int foo(const char* name) {
int rc = 0;
FILE* fp = 0;
char* buf = 0;
do {
fp = fopen("name", "r");
if (!fp) break;
buf = malloc(fileSize(fp));
if (!buf) break;
// do stuff ...
} while (0);
if (fp) fclose(fp);
if (buf) free(buf);
return rc;
}
It really is a goto failure in disguise, but I like the linearity. If there is anything inside the do ... while(0) which is a bit more complex, I will usually move it to its own function which also follows this pattern.
To be in context of the joke… that sentence is used to express an utmost crux
when someone is seeing some unusual things being done by others.
I had wrenched it out of the anecdote, in which a little boy, after spying his
parents in the bedroom, fairly noted not out loud: “… and these people suppress me picking my nose ?!..”
Not that the C code isn’t clear, it sure is, but Zig is still clear while being less error prone (by not having to jump back and forth from top to bottom to check if everything is handled) and more concise IMO. I know lines of code isn’t a crucial measure of code quality, but I do recall reading somewhere a statistic about how the number of bugs in a project rises along with the number of lines of code.
I like continue, break and goto when they are explicit.
In Zig we have explicit continue and (sometimes ugly/strange/weird) break
but we do not have goto. Why so? Even D language has it.
In my C example all assignments like r->smth = smth;
are located in one solid piece of code,
whereas in that ziggified version of C they are scattered all around.