When do you panic?

I had some trouble debugging one issue, and I missed the stack trace I get in other languages. Now I'm thinking maybe for unexpected errors I should panic instead of returning an error, so I get this stack trace?

Or do you just make better logs and don't panic ever?

79257

You can add a backtrace to your errors, or have anyhow do it for you.

5 Likes

that's cool, reading that I just recalled that I'm also doing async, so I think it's even harder, panic wouldn't do it

I didn't need async, I followed there because of axum, maybe it's worth going out of the way to make things sync, but how do people deal with it?

With async you can't get a meaningful backtrace (that I know of) but you can do structured tracing to get a picture of how you got to a certain point.

3 Likes

Backtraces work perfectly fine in async code.

1 Like

I should have said meaningful backtraces: showing the nested calls across threads. Since async is stackless, can you elaborate? Do you mean they work to give you a partial picture? Or are you referring to async-backtrace?


There is an open issue for async external backtraces (in debuggers), but that's to be expected.

I'm trying to recall if there is any language that async debugging isn't annoying

1 Like

It's possible to have stackful async (OCaml does). But yeah, in other languages I've only used threads and tons of logging!

With async code, you get a stacktrace of the poll methods, which shows you exactly the backtrace you would expect. For example:

async fn foo() {
    panic!();
}

async fn bar() {
    foo().await;
}

#[tokio::main]
async fn main() {
    bar().await;
}

prints a backtrace with the relevant parts looking like this:

   5: playground::foo::{{closure}}
             at ./src/main.rs:2:5
   6: playground::bar::{{closure}}
             at ./src/main.rs:6:11
   7: playground::main::{{closure}}
             at ./src/main.rs:11:11

The fact that async tasks are sometimes moved from one thread to another is irrelevant and does not break the backtraces.

4 Likes

Sorry, I think I'm wrong about this. Alice is the expert here.

1 Like

From API design perspective, panics generally are for bugs. If the cause of the error is programmer writing the program incorrectly, then panic. Otherwise you should return a Result.

If you're just debugging a program locally then panic!("woop") is fine — equivalent of printf-debugging. Just don't forget to remove it before committing/publishing.

Another compromise could be to add debug_assert! to make functions fail loudly in dev, but still return Result in release mode. This could make sense for really rare/unexpected errors.

7 Likes

Obligatory link to Using unwrap() in Rust is Okay - Andrew Gallant's Blog

You make better logs so that you can panic more.

Assuming that you can investigate and ship fixes promptly -- which obviously you want for other reasons too -- you should let devs panic! for "I don't think that can happen". You want the increased developer velocity of not writing a bunch of irrelevant code, as well the less debt of a bunch of untested error-handling paths.

Then the dev who wrote the panic! gets the bug if the panic is actually hit, with priority related to the frequency and consistency at which it can be hit.

2 Likes

Generally, I will try to return a result over panicking in all cases. The only time I tend not to is when I literally don't know how I would proceed. The only case that comes to mind for me, in that, is when a Mutex is poisoned (which usually indicates that something has gone Seriously Wrong).

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.