6 ms·
I'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 o
by rtpg 2y ago
I'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.
- eru 2y agoFor what it's worth, eg vscode can jump to definition even when your code is in a different crate that's not in your repository.
- haberman 2y agoI don't think grepping for panics is practical, unless you are trying to depend on exclusively no-panic libraries. Even if you are no_std, core has tons of APIs like unwrap(), index slicing, etc. that can panic if you violate the preconditions. It's not practical to grep for all of them.
- wongarsu 2y agoThere is panic-analyzer [1] that searches for code that needlessly panics. You can also use the no-panic macro [2] to turn possible panics in a specific function (including main) into a compile error 1: https://crates.io/crates/panic-analyzer https://crates.io/crates/panic-analyzer 2: https://crates.io/crates/no-panic https://crates.io/crates/no-panic
- haberman 2y agoPanic-analyzer looks like it is based on heuristics, searching for known-panicing APIs. I tried it on a workspace that uses io::stdout() and it did not flag this as potentially panicing. No-panic looks nifty: it appears to be reliable, which is great. I wish there was an easy way to automatically apply this annotation to every single function in a given file or crate.
- GolDDranks 2y agoI think the article is wrong in that std::io::stdout would be panicking, or that "the panic is reachable somehow". It's just the optimizer doesn't see it doesn't panic. https://doc.rust-lang.org/beta/src/std/io/stdio.rs.html#674-679 https://doc.rust-lang.org/beta/src/std/io/stdio.rs.html#674-... The implementation calls indeed a panicking API, OnceLock::get_or_init: https://doc.rust-lang.org/beta/std/sync/struct.OnceLock.html#method.get_or_init https://doc.rust-lang.org/beta/std/sync/struct.OnceLock.html... But it only panicks if it is being used in a wrong way, which it isn't. The usage is contained within the implementation of std::io::stdout, so it's an implementation detail. It's a shame that there are no better ways to eliminate panics in case they are impossible to trigger. The article shows some tricks, but I think the language is missing still some expressability around this, and the stdlib should also thrive harder to actually get rid of hard-to-optimize links to panic runtime in case of APIs that don't actually panic.
- rtpg 2y agoIt is interesting to consider how `panic!` serves as some documentation of explicitly giving up. Easy to see in a pull request. And having the string packed alongside it is nice. Still miffed, but we'll get there.
- sophacles 2y agoThere are panics that aren't greppable that way. For instance `some_array[past_bounds]` causes a panic.
- ninetyninenine 2y agoSometimes the program is in an invalid state. You don't want to keep running the program. Better to fail spectacularly and clearly then to fail silently and try to hobble along.
- jwatte 2y agoThe thing with functional programming (specifically, immutable data,) is that as long as the invalid state is immutable, you can just back up to some previous caller, and they can figure out whether to deal with it or whether to reject up the its previous caller. This is why Result (or Maybe, or runExceptT, and so on in other languages) is a perfectly safe way of handling unexpected or invalid data. As long as you enforce your invariants in pure code (code without side effects) then failure is safe. This is also why effects should ideally be restricted and traceable by the compiler, which, unfortunately, Rust, ML, and that chain of the evolution tree didn't quite stretch to encompass.
- duped 2y agoSay a function has some return type Result<T, E>. If our only error handling mechanism is Err(e) then were restricted to E representing the set of errors due to invalid arguments and state, and the set of errors due to the program itself being implemented incorrectly. In a good software architecture (imo) panics and other hard failure mechanisms are there for splitting E into E1 and E2, where E1 is the set of errors that can happen due to the caller screwing up and E2 being the set of errors that the caller screwed up. The caller shouldn't have to reason about the callee possibly being incorrect! Functional programming doesn't really come into the discussion here - oftentimes this crops up in imperative or object oriented code where function signatures are lossy because code relies on side effects or state that the type system can't/won't capture (for example, a database or file persisted somewhere). Thats where you'll drop an assert or panic - not as a routine part of error handling.
- turboponyy 2y agoYou shouldn't pass invalid values to a function. If a function can return some sensible value for some input, then the input is not invalid - even if the return type is an error by name. Ideally, you can constrain the set of inputs to only valid ones by leveraging types. But if that's not possible and a truly invalid input is passed, then you should panic. At least that's the mental model that Rust is going with. You do lose out on the ability to "catch" programming errors in subcomponents of your program. For example, it's extremely useful to catch exceptions related to programming errors for called code in response to a web request, and return a 500 in those cases. One could imagine a "try" "catch" for panics. The thing is, it takes a lot of discipline by authors to not riddle their code with panics/exceptions when the language provides a try/catch mechanism (see C# and Java), even when a sensible error as value could be returned. So Rust opts to not introduce the footgun and extra complexity, at the expense of ungraceful handling of programming errors.
- deleted 2y ago[deleted]
- duped 2y agoWhile sure, more things could be baked as results, most of the time when you see a panic that's not the case. It's a violation of the callee's invariants that the caller fucked up. Essentially an error means that the caller failed in a way that's expected. A panic means the caller broke some contract that wasn't expressed in the arguments. A good example of this is array indexing. If you're using it you're saying that the caller (whoever is indexing into the array) has already agreed not to access out of bounds. But we still have to double check if that's the case. And if you were to say that hey, that implies that the checks and branches should just be elided - you can! But not in safe rust, because safe code can't invoke undefined behavior.
- rtpg 2y agoI understand the value of panic when your invariants really are no longer holding. What I have seen is many cases of "oh a micro-invariant I kind of half believe to be true isn't being held, and so I will panic". Obviously context-free this is very hand wave-y, but would you want Firefox to crash every time a website prematurely closes its connection to your browser for whatever reason? No, right? You would want Firefox to fail gracefully. That is what I wanted.