It looks Windows is lousy with callbacks and APIs that put the burden of understanding everything on the user, and some of Windows uses 0 to mean success, and some of Windows doesn't.
It actually makes sense, but only to old Windows vets.
The underlying NT (microkernel) api (as found in used-to-be-undocumented NTDLL) returns STATUS_XXX from functions. With STATUS_SUCCESS being 0. This is still consistent as far as I know.
Win32 API, technically being a "subsystem" on top of NT, exposed to the user whatever convention its users were used to. Coming from Windows 3.1, it used BOOL (int actually) where FALSE was 0 and TRUE was 1, in a (mostly) consistent manner for success/failure.
An additional wrinkle may be APIs that return a HANDLE (or address for memory allocators) or NULL, which again if you treat as ints return non-zero for success and 0 for failure.
Posix Subsystem, on the other hand, could return 0s and non-zeros as desired.
So, if you knew Windows and thought about the API you were calling it made sense if you thought in terms of the types defined (BOOL, HANDLE, etc. for Win32) instead of 0 and non-zero.
No idea if it this is still consistent over the surface of the entire exposed API set now. So much accretion of cruft over the years under the eyes of an uninterested leadership has led to a lot of fragmentation and hence deterioration in the developer experience.
> An additional wrinkle may be APIs that return a HANDLE (or address for memory allocators) or NULL, which again if you treat as ints return non-zero for success and 0 for failure.
There are a lot of small details to know in order to use Windows APIs correctly. Which is stressful. On the other hand, comparing with Unix, the APIs are much much wider and are rarely if ever deprecated.
Unix APIs are a couple of syscalls and library functions. If you want to achieve more (like video, audio...) you have to resort to "optional" libraries that may have bad APIs too, but they aren't core Unix and can be replaced by something that is hopefully better.
The reason is C had no built-in boolean type, so boolean values use an integer type just like error codes. In C, zero means false and non-zero means true, so that’s how boolean values are represented, and hence a boolean value is_success is non-zero on success and zero on failure. For error codes, on the other hand, the convention is that zero means “no error” and non-zero means some specific error. So exactly the opposite. Unix also reflects this same discrepancy, in that a process (i.e. main() or exit() in C) returning zero means success, but in Unix C programs that’s boolean false. (Not to mention that for pointer return values, null often means failure.)
A valid criticism might be that one shouldn’t use true to mean success.
some of Windows uses 0 to mean success, and some of Windows doesn't.
In cases where there is only one successful result, 0 makes sense. Then nonzero can be a range of error codes. In cases where there is multiple successful results and only one failure, 0 means failure; malloc() is the stereotypical example of this.
Unix APIs return -1 on error pretty consistently. The error code can then be read from the errno thread local variable. In the Linux kernel internal APIs (and probably others), -errno is returned directly (no errno mess), which is still negative.
The one "Unix" API I know that returns > 0 on error is pthread, which returns +errno directly (but still 0 on success).
Which APIs return 0 on error? I can't think of any.
Yeah there are a couple of (3) functions (i.e. not syscall interfaces) that return pointers. It's very common for those to return NULL on error, hardly a surprise. It will also blow up with a segfault should you forget to check success.
You shouldn't intentionally rely on a segfault as a means of terminating the program -- do sth. like `exit(1)` instead.
What I'm saying is there isn't an ergonomic issue with NULL error return APIs. You might prefer algebraic error types (in other languages). I'm not even sure I do. NULL return APIs are clean and simple. If you do (accidentally) forget to check for NULL return code, you will for sure get a segfault on first access in practice (not on embedded platforms maybe). The compiler can't remove any following code unless it can prove only NULL will ever be returned. Don't fall trap to UB FUD.
The compiler can only reorder when that would be ok in the absence of UB. What can be reordered before the write through the pointer can't read the pointer, and can't really do any damage. Syscalls and library calls (in particular if dynamically linked) can't be reordered before the write because the compiler can't see what the do.
Most of the UB FUD are contrived examples that hardly happen in practice. What I'm saying is not that you should write bugs, but that bugs (especially segfaults) will immediately turn up in practice.
I've come across lots of C code which mixes whether it's using -errno, or errno and life all gets messy. And then there's the places where you're returning a pointer not an errno at all. (Which the kernel tries to solve with errptr I think - but there seems to be plenty of places fixing that)
And then there's the way that using -errno ends up with ssize_t (signed that is) which also confuses loads of things.
That's the thing about development with more than one developer designing APIs.
Looks like the RtlRunOnceExecuteOnce API and structures appeared in Windows Vista.
So one would have to do some code archaeology and dig into the Windows Git repo to look at the commits for RtlRunOnceExecuteOnce and see what the typical use cases were. For some reason, returning 0 for failure made sense. Given the pervasiveness of NTSTATUS, I have no idea what made them choose a non-NTSTATUS return value. Maybe there was another callback-type API which did something similar?
If one dev thought that RtlRunOnceExecuteOnce required NTSTATUS, then I'm sure other devs will have thought the same thing so I'd expect more such bugs in the current codebase and in the future.
That's why Microsoft static code analyzers (PREfix, PREfast) would flag such footguns.