The problem of libraries not being packaged is solved by distro-agnostic packaging systems. Why do you think everyone uses PyPI, Cargo, Go modules, NPM, etc. instead of this insane "package your app and all of its dependencies for every distro" idea? Pure lunacy.
It is not difficult to package for the most important distro (the others usually import from them). Those distro-agnostic packaging systems are popular because they basically have no quality control at all, so it is basically no effort to package for them, just register a github repository somewhere. But this is also why they are full of garbage and have severe supply chain issues.
> But this is also why they are full of garbage and have severe supply chain issues.
I don't think the solution to supply chain problems is "just make supplying things so shitty and annoying that nobody will bother". Not a good one anyway.
It's part of my job to ship software on Linux, and if anything the part making things "shitty and annoying" is when we have to deal with people/'suppliers' (exclusively) using "foreign" packaging methods.
We had 2 dependencies like that; one we talked to the creators about and helped them ship distro packages, the other we just got rid of. Life is nice now.
I do not think packaging for a distribution is annoying. If you do not bother because you do not want to match some minimal community standards, maybe your software should no be packaged? And if you think the tooling can be improved, then why not invest there? But this does not justify the existence alternate packing systems.
C has its unique advantages that make some of us prefer it to C++ or Rust or other languages. But it also has some issues that can be addressed. IMHO it should evolve, but very slowly. C89 is certainly a much worse language than C99 and I think most of the changes in C23 were good. It is fine to not use them for the next two decades, but I think it is good that most projects moved on from C89 so it is also good that C99 exists even though it took a long time to be adopted. And the same will be true for C23 in the future.
For two reasons: First, where C++ features are used, it make the code harder to understand rather than easier. Second, it requires newer and more complex toolchains to build GCC itself. Some people still maintain the last C version of GCC just to keep the bootstrap path open.
This is not my experience at all. In fact, my experience is where C++ is used in GCC it became harder to read. Note that GCC was written in C and then introduced C++ features later so this is not hypothetical.
In general, I think clean C code is easier to read than C++ due to much less complexity and not having language features that make it more difficult to understand by hiding crucial information from the reader (overloading, references, templates, auto, ..).
To be fair, there were multiple wrongs in that piece of code: avoiding typing with the forward goto cleanup pattern; not using braces; not using autoformatting that would have popped out that second goto statement; ignoring compiler warnings and IDE coloring of dead code or not having those warnings enabled in the first place.
C is hard enough as is to get right and every tool and development pattern that helps avoid common pitfalls is welcome.
The forward goto cleanup pattern is not something "wrong" that was done to "avoid typing". Goto cleanup is the only reasonable way I know to semi-reliably clean up resources in C, and is widely used among most of the large C code bases out there. It's the main way resource cleanup is done in Linux.
By putting all the cleanup code at the end of the function after a cleanup label, you have reduced the complexity of resource management: you have one place where the resource is acquired, and one place where the resource is freed. This is actually manageable. Before you return, you check every resource you might have acquired, and if your handle (pointer, file descriptor, PID, whatever) is not in its null state (null pointer, -1, whatever), you call the free function.
By comparison, if you try to put the correct cleanup functions at every exit point, the problem explodes in complexity. Whereas correctly adding a new resource using the 'goto cleanup' pattern requires adding a single 'if (my_resource is not its null value) { cleanup(my_resource) }' at the end of the function, correctly adding a new resource using the 'cleanup at every exit point' pattern requires going through every single exit point in the function, considering whether or not the resource will be acquired at that time, and if it is, adding the cleanup code. Adding a new exit point similarly requires going through all resources used by the function and determining which ones need to be cleaned up.
C is hard enough as it is to get right when you only need to remember to clean up resources in one place. It gets infinitely harder when you need to match up cleanup code with returns.
In theory, for straight-line code only, the If Statement Ladder of Doom is an alternative:
int ret;
FILE *fp;
if ((fp = fopen("hello.txt", "w")) == NULL) {
perror("fopen");
ret = -1;
} else {
const char message[] = "hello world\n";
if (fwrite(message, 1, sizeof message - 1, fp) != sizeof message - 1) {
perror("fwrite");
ret = -1;
} else {
ret = 0;
}
/* fallible cleanup is unpleasant: */
if (fclose(fp) < 0) {
perror("fclose");
ret = -1;
}
}
return ret;
It is in particular universal in Microsoft documentation (but notably not actual Microsoft code; e.g. https://github.com/dotnet/runtime has plenty of cleanup gotos).
In practice, well, the “of doom” part applies: two fallible functions on the main path is (I think) about as many as you can use it for and still have the code look reasonable. A well-known unreasonable example is the official usage sample for IFileDialog: https://learn.microsoft.com/en-us/windows/win32/shell/common....
I don't see this. The problem was a duplicate "goto fail" statement where the second one caused an incorrect return value to be returned. A duplicate defer statement could directly cause a double free. A duplicate "return err;" statement would have the same problem as the "goto fail" code. Potentially, a defer based solution could eliminate the variable for the return code, but this is not the only way to address this problem.
This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' fixes nothing.
I don't think so. The value is set in the assignment in the if statement even for the success path. With and without defer you nowadays get only a warning due to the misleading indentation: https://godbolt.org/z/3G4jzrTTr (updated)
No it wouldn't. 'err' is declared and initialized at the start of the function. Even if it wasn't initialized at the start, it would've been initialized by some earlier fallible function call which is also written as 'if ((err = something()) != 0)'
The advantage is that it automatically adds the cleanup code to all exit paths, so you can not forget it for some. Whether this is really that helpful is unclear to me. When we looked at defer originally for C, Robert Seacord hat a list of examples and how the looked before and after rewriting with defer. At that point I lost interest in this feature, because the new code wasn't generally better in my opinion.
But people know it from other languages, and seem to like it, so I guess it is good to have it also in C.
The return value depends on control flow ("obvious", please bear with me):
With "goto" the cleanup-up can jump anywhere. With "defer" the cleanup cannot really jump anywhere. It is easier to mentally stick to simply cleaning up in a common sense way. And taking care of multiple "unrelated" clean-up steps is "handled for you."
(Attacks on this sometimes approach complaints about lack of "common sense".)
There is a technical specification, so hopefully it will be standard C in the next version. And given that gcc and clang already have implementatians (and gcc has had a way to do it for a long time, although the syntax is quite different).
It is not yet a technical specification, just a draft for one, but this will hopefully change this year, and the defer patch has not been merged into GCC yet. So I guess it will become part of C at some point if experience with it is good, but at this time it is an extension.
We will likely decide in March that it will became an ISO TS. Given the simplicity of the feature and its popularity, I would assume that it will become part of the standard eventually.
I don't use much C but if you add them to the standard they'll probably trickle down to C++ compilers by 2045 and I'll have a good 10 years to use them before I retire.
Well, I'll take them over the nothing you're giving me :D
But in all seriousness, I want this:
struct Result { int value; int err; };
#define TRY(res) \
({ \
Result _res = res; \
if (failed(res)) return res; \
res.value; \
})
Result f1(...) { ... }
Result f2() {
int res = TRY(f1(...)); // <<<<
...
return success();
}
Can't be done with lambdas since the macro needs to return out of the actual function, not the lambda. Pretty much rust question mark, but without rust, or zig "try" but without zig.
I see, thanks! There is general consensus that statement expressions should become part of ISO C, but some lack of time to get it done. I am not part of WG21 though, so can't say anything about C++.
You can use pointer types by using a typedef first, but I agree this not nice (I hope we will fix this in future C). But then, I think this is a minor inconvenience for having an otherwise working span type in C.
reply