4 ms·
I do not claim that No-Panic Rust is popular (or even used at all) in Rust libraries currently. If it was popular, I would not have had to think so hard about
by haberman 2y ago
I do not claim that No-Panic Rust is popular (or even used at all) in Rust libraries currently. If it was popular, I would not have had to think so hard about it and write a blog entry. I claim that this technique is widespread in C libraries, and I believe I have demonstrated that.
Our conversation was sidetracked because you claimed that panic and unwrap() were essential parts of how Rust provides safety, and that the C precedent doesn't apply because C's approach is unsafe. But I claim that No-Panic Rust is potentially a solution that gives you the best of both worlds: comparable safety without risk of a (detected) bug crashing the entire process. So I do think that the C precedent applies.
I grant that there are applications where panics are a perfectly reasonable way of handling internal errors. Your ripgrep is a perfect example: it's a short-lived process that only does one thing, and users are running it from a terminal (and are probably tech savvy) so they can easily copy and paste the crash into a bug report.
But there are lots of other applications that are not like this. Consider the Linux kernel, where a panic takes down your entire computer. Or consider a mobile (iOS or Android) application where there is no terminal to dump to, and the user experience of a crash is that the app closes unexpectedly and without explanation. Or consider a web browser where it would be very annoying for an entire tab or browser to crash just because one operation (like using the search box) ran into an internal error.
In most of these cases, you want to let the program continue if reasonably possible after an error is encountered, while also logging the error for later inspection/diagnosis and possibly telemetry. Probably you will be abandoning any internal state associated with the failing operation.
It's true that my blog uses unsafe in two cases to get rid of panics. The first is to call libc::printf(), but this is only required because the Rust stdlib does not offer any No-Panic API for printing to stdout. This is really just a symptom of the fact that No-Panic programming has little precedent in Rust. If there was a No-Panic variant of the standard library, it could offer a safe API for printing to stdout.
The second case is an optimization, where we are trying to remove a bounds check for performance reasons. This is an example of "opting out on a case-by-case basis", except what I am proposing is more principled and arguably safer than merely switching to get_unchecked(). I am asserting the underlying invariant of the data structure, and then letting the optimizer infer that the invariant implies that the bounds check is not necessary. I think this is pretty interesting, and very cool that the compiler is able to do this.
So overall I do argue that No-Panic Rust offers comparable safety to panics and unwrap().
The Rust port of upb is on the back burner currently, and nothing is open-sourced yet.
- burntsushi 2y ago> I do not claim that No-Panic Rust is popular (or even used at all) in Rust libraries currently. I didn't say you did! Goodness this conversation is super frustrating. I'm not trying to get you to legitimize your opinions by pointing to popularity, but I just want to see some examples of your philosophy actually working in real world scenarios. > I claim that this technique is widespread in C libraries, and I believe I have demonstrated that. I have yet to see any such evidence. The C libraries you've shown me have a litany of UB branches where Rust would have panicking branches. None of the C libraries you've linked are coded in the style demonstrated in your blog. If they were, there would be a whole lot more invariant checking (like bounds checks) leading to error codes for those invariant violations. Instead, the C code primarily just lets UB take over for internal invariant violations. Which may indeed wind up in an abort. Or someone stealing your credit card numbers. ¯\_(ツ)_/¯ That's not at all the style you advocate for in your blog. The C libraries you link do have some error codes for something resembling internal invariant violations, but from my review, this is not practiced generally and is far more limited than the style you advocate for in your blog. > Your ripgrep is a perfect example I specifically did not cite ripgrep as an example. I cited my libraries. I might be best known for my work on ripgrep, but the vast majority of Rust work I've done over the last decade is in libraries. And those are used in all sorts of places. Moreover, it isn't just my libraries that use this philosophy. It's pretty much all of them, including std. > Consider the Linux kernel, where a panic takes down your entire computer. The Linux kernel is one of the few places where I've seen someone argue compellingly for "prefer UB on invariant violations generally, and not panicking." I don't agree with them, but I don't have any practical experience in that specific domain to refute them. Indeed, I view the practice quite skeptically, given that I'd greatly prefer my computer to shut down than, to, say, corrupt my data on disk. > Or consider a mobile (iOS or Android) application where there is no terminal to dump to, and the user experience of a crash is that the app closes unexpectedly and without explanation. Or consider a web browser where it would be very annoying for an entire tab or browser to crash just because one operation (like using the search box) ran into an internal error. > > In most of these cases, you want to let the program continue if reasonably possible after an error is encountered, while also logging the error for later inspection/diagnosis and possibly telemetry. Probably you will be abandoning any internal state associated with the failing operation. Your suggestion here amounts to asking Rust libraries to guarantee reasonable and consistent behavior when internal runtime invariants have been broken. That's the only way, "return an error for a broken invariant and otherwise continue on" actually works. I don't see how that's tractable and this is why I ask for examples. There is nothing you can say that's going to convince me. I have to be shown. Because the fundamental component of my skepticism is seeing the practice in the real world and the kinds of effects it has that are not captured by either your analysis or mine. Indeed, my years of experience building fundamental ecosystem libraries in Rust tells me that your approach does not scale. At all. I, several comments ago, carefully conceded that the style of Rust you advocate may be useful in niche scenarios. So my position is not, "your philosophy is never useful and it should never be used." My position is, "it is not good idea generally, and it does not match the prevailing convention of C libraries." I think this conversation has probably run its course. Sincerely, I would like to see examples of your practice more broadly. I want to see how it works and what the real and actual trade-offs are.