14 ms·
No-Panic Rust: A Nice Technique for Systems Programming
- staunton 2y agoThis website makes by browser freeze... No idea why. Not able to read the article.
- haberman 2y agoAuthor here -- that is surprising. What browser/OS are you on? I haven't had anyone else report this problem before.
- TallonRain 2y agoI’m seeing the same problem, the page crashes on Safari on iOS, saying a problem repeatedly occurred. Haven’t seen a webpage do that in quite a while.
- faitswulff 2y agoYep, same experience, same platform. I guess straight to reader mode, it is. EDIT - shockingly, reader mode also fails completely after the page reloads itself
- ubj 2y agoI'm seeing the same problem--the page crashes on mobile (Brave). On desktop it loads, but all of the code cells have a crashed page symbol in them.
- wbobeirne 2y agoSame issue on Android using Brave.
- lacraig2 2y agosame on chrome android
- IX-103 2y agoI'm also seeing this on Android Chrome. When I opened the page on my Linux desktop, I also saw the crashes (though they only affected the godbolt iframes). Note that on Android process separation is not usually as good, so a crashing iframe can bring down the whole page.
- anymouse123456 2y agosame for me. Chrome on Pixel 8
- flohofwoe 2y agoSame here: Chrome on a Google Pixel 4a. Page freezes while scrolling down and eventually oh-snaps.
- DemetriousJones 2y agoSame, the web view in my Android client crashed after a couple seconds.
- haberman 2y agoI wonder if it's all the Godbolt iframes. Do you have the same problem on other pages, like https://blog.reverberate.org/2025/01/27/an-ode-to-header-files.html https://blog.reverberate.org/2025/01/27/an-ode-to-header-fil... ?
- wavemode 2y agoOther pages on the site work fine for me yeah. But the OP blog post is crashing my Android browser, like the other commenters have mentioned.
- IX-103 2y agoYeah, I think it's all those iframes. I'm seeing something weird on my Linux desktop - all the godbolt iframes crash on reload unless I have another tab with godbolt open. I didn't see anything obvious in Chrome's log. I can't replicate the crash at all on my Linux cloud VM though. Usually the only difference there is that advertisers tend to not buy ads for clients on cloud IPs.
- DemetriousJones 2y agoYeah no problem on other pages
- arijun 2y agoThis happened to me too (Safari on iOS). Here’s a archived link: https://web.archive.org/web/20250204050500/https://blog.reverberate.org/2025/02/03/no-panic-rust.html https://web.archive.org/web/20250204050500/https://blog.reve...
- XorNot 2y agoThis seems..absurd for a programming language with goals like Rust. Why isn't this a compiler option? Just set -nopanics and the compiler errors and flags anything which is pulling in a panic at the very least?
- dccsillag 2y agoYeah, I'm fairly sure that there is such a flag/toplevel attribute... and if there isn't, there should be one. It also feels like most of the pains on avoiding panics centers around allocations which, though a bit unfortunate, makes sense; it was an intentional design choice to make allocations panic instead of return Results, because most users of the language would probably crash on allocation fails anyways and it would introduce a lot of clutter. There was some effort some while ago on having better fallible allocations, but I'm not sure what happened over there.
- dpc_01234 2y agohttps://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html#unrecoverable-errors-with-panic https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-... ``` [profile.release] panic = 'abort' ```
- rcxdude 2y agothat just makes the panics unrecoverable. It doesn't statically guarantee no panics.
- lmm 2y agoIt presumably avoids the linked in 300Kb that was supposedly part of the motivation for doing this though?
- wongarsu 2y agoThere is no-panic [1] to turn panics in a function into compile errors, or the older no-panics-whatsoever [2] to do the same for the entire binary 1: https://crates.io/crates/no-panic https://crates.io/crates/no-panic 2: https://crates.io/crates/no-panics-whatsoever https://crates.io/crates/no-panics-whatsoever
- davisp 2y agoDoes anyone know if there's an obvious reason that adding a `no_panic` crate attribute wouldn't be feasible? It certainly seems like an "obvious" thing to add so I'm hesitant to take the obvious nerd snipe bait.
- 7e 2y agoYou could do it, but I would prefer guarantees on a per-call chain basis using a sanitizer. It should be quite easy to write.
- davisp 2y agoI'm no rustc expert, but from what little I know it seems like disabling panics for a crate would be an obvious first step. You make a great point though. Turning that into a compiler assertion of "this function will never panic" would also be useful.
- 7e 2y agoIt’s a good first step, but half of the crates in crates.io have at least 40 transitive dependencies. Some have hundreds or thousands. A big effort.
- hathawsh 2y agoThe standard library has a significant amount of code that panics, so a `no_panic` crate attribute would currently only work for crates that don't depend on the standard library. I imagine most interesting crates depend on the standard library.
- davisp 2y agoWhat caught my eye in the article was the desire to have something that doesn't panic with a release profile, while allowing for panics in dev profiles. Based on other comments I think the general "allow use of std, but don't panic" seems like something that could be useful purely on the "Wait, why doesn't that exist?" reactions.
- meltyness 2y agoWhile not as strict, you can filter lints by their description and apply a policy to your crate https://rust-lang.github.io/rust-clippy/rust-1.84.0/index.html#/panic https://rust-lang.github.io/rust-clippy/rust-1.84.0/index.ht...
- andyferris 2y agoThis seems to obviate a lot of Rust's advantages (like a good std library). I wonder what it would take to write a nopanic-std library? Panics really seem bad for composability. And relying on the optimzer here seems like a fragile approach. (And how is there no -nopanic compiler flag?)
- wongarsu 2y agoThe standard library is slowly adding non-panicking options. The article shows some of them (like vec.push_within_capacity()) and ignores some others (vec.get_unchecked()). There is still a lot of work to do, but it is an area where a lot of work gets done. The issue is just that a) Rust is still a fairly young language, barely a decade old counting from 1.0 release, and b) Rust is really slow and methodical in adding anything to stdlib because of how hard/impossible it is to reverse bad decisions in the stdlib. The same article written a couple years in the future would look very different
- swiftcoder 2y agoTo be slightly pedantic, Vec::get() is the non-panicking version. Vec::get_unchecked() is just the version thereof that elides the bounds check.
- gpm 2y agoRust doesn't want to add any proof-system that isn't 100% repeatable, reliable, and forwards compatible to the language. The borrow checker is ok, because it meets those requirements. The optimizer based "no panic" proof system is not. It will break between releases as LLVM optimizations change, and there's no way to avoid it. Trying to enforce no-panics without a proof system helping out is just not a very practical approach to programming. Consider code like some_queue.push_back("new_value"); process(some_queue.pop_front().unwrap()); This code is obviously correct. It never panics. There's no better way to write it. The optimizer will instantly see that and remove the panicing branch. The language itself doesn't want to be in the business of trying to see things like that. Or consider code like let mut count: usize = 0; for item in some_vec { // Do some stuff with item if some_cond() { count += 1; } } This code never panics. Integer arithmetic contains a hidden panic path on overflow, but that can't occur here because the length of a vector is always less than usize::MAX. Or so on. Basically every practical language has some form of "this should never happen" root. Rust's is panics. C's is undefined behavior. Java's is exceptions. Finally consider that this same mechanism is used for things like stack overflows, which can't be statically guaranteed to not occur short of rejecting recursion and knowledge of the runtime environment that rustc does not have. --- Proof systems on top of rust like creusot or kani do tend to try to prove the absence of panics, because they don't have the same compunctions about not approving code today that they aren't absolutely sure they will approve tomorrow as well.
- rtpg 2y agoI've had an unpleasant amount of crashes with Rust software because people are way too quick to grab `panic!` as an out. This was most shocking to me in some of the Rust code Mozilla had integrated into Firefox (the CSS styling code). There was some font cache shenanigans that was causing their font loading to work only semi-consistently, and that would outright crash this subsystem, and tofu-ify CJK text entirely as a result. And the underlying panic was totally recoverable in theory if you looked at the call stack! Just people had decided to not Result-ify a bunch of falliable code.
- dralley 2y agoAt least sources of panic! are easily greppable. Cutting corners on error handling is usually pretty obvious
- pluto_modadic 2y agoI mean... rust modules aren't typically in your CWD, no? they're not in some node_modules that you can grep, but in a cargo folder with /all of the libraries you ever used/, not just the ones you have for this one project.
- gpm 2y agoPutting them all in the project root takes just a single `cargo vendor` command. But I would assume that for mozilla their entire CSS subsystem is pulled in as a git (hg?) submodule or something anyways.
- est31 2y agonot sure how the CSS subsystem is included (I remember it is shared with Servo?), but in general all of the Rust dependencies in mozilla-central is vendored, so nothing is in your ~/.cargo directory.
- est31 2y agoIf you run cargo vendor, they end up in a neat directory.
- wongarsu 2y agoThe approach at the end of declaring invariants to the compiler so the compiler can eliminate panics seems accidentally genius. You can now add the same invariants as panicking asserts at the end of each function, and the compiler will prove to you that your functions are upholding the invariants. And of course you can add more panicking asserts to show other claims to be true, all tested at compile time. You've basically built a little proof system. Sure, Rust is hardly the first language to include something like that and adoption of such systems tends to be ... spotty. But if it was reliable enough and had a better interface (that preferably allowed the rest of your program to sill have panics) this might be very useful for writing correct software.
- jwatte 2y agoFor systems where correctness is actually important, not just a nice-to-have (in most systems, it's nice-to-have,) we have had an increasing number of options over the years. From tools like "spin" and "tla+" to proof assistants like Coq to full languages like Idris and Agda. Some of the stronger-typed languages already give us some of those benefits (Haskell, OCaml) and with restricted effects (like Haskell) we can even make the compiler do much of this work without it leaking into other parts of the program if we don't want it to.
- haberman 2y agoWhile I would love this to be true, I'm not sure that this design can statically prove anything. For an assert to fail, you would have to actually execute a code sequence that causes the invariant to be violated. I don't see how the compiler could prove at compile time that the invariants are upheld.
- wongarsu 2y agoAn assert is just a fancy `if condition { panic(message) }`. If the optimizer can show that condition is always false, the panic is declared as dead code and eliminated. The post uses that to get the compiler to remove all panics to reduce the binary size. But you can also just check if panic code was generated (or associated code linked in), and if it was then the optimizer wasn't able to show that your assert can't happen. Of course this doesn't prove that the assert will happen. You would have to execute the code for that. But you can treat the fact that the optimizer couldn't eliminate your assert as failure, showing that either your code violates the assert, your preconditions in combination with the code aren't enough to show that the assert isn't violated, or the whole thing was too complicated for the optimizer to figure out and you have to restructure some code
- 7e 2y agoIt should be possible to write a sanitizer which verifies no panic behavior on a call graph, just as you can to verify no blocking, or no races.
- amelius 2y ago> Protocol Buffers Instead of serializing data (to disk, not the network), it would be much faster if Rust allowed us to allocate datastructures directly in an mmapped file, and allowed us to read back the data (basically patching the pointers so they become valid if the base address changed).
- lilyball 2y agoThis is basically what Cap'n Proto does, and it uses offsets instead of pointers so that way the mmapped data can be used as-is.
- amelius 2y agoGood to know, but does Rust have the machinery to make it work with standard Rust data structures?
- lilyball 2y agoWhat do you mean by "standard Rust data structures"? Like protobuf and other such formats, capnp uses code generation. The tooling for Rust is at https://github.com/capnproto/capnproto-rust https://github.com/capnproto/capnproto-rust and the generated code gives you structs that wrap borrowed data and give you accessors rather than direct field access.
- amelius 2y agoWell, it would be super convenient if you could use the standard collections like Vec, LinkedList, HashMap, etc. just like you would use them normally except now they are allocated inside an mmapped file.
- btown 2y agoDoes Rust have something like a deep-codemodding macro that could be used to un-panic-fy an entire function etc. automatically? Something like: Given a function, rewrite its signature to return a Result if it doesn't already, rewrite each non-Resulty return site to a Some(), add a ? to every function call, then recurse into each called function and do the same.
- dccsillag 2y agoIt has `catch_unwind` [1], but that still retains the panicking runtime, so not sufficient in the context of the post. [1] https://doc.rust-lang.org/std/panic/fn.catch_unwind.html https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
- gpm 2y agoIt's also not guaranteed to catch every panic - sometimes (notably if a destructor panics during unwinding) a panic can turn into a process-abort.
- LegionMammal978 2y agoTo add to that, Rust code is generally not written to be 'exception-safe' when panics occur: if a third-party function causes a panic, or if your own code panics from within a callback, then memory may be leaked, and objects in use may end up in an incorrect or unusable state. You really want to avoid sharing mutable objects across a catch_unwind() boundary, and also avoid using it on a regular basis. Aside from memory leaks, panicking runs the thread's panic hook, which by default prints a stacktrace. You can override the panic hook to be a no-op, but then you won't see anything for actual panics.
- meling 2y agoPerhaps verus would be helpful to the op: https://github.com/verus-lang/verus https://github.com/verus-lang/verus
- nektro 2y agoOP sounds like they'd be very interested in Zig to tackle this particular problem. they'd get to a very similar place and not have to fight the language or the standard library to get there.
- Joker_vD 2y ago> If we are trying to port a C library to Rust, we really do not want to introduce panics in the code, even for unusual error condition. "We'd much rather like to make library to corrupt the memory of the rest of the application and generally make the demons fly out of the users' noses, as it does when written in C"? I believe implementations of C stdio also can abort on program startup if somehow the pthreads' locking mechanism is broken (or if e.g. fcntl(2)/open(2) keeps returning -1), Rust is not that unique in this regard.
- vollbrecht 2y agoMost people are using a prebuild standard library. That comes with the problem that it comes with the features it was build for. Most of the bloat around panic for example can be eliminated by just compiling the std library yourself. This is done via the `-Zbuild-std` flag. Using this flag one than can use `panic_abort`. This will eliminate the unwinding part but would still give a "nice" printout on a panic itself. This reduces, in most cases, the mention bloat by a lot. Though nice printouts also cost binary space. For eliminating that `panic_immidiate_abort` exists. But yeah the above is only about bloat and not the core goal to eliminate potential path's in your program, that would lead to a panic condition itself. Also currently building the std library yourself needs a nightly compiler. There is afaik work on bringing this to a stable compiler but how exactly is still work in progress.
- rizky05 2y ago[dead]
- alkonaut 2y agoWhy worry about the code size if the code size is up to the library consumer (through their choice of panic handler)? If the consumer worries about code size, then their application has a minimal panic handler. If the consuming application does not have a minimal panic handler, then it must not worry about code size? Is there some context I'm missing here? Is this to be used from non-Rust applications for example?
- hkwerf 2y agoAs the author mentions, `panic!` is also not an acceptable failure mode in some applications. If you're developing safety-critical software and a process stopping is part of your risk analysis, many frameworks will ask you about the frequency of that happening. In that analysis, you may be required to set all systematic contributions to that frequency to zero. This happens, for example, if you try to control the associated risk using redundancy. If there is a code path that may panic, you may not be able to do this at all as you maybe just cannot conclude that your code does not panic systematically.
- alkonaut 2y agoYes that condition I understand. But that seems orthogonal to the code size issue. Having no panics in code where the stdlib is riddled with panics for exceptional situations (allocation failure, for example) seems like a situation where you would just always go with no_std?
- hkwerf 2y agoIt is orthogonal, yes. To your question, I have an example from the same domain, where it is reasonable to mix unrolling panic with code that never panics. Typically, safety-related processes are set up in two phases. First they set up, then they indicate readiness and perform their safe operation. A robot, for example, may have some process checking the position of the robot against a virtual fence. If the probability for passing through that fence passes some limit, this requires the process to engage breaks. The fence will need to be loaded from a configuration, communication with the position sensors will need to be established, the fence will generally need to be transformed into coordinates that can be guaranteed to be checked safely, taking momentum and today's breaking performance in account, for example. The breaks itself may need to be checked. All that is fine to do in an unsafe state with panics that don't just abort but unroll and full std. Then that process indicates readiness to the higher-level robot control process. Once that readiness has been established, the software must be restricted to a much simpler set of functions. If libraries can guarantee that they won't call panic!, that's one item off our checklist that we can still use them in that state.
- pedromsrocha 2y agoThis blog post is very interesting, using Rust’s compiler optimizer as a theorem prover. This makes me wonder: are there any formal specifications on the complexity of this "optimizer as theorem prover"? Specifically, how does it handle recursion? Consider, for example, the following function, which decrements a number until it reaches zero. At each step, it asserts that the number is nonzero before recursing: fn recursive_countdown(n: u32) -> u32 { assert!(n > 0, "n should always be positive"); if n == 1 { return 1; } recursive_countdown(n - 1) } Can the compiler prove that the assertion always holds and possibly optimize it away? Or does the presence of recursion introduce limitations in its ability to reason about the program?
- saagarjha 2y agoCompilers can typically reason fairly well about tail recursion like this. In this case the compiler cannot remove the assertion because you could pass in 0. But if you change the assert to be a >= 0 (which is vacuously true, as the warning indicates) it will optimize the code to "return 1" and eliminate the recursive call: https://godbolt.org/z/jad3Eh9Pf https://godbolt.org/z/jad3Eh9Pf
- jerf 2y ago> This makes me wonder: are there any formal specifications on the complexity of this "optimizer as theorem prover"? Basically, the promise here "We formally promise not to promise anything other than the fact that optimized code should have 'broadly' the same effects and outputs as non-optimized code, and if you want to dig into exactly what 'broadly' means prepare to spend a lot of time on it". Not only are there no promises about complexity, there's no promises that it will work the same on the same code in later versions, nor that any given optimization will continue firing the same way as you add code. You can program this way. Another semi-common example is taking something like Javascript code and carefully twiddling with it such that a particular JIT will optimize it in a particular way, or if you're some combination of insane and lucky, multiple JITs (including multiple versions) will do some critical optimization. But it's the sort of programming I try very, very hard to avoid. It is a path of pain to depend on programming like this and there better be some darned good reason to start down that path which will, yes, forever dominate that code's destiny.
- scotty79 2y agoFunnily enough this website hard-crashes Android Chrome and DuckDuckGo browsers after loading.
- deleted 2y ago[deleted]
- sneilan1 2y agoThis website panics on my iOS version 18.1.1 in Safari. Is anyone else having issues on mobile?
- dathinab 2y ago> Unrecoverable panics are very much designed to be recoverable at some well defined boundaries (e.g. the request handler of a web server, a thread in a thread pool etc.) this is where most of it's overhead comes from you can use panic=abort setting to abort on panics and there is a funny (but unpractical) hack with which somewhat can make sure that no not-dead-code-eliminated code path can hit a panic (you link the panic->abort handler to a invalid symbol)
- nicce 2y agoI would say that you are using them incorrectly if you assume them as recoverable. You should make everything you can so that they never happen. However, since it is still possible to have them in a place where the exiting the process is not okay, it was beneficial to add a way to recover from them. It does not mean that they are designed to be recoverable. > this is where most of it's overhead comes from Overhead comes from the cleaning process. If you don't clean properly, you might leak information or allocate more resources than you should.
- dathinab 2y ago> I would say that you are using them incorrectly if you assume them as recoverable. no it's them being recoverable at well defined boundaries is a _fundamental_ design aspect of rust > Overhead comes from the cleaning process. If you don't clean properly, you might leak information or allocate more resources than you should. and that same cleanup process makes it recoverable
- nicce 2y agoEven the book uses the word "unrecoverable". The wording is communication. The intention is not to recover them, while it could be possible. https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-...
- sieabahlpark 2y ago
- dathinab 2y ago> fn check_invariant(&self) { > unsafe { assert_unchecked(self.ofs < > self.data.len()) } > } Is fundamentally unsound `check_invariant` needs to be unsafe as it doesn't actually check the invariant but tells the compiler to blindly assume they hold. Should probably also be named `assume_invariant_holds()` instead of `check_invariant()`.
- vlovich123 2y agoI personally would have used the assume crate but I guess this got standardized more recently. They call out the safeness requirement and that it’s a sharp edge but like you I think they understate the sharpness and danger.
- virtualritz 2y agoThis page reliably crashes Chrome on my Android phone as a well as on my Ubuntu laptop.
- usefulcat 2y ago> not panic'ing (or at least not doing it so bluntly at a low level) can at the very least help with debugging. Wouldn’t panicking asap make debugging easier?