3 ms·
> Then you return an error and let the caller deal with it. No, this is absurd. C libraries generally don't do this either. Instead, they might just have UB in
by burntsushi 2y ago
> Then you return an error and let the caller deal with it.
No, this is absurd. C libraries generally don't do this either. Instead, they might just have UB instead where as Rust tends to panic. So in practice, your suggestion ends up preferring vulnerabilities as a result of UB versus a DoS.
See: https://news.ycombinator.com/item?id=43300120 https://news.ycombinator.com/item?id=43300120
Can you show me C libraries you've written that are used by others that follow your strategy of turning broken runtime invariants into error values?
- lelanthran 2y ago> No, this is absurd. 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? To be even more clear, I'm not making the argument that the program should proceed as normal after detecting an error (regardless of the type of error). That is not the argument that I am making which is why I find your "That's absurd" condescension extremely confusing.
- burntsushi 2y agoMy position is articulated here, with real examples: https://burntsushi.net/unwrap/ https://burntsushi.net/unwrap/ > 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.
- vacuity 2y agoI 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.
- int_19h 2y agoThis is exactly what assert() does, so literally any C library that contains an assert() can produce a nonrecoverable error. GP's point is that most libraries will not even assert(), but instead just assume the invariant holds and proceed accordingly, resulting in UB. And it is, of course, infeasible for a library to constantly test for invariants holding at every single point in the program. So in practice you have to assume that a library breaking its internal invariants is going to UB. If library dev added some asserts to this, they are doing you a favor by making sure that, at least for some particular subset of broken invariants, that UB is guaranteed to be a clean abort rather than running some code doing god knows what.