Rust was more or less specifically designed to appeal to people like us. Throw a rope to a drowning person and they'll grab onto it hard.
Rust was more or less specifically designed to appeal to people like us. Throw a rope to a drowning person and they'll grab onto it hard.
So Rust is not a solution.
But it will make those codebases a lot easier to deal with because the Rust compiler enforces correctness in a myriad of ways that other languages don't. I would much, much rather deal with a legacy Rust codebase than a legacy C++ one.
Legacy codebases are smelly not because of a lack of tools for enforcing correctness. They're smelly because a) programming is hard, and b) a boatload of things are more important than correctness, in the real world.
> I would much, much rather deal with a legacy Rust codebase than a legacy C++
Obviously, because right now legacy Rust codebases are only 5 years old, while legacy C++ codebases are 30+ years old.
But in 20 years it will make no difference. The Rust of 2031 that will need to accommodate 30 years of legacy backwards compatibility will be no prettier than C++ today.
There are? Like what?
> But in 20 years it will make no difference. The Rust of 2031 that will need to accommodate 30 years of legacy backwards compatibility will be no prettier than C++ today.
I don't really believe this. There are 20 year old Java codebases, and yes they can be a mess and hell to work with, but they're not nearl as bad as C++ ones. And Rust is stricter than Java.
Exceptions, for example.
> There are 20 year old Java codebases, and yes they can be a mess and hell to work with, but they're not nearl as bad as C++ ones.
That's because Java doesn't attempt to solve difficult problems.
Exceptions are better than magical return values to indicate errors, that's true, but Rust Results achieve the same thing without the downsides of exceptions.
> That's because Java doesn't attempt to solve difficult problems.
Java isn't my favourite language, but that's just silly. GraalVM will do as a counterexample.
Sometimes they are, and sometimes they are not.
> They introduce hidden control flow paths that developers forget about and fail to handle correctly.
The problem that exceptions solve involve control flow paths that aren't supposed to be handled. Exceptions are not for handling recoverable errors, they are for graceful aborts in a complex, layered and modular program. (E.g., any multi-threaded server, for example.)
For control flow paths that are considered irrecoverable (ie. "this can never happen" branches), Rust has panic!(), which defaults to unwinding the stack, calling Drop implementations (destructors) along the way.
panic!() unwinding only kills the thread it occurs in and Rust's thread-related APIs are designed around preventing data that's been left in an inconsistent state from being observable in other threads without explicitly acknowledging that you're dealing with something like a mutex that's set its "poisoned" flag.
For control flow paths that are considered recoverable, Result<T, E> is basically a way to get checked exceptions which work naturally with higher order functions and have a more concise "call the defined conversion to the specified error return type if necessary, and re-throw" syntax.
If you implement the From/Into interface to define how to convert the error type you received into the error type you're returning, the ? operator will do an "unwrap the Ok value or convert and do an early return of the Err value" in a single character.
A lot of people use the thiserror crate to define their custom error types, which has helpers to makeimplementing the From/Into interface trivial... possibly as trivial as annotating an enum variant with #[from], depending on what you want out of it.
Also, this is from 2005 and chose a "considered harmful" title, but this article makes some good points:
Rust code accumulates some cruft over time, but Rust's type system and safety guarantees put a floor on how crappy the code can be. The first Rust code I ever wrote is still part of my project five years later; it's less than perfect, but it's free of undefined behavior now just as it was then. The data structure parsers I wrote four years ago look a bit ugly to me now, but are free of exploitable security bugs, and always were. Etc.
What does that even mean in the absence of a formal standard and competing compiler implementations? (Hint: nothing.)
If you refuse to define anything then you automatically make the "undefinedness" problem go away. (But not the pain it causes.)
> Rust's type system and safety guarantees put a floor on how crappy the code can be
No. The floor on crappiness is defined by the problem domain. Powerful features allow for powerful takes on crappiness.
Unless you want Rust to stay a teaching language forever, you must necessarily introduce abusable features. (See Python for a real-time slow-motion elaboration of this train wreck if you don't believe me.)
It means that when the language designers are asked "what is the behavior of this code (that compiles and doesn't use 'unsafe')?" they never throw up their hands and say "it's undefined behaviour, you must not write that code" (as the C++ definition often says).
They may say "oops, we're not sure what it should do, we need to clarify the language definition and write some tests to ensure the compiler does that". (This happens in C++ too.)
> Powerful features allow for powerful takes on crappiness.
I don't know what this means.
> Unless you want Rust to stay a teaching language forever
A lot of companies big and small are using Rust in production so this is not a compelling premise.
In absence of a standard effectively every language construct is 'undefined behavior'.
Again, look at Python for a vivid example of this. (We were discussing just this is a neighboring thread: https://news.ycombinator.com/item?id=26826158)
I have zero reason to believe that Rust won't meet Python's fate.
The Rust people don't have a standard and don't understand why they need one; in fact, their lack of standards is somehow touted as a benefit. Apparently, people think that if there is no standard for "defined" and "undefined" behavior that everything is "defined" by default.
(They're absolutely wrong, of course; it's actually the opposite.)
For more see: https://blog.regehr.org/archives/213 and https://raphlinus.github.io/programming/rust/2018/08/17/unde... .
> In absence of a standard effectively every language construct is 'implementation defined behaviour'.
But the parent said:
> In absence of a standard effectively every language construct is 'undefined behavior'.
Undefined behaviour != implementation defined behaviour. Both of these things exist in C and are separate. You can rely on implementation defined behaviour giving some consistent result on a given, compiler and hardware. You cannot rely on the behaviour of your program if you hit undefined behaviour.
That's clear enough for programmers to rely on. It is nonsense to argue that this is equivalent to C's "the compiler may do anything it wants" just because the "Rust Book" is not called the "Rust Standard" or not "formal" enough.
Undefined behaviour is stuff that you compiler's optimizers have been promised can never occur, so they are allowed to transform your code based on that assumption.
Here's a post I made on Reddit in 2019 with a list of resources on what undefined behaviour is:
https://www.reddit.com/r/rust/comments/dpxswt/rust_2020_lets...
...and here's the link to an example of what invoking undefined behaviour can do in C++:
https://gcc.godbolt.org/z/0ubnbS
...and here's an explanation of why it does what it does:
https://kristerw.blogspot.com/2017/09/why-undefined-behavior...
(TL;DR: It injects a call to EraseAll because calling Do while it's still null is undefined behaviour, and Do is a static, so the optimizer determines that the only possible answer within the rules it was given is that code outside that compilation unit will have called NeverCalled to set Do = EraseAll before invoking main().)