C is not a holy cow, is it? It's just a very, very popular language. Error handling is important in programming in general, and having ways to implement it conveniently helps a lot.
Yes, errors are just data. And everything is data, including the code itself, right? It a question of abstractions introduced to the language and the program at question.
errno.h is bad. It's inconvenient, it's global, even if it's a thread-local something, it's cumbersome to use.
AFAIK, there are 2 error-related proposals for inclusion. One is making errors special, the other is a bit more generic. Both would improve on the current situation.
You don't need to change the language. You need to add new functions to the standard library. There are already quite a few versions of older functions, with an "_r" suffix in their names (from the top of my head, strtok_r(), although that is not a great example) that take an explicit pointer to local state, eliminating global state. You could just make more of them if you want to eliminate errno.
> It a question of abstractions introduced to the language and the program at question.
I don't think these kind of abstractions are in the spirit of C. As it says in the Charter linked here, "provide only one way to do an operation".
A more acceptable way to me would be allowing multiple return values in general. But maybe there are technical reasons why this is not done. Or it has to do with the complexities that such a mechanism would add to the expression syntax.
I absolutely agree about the multiple return values in general.
Unfortunately the discussion around N2289 is more about providing something a lot like exceptions for both C and C++ but without all the exception problems.
The proposal really has some ugly corners related to how one actually handles the errors. You can read the discussion here:
The error handling proposal adds multiple return values- not as records/tuples, but as a sum type so only one can be returned from any given call.
This could certainly be made more general, or just done via tagged unions (the way multiple return values can be done via structs), but there are massive advantages to be had by building it into the language and calling convention.
The implementation can be far more efficient than any of a) the standard `int f(ret *out)` idiom, b) using errno, or c) some sort of `struct my_tagged_union f()` (which nobody uses anyway because it's a huge pain syntactically). In addition to being cheaper, the proposed built-in version of (c) is also syntactically simple enough to become a standard, shared mechanism.
Really, really hard to believe. Not only since returning unions is already legal (isn't it)? What function do you have in mind where an explit error-return-pointer argument is not close to maximally efficient?
The first problem is that the error-return-pointer-argument approach must go through memory, because the caller can request that the return value go anywhere at all.
The second problem is that existing calling conventions for `struct { bool tag; union { .. }; }` put everything in memory anyway, using a hidden pointer argument. Further, there's no way to put this type in the standard library because C doesn't have generics.
The new implementation can put that single-bit tag in the CPU's carry flag where it has dedicated branch instructions and doesn't interfere with other values. It can leave the actual return value in a register without any kind of union aggregate lowering.
So far this is all just calling convention tweaks, and could be done by pattern-matching user-defined tagged unions, but building it into the language a) makes it possible to standardize its semantics and connect it to platforms' C ABIs so other languages can also participate and b) makes it far simpler to implement and use so it's actually likely to be adopted.
Why would it matter that the return-pointer write goes through memory? It's a handful of cycles. I'm curious what kind of function you envision that is so short-lived AND needs tricky error handling AND is extremely performance-sensitive.
All of them- it adds up. Calling conventions are a perfect place for this kind of microoptimization, because they apply pervasively and (assuming you want better error handling support) without additional change to program source.
The same reasoning applies to putting effort into register allocators, or switching from setjmp/longjmp to table-based unwinding, etc.
Small micro optimizations do not add up, especially not for something like error handling that does not concern most operations to begin with. Something like this error handling strategy clearly has its own cost in complexity of implementation, such that the whole thing will collapse under its own weight before you even notice a speed up.
You need to make sure that you keep the size and complexity of the language and its specification within reasonable limits. So you can't just add "all of them" with a blanket statement that they will add up.
I'm being a bit nit-picky, but in C code is not a lot like data, no.
You cannot take sizeof a function, and you can't copy functions around, for instance. The address of a function is a value ("data"), but not the function itself.
With a crapload of special case code, nests of #ifdefs and a dollop of "we should have thought of that possibility at this level of abstraction" refactoring.
ISO/IEC C committee does not think like that. The people considering it portable assembler are long gone away from that committee. The current members consider it a mid-high level programming language with a defined abstract semantics.
Conditional compilation is just a compatibility workaround to them.
But that only strengthens the point: language features are just abstractions we use to reason about data. One can always strip all the abstractions and end up working with raw bits.
If there's a useful way to think about certain kinds of data - it might be useful to codify that way as a language feature. Such as specialised error handling.
In Lisps, a function is also just a reference, and not the object itself. You can't take its size or copy it. (You could copy it if your dialect provided a copy-function function, of course. I don't think I've ever seen one. Such a thing could be useful if it provided a frozen copy of a closure's lexical environment, that would be unaffected by mutations when the original copy of the function is invoked.)
Okay, I will try to explain it to the downvoter. Suppose we have a lambda like this:
(let ((counter 0))
(lambda () (incf counter))
When we evaluate this we get a function. It contains the captured lexical environment. If we call that function, the captured counter variable mutates.
Now suppose we had a copy-function library function. I would expect it that if we apply it to this function, we get an object which carries a duplicate of the lexical environment. This means it has its own instance of counter. If we call the original function, the copied function's counter stays the same and vice versa.
I don't remember seeing such a copy-function in any Lisp dialect; there isn't one in ANSI CL. It seems it might be useful, same as the ability to copy a structure or OOP object.
Yes, errors are just data. And everything is data, including the code itself, right? It a question of abstractions introduced to the language and the program at question.
errno.h is bad. It's inconvenient, it's global, even if it's a thread-local something, it's cumbersome to use.
AFAIK, there are 2 error-related proposals for inclusion. One is making errors special, the other is a bit more generic. Both would improve on the current situation.
reply