The C++ language committee _does not_ want to add more annotations to increase memory safety.
It's like being surprised that the UN Security Council keeps making decisions which favour Russia.
In fact, WG14 very clearly has acted against Dennis when he submitted papers that could have improved C's safer.
Maybe his fat pointers proposal was not good enough, but apparently is wasn't something worth improving upon either.
C authors indeed moved on, first with Alef (which granted had a few design issues), Limbo and finally Go, as C as being driven by WG14 was no longer their thing, C on Plan 9 isn't even C89 compliant.
So they clearly doesn't care so there's no point convincing them.
Besides, throwing an exception is a terrible way to do range checking. There's a huge number of projects out there banning exceptions that would benefit from safe interfaces.
I thought "banning exceptions" would mean fno-exceptions, turning the throw into an abort - that's pretty good for a systems programming language, no?
What other way would you propose?
Panicking is in no way different from say Undefined Behavior, with the exception that panicking tends to be "loud" and therefore fixed promptly.
> but bubble the error up and then exit the process anyway.
What is so bad about this? Its only a problem because all languages are not ergonomic. Even Rust. Here's how to do it properly in my mind.
What I am proposing is not Rust, but what I wish Rust would have been.
First, you have functions that cannot fail
``` fn not_failable() -> Result<u32, ()> { return 0; } ```
Notice that the function still returns Result<T,E> but the error is (). Now what happens if the function can fail?
``` fn failable(value) -> Result<u32, () | A | B | C | D> { if value == 0 { return Ok(0); } else if value == 1 { return Err(A()); }else if value == 2 { return Err(B()); }else if value == 3 { return Err(C()); }else if value == 4 { return Err(D()); } } ```
Notice that we don't have to specify a type for the errors, they are just the unions of all the error types that is possibly returned by the function. This union could be inferred by the type system to be ergonomic (meaning it can be omitted from the type signature for ergonomic purposes)
You might think that this is almost like exceptions. And you are right, but this is where exceptions got wrong, the user of this function.
When using this function, you are forced to handle all the possible error types (exceptions) returned by the function
``` fn use1() -> Result<u32, () | C | D> { match failable() { Ok(v) => {} Err(A) => {} Err(B) => {} e => return Err(e), } } ```
Notice 2 things:
1. You are FORCED to handle all the possible exceptions. 2. You can specify which exceptions you want to handle and what you throw back. The difference to try/catch here is just the syntax. 3. The function signature can be automatically be duduced by the fact that A and B are already handlded, and that this function can only throw C or D now.
Now you might complain about ergonomic, why can't things just blow up the moment anything bad happens? I propose a trait that will be automatically be implemented for all Result<T,E>
``` impl Unwrap for Result { fn unwrap(self) -> u32 { match self() { Ok(ok) => return ok, Err(err) => panic!("error"), } } } ```
Which means that you can simply do this,
``` fn fail_immediately() -> Result<u32, ()> { return failable().unwrap(); } ```
Or, if you want to bubble up the errors, you can use ?
``` fn fail_immediately() -> Result<u32, A | B | C | D> { return failable()?; } ```
My only opinion on the \n versus std::endl discussion is that people set overly aggressive linting rules that always flag endl even when flush is intended.
So yeah, such things exist, but what's important is what the compiler devs choose to do once such issues are found. The C++ compiler devs say "That's an unfortunate case that cannot be fixed." The Rust devs say "That's a bug, here's the issue link."
[1] https://doc.rust-lang.org/std/env/fn.set_var.html
[2] https://doc.rust-lang.org/edition-guide/rust-2024/newly-unsa...
It doesn’t hurt anything to acknowledge that safe Rust can be unsafe due to mistakes in abstractions while simultaneously acknowledging that Rust still has orders of magnitudes fewer memory safety issues than c/c++ even with these problems.
As for this specific bug, this kind of bug took a long time to fix for what it’s worth since it can take up to 3 years for a new edition to allow for fixing it. And “fixing” it doesn’t actually fix the unsoundness in existing code - it just changes the responsibility of who’s supposed to validate the usage is safe. It basically shifts the “blame” to the user for holding their tool wrong because code patterns that had been documented as being sound are now documented as being unsound and the user has to figure out how to make it safe once more.
I’m not trying to cast blame or aspersions - mistakes happen and that this was dealt with shows the strength of Rust’s ability to solve these problems vs c++ which is hopeless. But pretending like safe Rust exists purely in a vacuum devoid of interaction with the real world isn’t helpful I think.
I say all this with the utmost respect to your expertise and we basically agree on a lot of rust-related things. I just disagree slightly on the messaging here.
I suspect we do too!
> It doesn’t hurt anything to acknowledge that safe Rust can be unsafe due to mistakes in abstractions
I think that the core disagreement here is not that I think that it's harmful, it just seems incredibly banal to me. That is, like, of course it can! So bringing it up feels like an attempt at a "gotcha" that's not really a gotcha.
Modula-2, Ada, Object Pascal, Eiffel, Delphi,...
There's a big difference between "it'll be some number, not promising which one" and "the program loses definition and anything can break, often even retroactively".
On my laptop, sizeof(long) is 8; that's implementation defined. It could be different on my phone, or my desktop, or my work laptop.
Undefined means, roughly, "doing X is considered nonsensical, and the compiler does not have to do anything reasonable with code that does X." In "Design and Evolution of C++," Stroustrup says that undefined behavior applies to things that should be errors, but that for some reason the committee doesn't think the compiler will necessarily be able to catch. When he came up with new ideas for the language, he would often have to choose between making a convoluted rule that his compiler could reliably enforce, or a simple rule that his compiler couldn't always give a sensible error message for.
For instance, the original compilers relied on the system's linker. If the compiler could interact with the linker, it could perhaps detect violations of the One Definition Rule ( https://en.cppreference.com/w/cpp/language/definition ), but since the linker might have been written by a completely different company, and it's acceptable for different source files to be compiled by different compilers (and even be written in other languages -- https://en.cppreference.com/w/cpp/language/language_linkage ) and put together by the linker, and it's common for binary libraries to be sold without source, there's no guarantee that the compiler will ever have the information necessary to detect a violation of the One Definition Rule. So the committee says that violations create a nonsense program, which isn't required to behave in any particular way.
What? gccgo, TinyGo, and GopherJS.