3 ms·
I'll go into details about your particular question, but I first want to explain why it's missing the point. The difference in terms of logging between exceptio
by tsimionescu 2y ago
I'll go into details about your particular question, but I first want to explain why it's missing the point. The difference in terms of logging between exceptions and Rust error handling (or Haskell, or Go, or C) is unrelated to how you print out the log information. It's related to the fact that the exception object itself collects and carries the stack trace information, which the runtime populates if and when an exception happens, whereas in Rust it's up to the programmer (or some library) to manually collect this information and either print it or add it to a custom error object, at every call site. The fact that uncaught exceptions get printed to stdout is the tiniest little bonus, and irrelevant for most programs: you shouldn't have uncaught exceptions in the first place. The important thing is that whenever you catch an exception, you know for sure that you'll have some useful diagnostic information about where exactly it occurred, regardless of who wrote the code between here and there.
Now on to your specific question.
First of all, I explicitly called out C++ exceptions as not having this useful property. C++ exceptions don't collect a stack trace, and the C++ runtime simply exits with an error code if an exception is thrown without a handler.
Now, moving to any other language with exceptions. What happens by default if executing in an environment without stdout will depend on details of the runtime of that language for that environment.
But let's assume that the runtime is not written to handle this gracefully. Here's the entirety of the code you need to add to your exception-based program to handle a lack of stdout and still get stack traces, in pseudo-code:
int main() {
try {
return oldMain();
} catch (Exception e) {
with(File f = openFile("my-log.log")) {
f.write("Unhandled exception:");
e.printStackTrace(f);
}
}
}
Where oldMain() is the main() you'd write for the same program if you did have stdout.
- whytevuhuni 2y agoYou seem to be arguing more for stack traces than for exceptions? Rust can store backtraces in value objects as well [0]. It's opt-in (capturing a stack trace at the error value's creation may be expensive if that error is eventually handled), but with the anyhow crate you get a decent compromise: a stack trace is captured at the boundary of your program and libraries during the conversion, and then shown only if the error bubbles up to main(). And you get the bonus of storing both the stack trace, and relevant context where needed, e.g. to show values of parameters. Here's how that playground example above fails: Error: Second try Caused by: 0: Parsing 'forty-two' as number 1: invalid digit found in string Stack backtrace: 0: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from at ./.cargo/registry/src/index.crates.io-6f17d22bba15001f/anyhow-1.0.94/src/backtrace.rs:27:14 1: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual at ./.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:2009:27 2: playground::parse_number at ./src/main.rs:25:8 3: playground::parse_and_increment at ./src/main.rs:18:18 4: playground::main at ./src/main.rs:7:19 ... [0] https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=99dbbbc70875df5fdd44d90e2dbf7c69 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- tsimionescu 2y agoCool, I didn't know about RUST_BACKTRACE = 1. That addresses the core of my comment, yes. I will note that some runtimes (like Java or C#, I believe) don't compute the stack trace unless and until it is requested, which means that exceptions that are caught and handled without being logged shouldn't incur the performance cost - thus removing most of the reason you may want to have a way to disable this behavior. I did know about anyhow, that was exactly the library I was mentioning. But that requires manually adding context at all places where the error is passed.
- neonsunset 2y agoExceptions thrown in both Java and .NET eagerly compute the stack trace and the text associated with it. They are also very expensive compared to the happy path. Historically, manually thrown exceptions in OpenJDK have been cheaper than .NET's (although .NET 9 makes them twice as cheap) while NPE's in OpenJDK are much more expensive than regular Java exceptions or .NET's NREs In Java, you can disable stack traces altogether that massively reduces the cost (which is what e.g. Crafting Interpreters suggests - it's a good course but the author is both wrong and actively misleading about cost model of implementations covered in parts 1 and 2 because of this) but few codebases do this.