In contrast, unless you manually add context to the error (or use a library that does something like this for you, overriding the default ? behavior), you won't get any information about where an error occurred at all.
Sure, with exceptions, you don't know statically where an exception might happen. But at runtime, you do get the exact information. So, if the error is hard to reproduce, you still have information about where exactly it occurred in those rare occasions where it happened.
OK, so, if I write the canonical modern C++ Hello World, execute it against an environment where the "standard output" doesn't exist, where does this stack trace get recorded? Maybe it depends on the compiler and standard library implementation somehow?
My impression is that in reality C++ just ignores the problem and carries on, so actually there was no stack trace, no logging, it just didn't work and too bad. Unsurprisingly people tasked with making things work prefer a language which doesn't do that.
If you're executing against a POSIX-compatible environment, then stdin, stdout, and stderr are expected to exist and be configured properly if you want them to work[1].
If you're executing against some other environment, like webassembly or an embedded system, then you'll already (hopefully) be using some logging and error handling approach that sends output to the correct place. Doesn't matter if you're using C, C++, .NET, Rust, Zig, etc.
For example, webassembly is an environment without stdio streams. It's your responsibility to make sure there is a proper way to record output, even if it's just a compatibility layer that goes to console.log.
[1]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/s...
In the specific case of "Hello, World" it's more embarrassing. The Rust Hello World does indeed experience and report errors if there are any, the canonical C just ignores them, as does the C++.
Can you give an example for each of those?
I don't think it's a bug if, like in the C example, you don't handle the return value of the function you are calling. The strace shows that the function returned an error, but the code doesn't check it. Not a language flaw.
In fact, in most of the languages that "don't have the bug", the runtime is automagically capturing the issue and aborting the program. Like an exception. Rust just "doesn't have the bug" because the compiler forces you to handle the error. All the .NET languages do the same thing at runtime and force you to handle the I/O error... with an exception handler.
Unfortunately, your talking points just seem like more Rust fanaticism trying to discredit any other language. This happens in every single discussion about any language other than Rust, especially C/C++. I'm not going to engage any further.
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.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&editio...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.
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.