> Just to be clear, you are making the argument that when a library call detects an error of the form of unexpected NULL/not-NULL, that they abort immediately?
There's no blanket answer here because your scenario isn't specific enough. Is the pointer caller provided? Is the pointer entirely an internal detail whose invariant is managed internally? Is this pointer access important for perf? Is the pointer invariant encapsulated by something else?
Instead, I suggest showing examples of C libraries following your philosophy. Then we'll have something concrete.
In the comment I linked, you'll noticed that I actually looked at and reviewed real code examples. Maybe you could engage with those.
I happen to agree with @lelanthran's position, but aside from that I think their point is not that there are C libraries following their principle, but rather that libraries should follow it. This is akin to Rust's "get"/"try" style of fallible methods, avoiding both UB and exceptions/panics. It also seems moot to ask this of typical C libraries, as they wouldn't use exceptions either.
(I've edited this multiple times by now, apologies if it's confusing. Only adding things to it, but it may read weirdly as I've reconsidered what I'm trying to say.)
For something like array indexing in Rust, it's not bad to have a panicking operator by default because it's very upfront and largely desired. Similarly, a library may document when it panics just as it would document its error type if it returned error values. But something that I would consider very bad design is if I use a library that spawns another thread and does some file processing and can panic, without making this clear to me.
I think one of your main points is, suppose a library theoretically could index an array OOB and panic; it is not formally verified not to and so the developer is just covering all bases conveniently. The normal alternative being UB is of course unacceptable. There is a crucial distinction to be made here. If the index is derived from the application, return an error value making this clear to the application. However, at some point the index may be considered only internally relevant. I agree this is fine. The thought is that this will never trigger and the application will be none the wiser. If it is ever triggered, the library should be patched quickly. I think is not all the panics that people in this thread have in mind, as otherwise panics should be seen basically never, just as a well-designed but otherwise normal C program would have risk of UB but should exhibit this basically never. There should be an effort to minimize panics to the ones that are just sanity checks and only there for completeness, rather than a convenient way to handle failure.
With panics, either I just let them happen when they will or I have to defensively corral the library into working for me. With error values, the library has set out to state its terms and conditions and I can be happy that the burden is on me to use it properly. I have more control over the application's behavior from the start, and the extra work to surface errors to users properly is more or less equal between both approaches. Yes, panics can also be laid out in the API contract. But it's more enforceable with error values.
If there was a good way to do error-values-as-exceptions (automating Result bubbling with ?) that just panics up until a good boundary and returns a Result, that's basically catch_unwind but cleaner. It's true that oftentimes aborting (perhaps after cleanup) is the best way to handle errors, but it shouldn't be a struggle to avoid that when I know better. Particularly with C's malloc(): maybe I do want to change my program behavior upon failure instead of stopping right then and there.
I seem to be rambling. I will add, particularly to clarify my third paragraph (the big one): a library should not panic. It can have defensive panics, but overall it should not panic, and triggering a defensive panic is to be treated as a bug. The exception is the panics that the application developer can reasonably be considered to have agreed to, and presumably the library should take care to make those panics as easy to handle as possible.
The issue being addressed in this thread is that OP says this:
> While C++’s std::vector<T>::at() throws an exception which can then be caught and cleanly relayed to the application, a panic!() or an abort() are much more annoying to catch and handle. Moreover, panic!()’s are hiding even in the most innocious places like unwrap() and expect() calls, which in my perception should only be allowed in unsafe code, as they introduce a surface for a denial-of-service attack.
This is not a nuanced position separating internal runtime invariants with preconditions and what not, like what you're doing and like what my blog does. This is a blanket statement about the use of `unwrap()` itself, and presumably, all panicking branches.
This in turn led to this comment in this thread, to which I responded to as being terrible advice:
> That behavior is up to the user. The library should only report the error.
This is an extreme position and it seems to be advocated by several people in this thread. Yet nobody can point to real examples of this philosophy. Someone did point out sqlite, libavcodec, lmdb and zlib as having error codes that suggest this philosophy is employed, but actually looking at the code in question makes it clear that for most internal runtime invariants, the C library just gets UB. In contrast, Rust will usually prefer panics for the same sorts of broken invariants (like out-of-bounds access).
The bottom line here is that I perceive people are suggesting an inane philosophy to dealing with internal runtime invariants. And that instead of going back-and-forth in abstraction land trying to figure out what the fuck other people are talking about, I think it's far more efficient for people to provide concrete examples of real code used by real people that are following that philosophy.
If the philosophy being suggested has no real world examples and isn't even followed by the person suggesting it, then the certainty with which people seem to put this philosophy forward is completely unwarranted.
Asking for real world examples is a shortcut to cutting through all this confusing language for trying to describe the circumstances that can lead to aborts, panics or UB. (Hence why my blog I linked earlier in this comment is so long.) It's my way of trying to get to the heart of the matter and show that these pithy comments are probably not suggesting what you think they're suggesting.
I've written hundreds of thousands of lines of Rust over the years. Many of my libraries are used in production in a variety of places. All of those libraries use `unwrap()` and other panicking branches liberally. And this isn't just me. Other ecosystem libraries do the same thing, as does the standard library.
> Just to be clear, you are making the argument that when a library call detects an error of the form of unexpected NULL/not-NULL, that they abort immediately?
There's no blanket answer here because your scenario isn't specific enough. Is the pointer caller provided? Is the pointer entirely an internal detail whose invariant is managed internally? Is this pointer access important for perf? Is the pointer invariant encapsulated by something else?
Instead, I suggest showing examples of C libraries following your philosophy. Then we'll have something concrete.
In the comment I linked, you'll noticed that I actually looked at and reviewed real code examples. Maybe you could engage with those.