Stuff like this is why I'm supportive of newer languages like Go and Zig that sidestep libc entirely (when not using cgo as in TFA of course). libc is a great achievement and has served us well but, boy, it sure is a product of its time.
`errno` is another relic that needs to die yesterday.
I think it's the same in Windows, right? Can't use the syscalls underneath the hood, everything through the standard libraries. Maybe I'm wrong (I know very little about Windows other than how to use it to play games, and WSL)
The standard libraries on Windows don't involve libc.
The Windows APIs look rather different, and in general are much more friendly to multi-threading. POSIX on the other hand tends to assume that the program is in control of everything happening inside of it, which is an incorrect assumption due to libraries.
In this particular case, the Windows APIs have neither getaddrinfo() nor getenv(); and the closest equivalent GetEnvironmentVariableW is perfectly thread-safe.
Microsoft additionally has a C runtime (msvcrt) providing functions like getenv(), but this is much less fundamental than it is on other system. Every program is supposed to ship its own copy of the C runtime, it's not officially part of Windows! And it's perfectly possible for multiple different copies of the C runtime to be loaded into the same Windows process. And since *environ is a variable defined by the C runtime, there's a different copy for each C runtime...
Almost correct, except that since Windows 10 there is now a C runtime shipped as standard, ironically it is actually written in C++ taking advantage of its safety features over plain C, and exposing the C API via extern "C".
On windows it’s somewhat possible to avoid most of it by linking to ntdll, which only provides symbols for raw syscall wrappers. But a lot of it is unstable and may change from a windows release to the next.
Doing raw syscalls without ntdll is also possible, but windows syscall numbers change on essentially every release, so you’d end up with something that only works on your windows version.
We've been building everything with CGO_ENABLED=0 for years now, with no nasty side effects. It gets to be a pain using the default, when something as innocuous as a point version of a Docker image breaks compatibility because of a glibc version change[1].
[1] golang official image 1.20.4 to 1.20.5 went from Debian 11 to 12 base. Always use the -(debian version) tags.
Split DNS is broken on macOS when doing that, and for users with VPN that does split DNS it is not just an annoyance it leads to software not actually functioning.
Re-implementing system capabilities is fine and all as long as you support common use cases properly, which Golang does not.
And on the flip side, there have been a number of instances where, in cases where the behavior differs, the Golang documentation describes a function only as it behaves with the Golang-native implementation, rather than the system implementation which ends up being the default - without calling any of this out
Yeah, there's so much misery in the C ecosystem that it's better to eschew it altogether. Even merely packaging anything that depends on C ends up being a hugely painful undertaking since every C library has its own bespoke build system and its own implicit set of dependencies (and implicit versions of those dependencies, and expectations about where on the system those dependencies live).
I mostly like C as a language, but between the security concerns and the tooling concerns (and the community's zealous devotion to ignoring these very real problems) I'm really excited for its increasing marginalization. Unfortunately, it's not being marginalized in favor of "a better C", but rather every ecosystem is rewriting the same stuff from scratch which seems like a bit of a bummer (but still better than depending on C).
if (somecall() == -1) {
printf("somecall() failed\n");
if (errno == ...) { ... }
}
sure, the issue is that `somecall(...)` might have altered `errno` through 'acts-of-omission-or-comission' :o)
fwiw, posix has updated its definition to pretty much say that 'value of errno in one thread is not affected by assignments to it by another'. this has been the case since at least a decade-and-a-half (iirc), which in internet years would positively be in the pleistocenic era :o)
so, i am not sure i really appreciate 'the shared-global-mutable-state' argument above. thanks !
The problem in that snippet is that `printf` could have altered the `errno` set by `somecall`, and that's only thanks to it being shared-global-mutable-state. You not realizing that was possible makes for a great example of why shared-mutable-global-state is hard to reason about.
This thread isn't talking about how to fix the errno problem generally. It's talking about the existence of a problem in the first place. Fixing it would be a whole different can of worms, and indeed, sisyphean sounds about right.
Notice how this entire thread was started by someone asking why errno was problematic. This is just about understanding.
Returning a"Result" struct doubles the size. This is one less register to use.
Exception handling is even more invasive.
They are great for high(-er) level language, but less prefect on lower level where performance is critical.
EDIT: Linux kernel use negative return value for error. It's good and efficient when it work. But it is not always an option when you need the full register width
You are saving one register at the cost of having a thread local variable that is visible to signal handlers, so none of its uses can be optimized away. Which results in things like gcc having to decorate every math instruction with code to set errno on the off chance that someone somewhere might read it (no one ever does).
Some newer POSIX APIs, such as pthreads, do return the error this way. But many legacy APIs, such as dup or read, use the positive integer space, thus the negation pattern you often see in syscalls. Notably, POSIX guarantees <errno.h> values to be positive integers.
There are a lot of alternatives, and it's not clear why the ones you've suggested are inappropriate. You've listed some perceived costs, but I don't see why those costs are greater than the ones paid by the status quo.
Linux even shows you a path, yet you reject it for reasons that don't seem compelling to me.
The performance cost of having otherwise pure functions clobber global mutable memory defeating many optimization passes, is way higher than clobbering another register for the result.
The most obvious way it is wrong is that it is archaic. There is simply no reason to ever pass return values in hidden state. Just use return values damnit.
Err no thank you. Ld preload and similar mechanism are great to inject code into apps legitimately, i.e. to patch long unsupported systems or to tame current ones.
For example I have vision issue and without reshade filter I would be unable to play a great deal of games.
Now that is also an attack vector, that's for sure, but you cannot go ax features willy nilly just because you don't see value in them.
LD_PRELOAD won't be needed if the OS were built around containers / jails, instead of the weakly isolated processes and process groups.
The Unix kernel (both Linux, BSD, and Solaris) already had much of what's needed, say, 30 years ago, but nobody saw it as such a burning necessity (likely except Solaris which eventually developed Zones).
On a "normal" desktop system, you don't need containers or jails. Your programs must communicate with each other (copy paste, print screen, etc.).
But today every god damn UI program needs an internet connection to phone home and execute remote code. This is the actual problem which must be fixed.
`errno` is another relic that needs to die yesterday.