Hacker News
points by muvlon 2 days ago | 0 comments
C's lack of all of

- sum/union types

- multiple return values/tuple types

- exceptions

means that most ways of returning an error from a function are not available in C. Because of this, many C APIs, including `crypt` in this case, resort to in-band error signalling by using a sentinel value (like NULL, a negative integer, "*" etc.).

Most other languages lack one or two of the above, but very few lack all three, and so most have developed a different culture around error reporting (Result in Rust, multiple return values in Go, exceptions in Java, Python etc.).

[–] tialaramex 2 days ago
One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

And really often when your "in-band error signalling" in Rust is more expensive you realise (perhaps to your horror) that your C wouldn't be correct, so you just dodged a bullet. For example Option<OwnedFd> is, like OwnedFd, a 32-bit value, Unix has always reserved -1 to mean invalid, so Rust can treat that as a niche. But the Windows equivalent Option<OwnedHandle> can't do this because, well, cue Raymond Chen: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40...

[–] creata 2 days ago
> One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

But aside from NonZero- and NonNull-based layout optimizations, I don't think Rust is doing anything special to obtain that machine code - it's just relying on LLVM to optimize things.

[–] tialaramex 1 day ago
Well, all of the niche optimisations, so that includes every normal C-style user-defined enumeration because (unlike C or C++) Rust says those aren't just the integers wearing a funny hat.

But sure, for the most part LLVM will do The Right Thing™ here without much help and Rust is just giving better ergonomics to the same backend.

[–] Gibbon1 1 day ago
I remember mucking with returning multiple values as anonymous structs. It was tantalizing close but no banana.

I think with C23 you got half a banana now. Still can't do it because C23 didn't adopt proposals to allow returns of untagged/anonymous structs.

If you could then you could write

  auto rtn = Foo();
  if(rtn.err) 
    printf("err=%i", rtn.err);
  else
    printf("Foo=%i\n", rtn.foo);
[–] sscaryterry 2 days ago
The first 2 it has, and the 3rd, you really don't want.
[–] muvlon 2 days ago
The first 2 C has in the same way that I don't need Dropbox if I just have an FTP account and curlftpfs (https://news.ycombinator.com/item?id=9224).
[–] sscaryterry 2 days ago
Your analogy is way off...
[–] jppittma 2 days ago
C has sum types/unions? What are you talking about? You don't need multiple return. You can use a struct and it's the same thing.
[–] adgjlsfhk1 2 days ago
not in a way that forces the user to actually handle them properly. C is a weakly typed language and as such is pretty badly suited to systems programming.
[–] uecker 2 days ago
C is not a weakly typed language, this is just nonsense. There is also [[nodiscard]] which you can use to make sure a return value is handled.
[–] muvlon 2 days ago
What is a weakly typed language to you? To me, implicit type conversions are a big part of the definition, and C is flush with those (void* converts to any pointer, all kinds of number conversions, arrays decay to pointers, functions convert to pointers, lvalue conversion strips const/volatile etc.).

However, I also think "weakly typed" is not a well-defined concept, everyone seems to apply it differently. In particular, it seems to mostly mean "playing it loose with types in a way that I don't like".

[–] uecker 2 days ago
Yes, it is not well-defined. But most conversions, function calls, and accesses are strongly typed in C, so I would not call it weakly typed.

Implicit conversion could be seen as weak typing, but it is mostly limited to specific arithmetic operations in C. I would see implicit conversions as problematic if they allowed operations that make no sense. Decay of arrays and function to pointer conversion also do not seem problematic to me for this reason.

Void pointers are also weakly typed, but they are specifically designed for this. So if you do not want this behavior, one would simply not use them. It is a bit like using "unsafe" in Rust.

There are certainly stronger typed languages, but on the full range C is more on the strongly than weakly typed side in my opinion. A language that allows arbitrary implicit conversions between pointers, would be weakly typed, or which allows calling functions with wrongly typed arguments (which was true in the past for C a long time ago)

[–] adgjlsfhk1 2 days ago
C is definitely not the most weakly typed (e.g. Javascript/PHP are both weaker), but IMO C is weaker than python in that python will give you a runtime type error if you do something invalid, while in C you cast something and just get UB. Most higher level strongly typed languages are able to make their casts checked because they have generics to remove the need for 99% of them, but C is missing those features and thus has to rely much more heavily on unchecked and unsafe runtime casts.
[–] uecker 2 days ago
Casts are escape hatches just like "unsafe" in Rust. You can write safe generics in C that either do not use casts or wrap all unsafe operations behind type-safe interfaces.
[–] muvlon 2 days ago
C unions are sum types if you just "draw the rest of the fucking owl". C has sum types the way x86 assembly has function calls. You see, there are "call" and "ret" instructions. Just figure out calling convention and the stack yourself, it's easy. And yes I can stick the tag next to the union myself in C and write a bunch of functions that hopefully deal with it correctly. I can even write some macros that look like pattern matching and maybe convince myself that it's a language feature, but it just isn't.

And it's very evident why the difference matters. People gravitate to what's readily available and ergonomic. That's why every C API under the sun (most definitely including libc itself) does janky stuff like returning -1 or "*" on error instead of building their own bespoke Result-style abstraction. A programming language is not just the things that are in some way possible within it, but more importantly the things that are easy in it.

[–] uecker 2 days ago
While I agree that this should come out of the box, the "rest of the fucking owl" is a tiny macro or function, so not exactly something difficult to do complex as "drawing an owl" would imply.
[–] dvdkon 2 days ago
Sure, when I last wrote a sizeable chunk of C, I wrote those macros and with a bit of help from new-ish compiler warnings, I got a reasonable error handling system in C. But whenever I interacted with anyone else's code, they apparently didn't get the memo that this is possible and desirable.

A language should encourage writing robust/secure code, not just make it possible.

[–] uecker 2 days ago
While I see the point, I am not entirely sure I agree. I kind of like that C is not an opinionated language which tells you exactly how to write things. This comes at the cost that many people will do things in a bad way, and this is difficult to fix, but still probably easier than getting them to move to a different language.

In any case, I do not think the issue here is caused by not having a pre-defined result type. Checking for null would have been fine. The issue was that to accommodate old and broken code a compromise was made. But as long such compromises are done even where fixing such old code would be quite easy, it is clear that changing to another language is completely unrealistic. So what are we even talking about? The real issue is that nobody wants to spend money or effort maintaining the old code. No language can fix this.

[–] muvlon 2 days ago
There are different amounts of effort you can pour in to this depending on your requirements, and we can argue about those, but to me the evidence is clear: It's too much of a hassle for library authors to actually go and do it. Everyone just uses sentinel values instead.