Please stop using panic! all over the place! Here's a better choice: https://docs.rs/anyhow/1.0.44/anyhow/
I agree! Latest trunk has 453 panic!() calls! That's just insane! :laughing: (to be fair: I checked with grep so there might be false positives in this number)
Here's a tool that stops compilation after first error
cargo watch -cs "cargo first run"
cargo install cargo-watch cargo-first
I'm working on making roc_cli panic-free
https://github.com/rtfeldman/roc/pull/1859
I am now trying to make /compiler/build panic-free!
We already have error formatting in the reporting crate. We'd want to use a consistent style across all error messages
also for development we absolutely need the source location where the panic occured. Does anyhow provide that?
I mean the rust source location, panics give file and line number
yeah the #1 reason we've used panic! and todo! is as a placeholder for "doing this properly will take significantly longer, so let's get something working and revisit this later"
so if anyone wants to revisit them and make them do the right thing instead, that would be awesome! :heart_eyes:
however, the right thing is not "replace panic with anyhow and call it a day" :big_smile:
it's going to vary a lot on a per-panic basis!
e.g. I know in several cases it's "we already have a nice error handling system in place, but we didn't take the time to assemble the necessary data structures it needs, and just panicked instead for now"
and in others it's "we handle 80% of the branches of this pattern match but not all of them, so the actual solution is not to error out at all, but rather to finish implementing the other branches"
etc
so @Matthias Beyer I love the idea of making more things panic-free, and reviewing a PR for that would make me super happy! 🤩
I would recommend following a guideline of "don't introduce any new error handling dependencies" (such as anyhow) without discussing first, because otherwise I there's a high risk of the PR feedback being "this isn't how we want to handle these errors; actually it's ______" which would be easy to avoid with a little discussion instead! :smiley:
Folkert de Vries said:
also for development we absolutely need the source location where the panic occured. Does anyhow provide that?
Yes
Most of the panic is caused by unwrap.
With anyhow, every error can be caused by other errors. I'll try not to alter existing error reporting behavior.
ah damn I just saw your PR @Locria Cyber - sorry, I wish we'd talked about this sooner!
We can still talk about this now.
Yeah let's!
I have some time but will have to go in 10ish minutes (but happy to talk more about it tonight)
I added MaybeError that describe either a typed error that will be handled or any error.
so fundamentally my concern with anyhow and similar solutions is that we're turning our placeholders into concrete long term versions of the placeholder
For example, there is this
https://github.com/rtfeldman/roc/blob/9d53e1d748ab6ba3b057158415db6ffeb64fd721/cli/src/repl/gen.rs#L210
gen_and_eval should probably be two functions though
this change is an example of what I'm talking about
this is changing Result<BuiltFile, LoadingProblem<'a>> into Result<BuiltFile, MaybeError<LoadingProblem<'a>>>
but we already have a concept of a loading problem - that's what LoadingProblem does! :big_smile:
so this change wraps our existing error handling mechanism in a new one, and what the new one does is to take a TODO error message and present it to the user
what I think we should actually do here is incorporate this error into LoadingProblem, and present it to the user in a nice way
so this is what I mean when I say I think introducing anyhow is fundamentally not how we want to be handling these
in most cases the problem isn't "panic is worse than Result" but rather "we aren't handling the error gracefully, and we should handle it gracefully instead"
and the graceful error handling mechanism is something other than anyhow :big_smile:
to add, some errors are meant to be panics: they indicate internal issues, and for debugging purposes we want them to be panics, or at least present the same information. I don't see how anyhow could get hold of a file/line number in this context
yeah, actually I think we do want an ice! macro (short for Internal Compiler Error) - rustc uses this a bunch
Folkert de Vries said:
to add, some errors are meant to be panics: they indicate internal issues, and for debugging purposes we want them to be panics, or at least present the same information. I don't see how
anyhowcould get hold of a file/line number in this context
std::error::Error has traceback builtin.
yea that sounds good. I believe the elm compiler also has some panic catcher
it's a panic that prints a message like "This is a compiler bug - it should not have happened, so please report this error! Here's how to do that"
Richard Feldman said:
it's a panic that prints a message like "This is a compiler bug - it should not have happened, so please report this error! Here's how to do that"
I already tried https://crates.io/crates/human-panic and it doesn't work
oh I don't think we need a crate for that!
final consideration: dependencies slow down our builds.
we just define our own macro named ice! and all it does is to call panic! with some stock message around it like "Hey, you hit a compiler bug! We didn't think it was possible that this would happen, sorry about this...here's how to report it"
not really
@Locria Cyber why do you say that?
The bevy game engine compiles very fast by designing code for fast compilation
currently most of codegen happens in roc_cli(bin). That's probably a sign for macro/link time
be that as it may, it's a fact that adding dependencies makes the build slower :big_smile:
(it's linking time mostly, yes)
It's acceptable since it's boilerplate you have to write anyways?
but regardless, I don't think an ice! macro needs anything that's not in std to do what we want
Just calling panic won't show the reason up to causing the error
You only get the stack trace
what else would you want/need?
With anyhow you get like
Error: Cannot open file <line number>
Context: when trying to load module <line number>
Context: when processing repl <line number>
Context: ...
(I gotta go, as previously mentioned, but I'll catch up on this discussion tonight! :wave:)
hmm, so I just get that information from the stack trace as I investigate
but I'm very familiar with the codebase so maybe I'm biased
And you can intercept all this information and
main and show default panic (above message)a variable is declared twice here and herealso how to use another linker under nix
mold is fast
mold should be fast, but I did not get big speedups when I tried it a while ago. Maybe something changed though
mold is really fast
I hope this doesn't come off the wrong way but I strongly recommend getting more intimate with the project first before making sweeping changes on the basis of "obviously correct", many things have quite a lot of context behind the decisions. But really this is easily resolved by bringing things up in a conversation before spending time on code that may not end up getting merged. There's many builtins still to be implemented and that's generally a great way to learn how things work in the compiler, if you want some help adding a builtin feel free to shoot me a DM and we can set up a time. If you prefer to work solo, then any previous PR for a builtin should act as a good template. This also isn't to say your suggestion isn't a good idea, but it's sad to have to close work that someone spent time on due to a lack of communication before hand.
So I used to work professionally with Go where a lot of emphasis is placed on errors, error handling, and error wrapping/propagating. How our errors worked actually closely represented the examples @Locria Cyber mentioned with anyhow.
I would say that, for the most part, changing to something like that would not benefit Roc. Roc has what I would label as 3 main classes of errors Todo/unimplemented, internal compiler errors, and user errors.
Todo should all eventually be removed and don't matter for long term error tracking.
User errors should be cleanly displayed to the user and often times, in code, might not even be represented as an error. These errors often need to be custom with extra tracking information in order to make a pretty user error message. They should never need stack trace information or compiler internals. If we have to show a stack trace to a user, there is a bigger problem with our design. A number of user errors in roc are currently not represented correctly because it is more work to properly report an error than just panic and come back to it later. It would probably be a good idea to distinguish needing better reporting and actual compiler internal errors.
Compiler internal errors should never be hit, but exist because people write bugs and make wrong assumptions. This is the case where an panic, unwrap, or expect happens to be wrong but never should be. If the error is expected to be possible, this should be upgraded to a user error. A lot of cases where we use expect and explain what happened most likely should be switched to user errors, but it depends on context.
Compiler internal errors are where something like anyhow might be useful. I think it would be most useful to new compiler developers. The main advantage it gives is an annotated stack trace instead of a raw stack trace. In my experience, naming in wrapped errors tends to be not much more useful than a stack trace, but for new users, it can jumpstart their understanding as opposed to feeling lost in a sea of functions that have a good chance to not even have documentation.
There's many builtins still to be implemented and that's generally a great way to learn how things work in the compiler, if you want some help adding a builtin feel free to shoot me a DM and we can set up a time.
this is a great suggestion and I highly recommend pairing with Lucas in general! :smiley:
Roc has what I would label as 3 main classes of errors Todo/unimplemented, internal compiler errors, and user errors.
I totally agree with this, and with the description of how we should handle the 3 categories of errors - love it! :100:
Compiler internal errors are where something like
anyhowmight be useful. I think it would be most useful to new compiler developers. The main advantage it gives is an annotated stack trace instead of a raw stack trace. In my experience, naming in wrapped errors tends to be not much more useful than a stack trace, but for new users, it can jumpstart their understanding as opposed to feeling lost in a sea of functions that have a good chance to not even have documentation.
yeah, although this is where I'm a bit torn, because that benefit is not free; there's some amount of runtime overhead compared to panic! (a small amount of overhead in isolation, but multiplied by a lot of call sites) and it also makes the types more involved in that it can make a function that should theoretically "always succeed" (unless there's a bug somewhere) return a Result
also it's one more dependency, which slows down builds incrementally
so while I definitely acknowledge that there's a benefit, I think it's pretty small (in the very specific case of an internal compiler bug, it gives more diagnostic context to compiler authors than a stack trace alone), and I'm not convinced that benefit outweighs the costs
I agree. Just noting where I see a valid use case.
As a side note, we might at some point want to do some sort of larger compilation speed optimization project. Like actually do deeper analysis and try to get larger changes done that could speed up compilation time. Things like: removing/swapping crates, minimizing used features, using 1 version of a crate everywhere, reorganizing our crates for better dependency chains, looking at other tips and tricks. Generally speaking compilation time slowly rises, and it can be hard to make a dent without a larger group effort.
FWIW, I've used anyhow before and it was nice. but I understand being hesitant to add new dependencies. @Brendan Hansknecht That error category break down would make for a pretty nice blog post :P
I really hope there is an easy way to track all the functions that just panic!
no_panic is quite useless since it only tells you one function that might panic! while not telling you where inside the function.
I think right now I want to
Actually, I'll be unavailable over a month.
if the build is fast enough, then we can add dependencies
Just a general note, we are dependency adverse, but we do add new dependencies. Generally, it is a measure of trade-offs and value. I wouldn't label this as a build time issue. Yes we want faster build times, but we won't restrict ourselves from ever adding dependencies due to build times rising.
Also as a starting point here, it might be a good idea to make 2 macros that wrap panic!:
panic! should be replaced by a proper user error message. This way, those errors will be track directly and we can slowly work to replace all of these cases. It will make it much easier for new users to know locations where it would be a great contribution to switch to roc_reporting or similar.This would enable us to encode some of the domain specific knowledge around when we do or don't want a panic!. Overall, it should make it significantly easier to make future changes without changing the wrong type of panic!.
yeah I like ice! for the former, like rustc uses, and maybe we could do roc_todo! for the latter?
If we wanted to get really fancy, we could have roc_todo! take a u64 as its first argument, which would be an issue number - so the expectation would be that you'd open an issue for each new usage of the macro, which would make it easier for others to discover later when looking through the issue tracker
once we'd converted everything over, we could also have a CI step that verifies we're not calling raw panic! or todo! - or even unwrap or expect (so we'd have to explicitly use unwrap_or_else with unreachable! if we're really sure, which personally I'd be ok with)
As long as we are fine with them having the same issue number, that sounds fine. For example, the linker would likely use it for most of it's current panics that just want better error reporting. Probably would just want one issue for that.
Also, not sure I would call it "todo". Since I think it should be explicitly for things we want to generate nice user errors for and not actual todos.
Richard Feldman said:
once we'd converted everything over, we could also have a CI step that verifies we're not calling raw
panic!ortodo!- or evenunwraporexpect(so we'd have to explicitly useunwrap_or_elsewithunreachable!if we're really sure, which personally I'd be ok with)
The current state-of-the-art is no_panic crate, which will error out at link time. So you have to compile the whole project before knowing that your program will panic without further information.
We would need to use static analysis for faster feedback, which calls for a new tool, probably with help from rust-analyzer.
Last updated: Jun 16 2026 at 16:19 UTC