The only reason this library did the insane thing of returning "*" is because it was extremely easy to cause crashes or false-positives (NULL == NULL) by returning NULL+errno. The reason that was easy is because C lacks a means of making error handling mandatory (exceptions, tuple returns, tagged unions, whatever).
If you return a tagged union, users might mis-handle it, it's true (https://blog.cloudflare.com/18-november-2025-outage/). But they have to handle it, which is a requirement that C APIs are incapable of providing.
[–] hedora 2 days ago
[–] jstanley 2 days ago
But it crashes at runtime, so such a bug can exist for years with nobody noticing.
The existence of such bugs is what motivated the musl developers to return "*" instead of NULL.
[–] zbentley 2 days ago
In practice, that's less of a risk than:
char* x = function_that_might_return_null_and_error();
dangerous_function();
if (x == NULL) {...}
Or code for which pointer identity is relevant (e.g. accessing slices/subparts of a data structure): char* x = returns_null_or_a_slice_pointer(data_structure);
char* y = other_function_that_returns_null_or_a_slice_pointer(data_structure);
if (x == y) { puts("you're fucked now!"); }
And for sure there are much better ways to write both kinds of code! But people routinely do not do that.[–] hedora 1 day ago
x : Result<string, err> = function...
dangerous_function();
switch(x) {
Ok() ...
Error() ...
}
There's no difference between result and null other than readability (there's a mechanical transformation between the two, where you use unwrap() in rust on an unchecked null dereference, and you read errno in C instead of looking inside the Err() result variant).To be clear, I strongly prefer Result<> because of the readability, but any dumb thing you can write with NULL + errno can be written with Result (and vice versa).
Also, in things like rust you can just do result.unwrap() if you want. In general, language constructs don't really save you from bad programmers.