5 ms·
> When invariant violations or mistakes by programmers (aka bugs) are detected, the program should halt as it is in an inconsistent state and continuing could b
by alerighi 4y ago
> When invariant violations or mistakes by programmers (aka bugs) are detected, the program should halt as it is in an inconsistent state and continuing could be very dangerous (think privacy/security/data corruption). Otherwise, don't halt (handle it or have the caller handle it).
Well it's not always the case. There are situations in which if you detect errors you want the program to continue running, and have only that particular functionality to fail.
I tend to write resilient code, since I work in embedded systems and what you never want is the system to crash. Halting a CPU on an invariant violation (i.e. and assert failing) is something useful for debugging (you trigger the debugger and you then analyze why it happened), but something you generally don't want in production.
Bette to have a ton of checks more and in case of an invariant violation (that maybe is resulting from a programmer mistake, but there is always the possibility of hardware memory corruption errors) to return an error and handle it in some ways (for example restart the task that returned the error, trying to go back to the last working state).
- burntsushi 4y ago> There are situations in which if you detect errors you want the program to continue running, and have only that particular functionality to fail. Yes, like a web server. If a request handler fails by panicking, in a Rust program, you catch the panic, respond with a 500 error and log the panic somewhere. But you continue serving other requests. I talked about this in the blog post. The problem with your strategy is that it requires you to be aware of your own mistakes. That doesn't sound like a robust strategy, unless you're investing huge resources into sophisticated tooling and have drastically restricted the expressivity of your programming environment. That exists and is fine, and I even addressed that in the blog post too.
- marshray 4y agoGreat article BTW, loved it! Will certainly become a classic. The web server example scares me. Something happened during execution that the programmer didn't expect. There's a bug in the program. What if the panic is due to memory corruption (less likely in Rust) or internal data structure corruption? Without knowledge to the contrary, swallowing a panic and YOLO'ing execution is driving full speed down the road of very poorly defined behavior. If the programmer had sufficient knowledge to conclude it was safe, they could have just used a Result<> to report the error. So ... don't make panic part of your API, and don't recover from panics?
- burntsushi 4y agoDoesn't scare me in a memory safe language. Request handlers tend to be very well isolated. It's standard practice for web servers written in Go too. > So ... don't make panic part of your API, and don't recover from panics? That's the wrong lesson. As my blog says, don't use panicking for error handling. Making panicking part of the API is standard practice. Otherwise `slice[i]` wouldn't exist. (Its API is to panic when `i` is out of bounds.) Lest you think you should just never panic and convert everything into errors, that's addressed in the blog too. :)
- marshray 4y agoAs the sibling post said: > I mean, if all bets are off, all bets are truly off. We have to expect that panic (e.g. from slice index operator) exercised an execution path that the library developer had never even considered, much less validated. So then, post-panic, are we left with any guarantees about the validity of postconditions?
- burntsushi 4y ago> So then, post-panic, are we left with any guarantees about the validity of postconditions? Yes, because in order to catch a panic, the routine you're running has to be unwind-safe: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html If you want to go on crashing your process and restarting it, then that's fine. It's a fine strategy. But you can't ignore that request handlers tend to be very well isolated, and catching a panic from one of them while continuing to service requests actually works well in practice. Oodles of folks are doing this in production. I've been doing it production for years. I have literally, not once, not ever, seen any problems arise because of this. Consider what happens if there's a 'slice[i]' that panics in a regex library. The stack unwinds and any objects associated with that specific search that panicking get dropped. And since 'Regex' implements 'UnwindSafe', it says, "If a panic occurs it's all good to continue using me if you catch the panic." You keep wanting to argue the theory. Rust has UnwindSafe for that. But please, consider looking at the practice too. You don't seem to have acknowledged that at all.
- Tainnor 4y ago> The problem with your strategy is that it requires you to be aware of your own mistakes. That doesn't sound like a robust strategy, unless you're investing huge resources into sophisticated tooling and have drastically restricted the expressivity of your programming environment. That exists and is fine, and I even addressed that in the blog post too. Those "huge resources" and "sophisticated tooling" could just mean something like Erlang, where fault isolation is a core design principle. But fault isolation works in simpler systems, too. Here's a recent example: I wrote a function that automatically generates a table of contents for an HTML document. While extensively testing it, I ran across a number of edge cases that would lead to faults (mostly due to messy underlying libraries, but also due to programmer error). I fixed all that I found, but I still didn't feel comfortable not adding a generic exception handler to the function. In case that an exception is ever thrown in that code, the function will just return the input HTML unchanged (and log an error, of course). That's graceful degradation, and a good example of recovering from a potentially unknown fault. Similar situations can often be found in many applications. Not all functions are essential, and we're not always 100% confident about all parts of the code working correctly. (Note that I'm not talking about Rust here, I lack sufficient knowledge about it. It's possible that this strategy wouldn't work in Rust for reasons of memory-safety or anything else.)
- mook 4y ago> I tend to write resilient code, since I work in embedded systems and what you never want is the system to crash. That might be okay too depending on what your system is. I've had a cell phone (the monochrome dumb kind) display an assertion error at me, that was kind of cool. A screw was coming loose, so there were hardware issues. Worst case, I'd have to bring it to the store and get it repaired or replaced; there was no threat of injury or large monetary damage.