Readit News logoReadit News
dvratil · 7 months ago
The one thing that sold me on Rust (going from C++) was that there is a single way errors are propagated: the Result type. No need to bother with exceptions, functions returning bool, functions returning 0 on success, functions returning 0 on error, functions returning -1 on error, functions returning negative errno on error, functions taking optional pointer to bool to indicate error (optionally), functions taking reference to std::error_code to set an error (and having an overload with the same name that throws an exception on error if you forget to pass the std::error_code)...I understand there's 30 years of history, but it still is annoying, that even the standard library is not consistent (or striving for consistency).

Then you top it on with `?` shortcut and the functional interface of Result and suddenly error handling becomes fun and easy to deal with, rather than just "return false" with a "TODO: figure out error handling".

jeroenhd · 7 months ago
The result type does make for some great API design, but SerenityOS shows that this same paradigm also works fine in C++. That includes something similar to the ? operator, though it's closer to a raw function call.

SerenityOS is the first functional OS (as in "boots on actual hardware and has a GUI") I've seen that dares question the 1970s int main() using modern C++ constructs instead, and the API is simply a lot better.

I can imagine someone writing a better standard library for C++ that works a whole lot like Rust's standard library does. Begone with the archaic integer types, make use of the power your language offers!

If we're comparing C++ and Rust, I think the ease of use of enum classes/structs is probably a bigger difference. You can get pretty close, but Rust avoids a lot of boilerplate that makes them quite usable, especially when combined with the match keyword.

I think c++, the language, is ready for the modern world. However, c++, the community, seems to be struck at least 20 years in the past.

jchw · 7 months ago
Google has been doing a very similar, but definitely somewhat uglier, thing with StatusOr<...> and Status (as seen in absl and protobuf) for quite some time.

A long time ago, there was talk about a similar concept for C++ based on exception objects in a more "standard" way that could feasibly be added to the standard library, the expected<T> class. And... in C++23, std::expected does exist[1], and you don't need to use exception objects or anything awkward like that, it can work with arbitrary error types just like Result. Unfortunately, it's so horrifically late to the party that I'm not sure if C++23 will make it to critical adoption quickly enough for any major C++ library to actually adopt it, unless C++ has another massive resurgence like it did after C++11. That said, if you're writing C++ code and you want a "standard" mechanism like the Result type, it's probably the closest thing there will ever be.

[1]: https://en.cppreference.com/w/cpp/utility/expected

jll29 · 7 months ago
> I think c++, the language, is ready for the modern world. However, c++, the community, seems to be struck at least 20 years in the past.

Good point. A language that gets updated by adding a lot of features is DIVERGING from a community that has mostly people that still use a lot of the C baggage in C++, and only a few folks that use a lot of template abstraction at the other end of the spectrum.

Since in larger systems, you will want to re-use a lot of code via open source libraries, one is inevitably stuck in not just one past, but several versions of older C++, depending on when the code to be re-used was written, what C++ standard was stable enough then, and whether or not the author adopted what part of it.

Not to speak of paradigm choice to be made (object oriented versus functional versus generic programmic w/ templates).

It's easier to have, like Rust offers it, a single way of doing things properly. (But what I miss in Rust is a single streamlined standard library - organized class library - like Java has had it from early days on, it instead feels like "a pile of crates").

moomin · 7 months ago
I’ve seen it argued that, in practice, there’s two C++ communities. One is fundamentally OK with constantly upgrading their code (those with enterprise refactoring tools are obviously in this camp, but it’s more a matter of attitude than technology) and those that aren’t. C++ is fundamentally caught between those two.
d_tr · 7 months ago
C++ carries so much on its back and this makes its evolution over the past decade even more impressive.
Rucadi · 7 months ago
I created a library "cpp-match" that tries to bring the "?" operator into C++, however it uses a gnu-specific feature (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), I did support msvc falling-back to using exceptions for the short-circuit mechanism.

However it seems like C++ wants to only provide this kind of pattern via monadic operations.

zozbot234 · 7 months ago
> The one thing that sold me on Rust (going from C++) was that there is a single way errors are propagated: the Result type. No need to bother with exceptions

This isn't really true since Rust has panics. It would be nice to have out-of-the-box support for a "no panics" subset of Rust, which would also make it easier to properly support linear (no auto-drop) types.

kelnos · 7 months ago
I wish more people (and crate authors) would treat panic!() as it really should be treated: only for absolutely unrecoverable errors that indicate that some sort of state is corrupted and that continuing wouldn't be safe from a data- or program-integrity perspective.

Even then, though, I do see a need to catch panics in some situations: if I'm writing some sort of API or web service, and there's some inconsistency in a particular request (even if it's because of a bug I've written), I probably really would prefer only that request to abort, not for the entire process to be torn down, terminating any other in-flight requests that might be just fine.

But otherwise, you really should just not be catching panics at all.

bionhoward · 7 months ago
This is already a thing, I do this right now. You configure the linter to forbid panics, unwraps, and even arithmetic side effects at compile time.

You can configure your lints in your workspace-level Cargo.toml (the folder of crates)

“””

[workspace.lints.clippy]

pedantic = { level = "warn", priority = -1 }

# arithmetic_side_effects = "deny"

unwrap_used = "deny"

expect_used = "deny"

panic = "deny"

“””

then in your crate Cargo.toml “””

[lints]

workspace = true

“””

Then you can’t even compile the code without proper error handling. Combine that with thiserror or anyhow with the backtrace feature and you can yeet errors with “?” operators or match on em, map_err, map_or_else, ignore them, etc

[1] https://rust-lang.github.io/rust-clippy/master/index.html#un...

codedokode · 7 months ago
It's pretty difficult to have no panics, because many functions allocate memory and what are they supposed to do when there is no memory left? Also many functions use addition and what is one supposed to do in case of overflow?
arijun · 7 months ago
`panic` isn’t really an error that you have to (or can) handle, it’s for unrecoverable errors. Sort of like C++ assertions.

Also there is the no_panic crate, which uses macros to require the compiler to prove that a given function cannot panic.

alexeldeib · 7 months ago
that's kind of a thing with https://docs.rs/no-panic/latest/no_panic/ or no std and custom panic handlers.

not sure what the latest is in the space, if I recall there are some subtleties

johnisgood · 7 months ago
I do not want a library to panic though, I want to handle the error myself.
dvt · 7 months ago
Maybe contrarian, but imo the `Result` type, while kind of nice, still suffers from plenty of annoyances, including sometimes not working with the (manpages-approved) `dyn Error`, sometimes having to `into()` weird library errors that don't propagate properly, or worse: `map_err()` them; I mean, at this point, the `anyhow` crate is basically mandatory from an ergonomics standpoint in every Rust project I start. Also, `?` doesn't work in closures, etc.

So, while this is an improvement over C++ (and that is not saying much at all), it's still implemented in a pretty clumsy way.

singingboyo · 7 months ago
There's some space for improvement, but really... not a lot? Result is a pretty basic type, sure, but needing to choose a dependency to get a nicer abstraction is not generally considered a problem for Rust. The stdlib is not really batteries included.

Doing error handling properly is hard, but it's a lot harder when error types lose information (integer/bool returns) or you can't really tell what errors you might get (exceptions, except for checked exceptions which have their own issues).

Sometimes error handling comes down to "tell the user", where all that info is not ideal. It's too verbose, and that's when you need anyhow.

In other cases where you need details, anyhow is terrible. Instead you want something like thiserror, or just roll your own error type. Then you keep a lot more information, which might allow for better handling. (HttpError or IoError - try a different server? ParseError - maybe a different parse format? etc.)

So I'm not sure it's that Result is clumsy, so much that there are a lot of ways to handle errors. So you have to pick a library to match your use case. That seems acceptable to me?

FWIW, errors not propagating via `?` is entirely a problem on the error type being propagated to. And `?` in closures does work, occasionally with some type annotating required.

ackfoobar · 7 months ago
> the `anyhow` crate is basically mandatory from an ergonomics standpoint in every Rust project I start

If you use `anyhow`, then all you know is that the function may `Err`, but you do not know how - this is no better than calling a function that may `throw` any kind of `Throwable`. Not saying it's bad, it is just not that much different from the error handling in Kotlin or C#.

maplant · 7 months ago
? definitely works in closures, but it often takes a little finagling to get working, like specifying the return type of the closure or setting the return type of a collect to a Result<Vec<_>>
skrtskrt · 7 months ago
A couple of those annoyances are just library developers being too lazy to give informative error types which is far from a Rust-specific problem
mdf · 7 months ago
Generally, I agree the situation with errors is much better in Rust in the ways you describe. But, there are also panics which you can catch_unwind[1], set_hook[2] for, define a #[panic_handler][3] for, etc.

[1] https://doc.rust-lang.org/std/panic/fn.catch_unwind.html

[2] https://doc.rust-lang.org/std/panic/fn.set_hook.html

[3] https://doc.rust-lang.org/nomicon/panic-handler.html

ekidd · 7 months ago
Yeah, in anything but heavily multi-threaded servers, it's usually best to immediately crash on a panic. Panics don't mean "a normal error occurred", they mean, "This program is cursed and our fundamental assumptions are wrong." So it's normal for a unit test harness to catch panics. And you may occasionally catch them and kill an entire client connection, sort of the way Erlang handles major failures. But most programs should just exit immediately.
fpoling · 7 months ago
Result type still requires quite a few lines of boilerplate if one needs to add custom data to it. And as a replacement of exceptions with automatic stack trace attachment it is relatively poor.

In any case I will take Rust Result over C++ mess at any time especially given that we have two C++, one with exception support and one without making code incompatible between two.

jandrewrogers · 7 months ago
FWIW, stack traces are part of C++ now and you can construct custom error types that automagically attach them if desired. Result types largely already exist in recent C++ editions if you want them.

I use completely custom error handling stacks in C++ and they are quite slick these days, thanks to improvements in the language.

kccqzy · 7 months ago
The Result type isn't really enough for fun and easy error handling. I usually also need to reach for libraries like anyhow https://docs.rs/anyhow/latest/anyhow/. Otherwise, you still need to think about the different error types returned by different libraries.

Back at Google, it was truly an error handling nirvana because they had StatusOr which makes sure that the error type is just Status, a standardized company-wide type that stills allows significant custom errors that map to standardized error categories.

jasonjmcghee · 7 months ago
unfortunately it's not so simple. that's the convention. depending on the library you're using it might be a special type of Error, or special type of Result, something needs to be transformed, `?` might not work in that case (unless you transform/map it), etc.

I like rust, but its not as clean in practice, as you describe

ryandv · 7 months ago
There are patterns to address it such as creating your own Result type alias with the error type parameter (E) fixed to an error type you own:

    type Result<T> = result::Result<T, MyError>;

    #[derive(Debug)]
    enum MyError {
        IOError(String)
        // ...
    }
Your owned (i.e. not third-party) Error type is a sum type of error types that might be thrown by other libraries, with a newtype wrapper (`IOError`) on top.

Then implement the `From` trait to map errors from third-party libraries to your own custom Error space:

    impl From<io::Error> for MyError {
        fn from(e: io::Error) -> MyError {
            MyError::IOError(e.to_string())
        }
    }
Now you can convert any result into a single type that you control by transforming the errors:

    return sender
        .write_all(msg.as_bytes())
        .map_err(|e| e.into());
There is a little boilerplate and mapping between error spaces that is required but I don't find it that onerous.

Deleted Comment

Deleted Comment

Cloudef · 7 months ago
You can use anyhow, but yeah zig generally does errors better IMO
koakuma-chan · 7 months ago
You can use anyhow::Result, and the ? will work for any Error.
loeg · 7 months ago
I work in a new-ish C++ codebase (mid-2021 origin) that uses a Result-like type everywhere (folly::Expected, but you get std::expected in C++23). We have a C pre-processor macro instead of `?` (yes, it's a little less ergonomic, but it's usable). It makes it relatively nice to work in.

That said, I'd prefer to be working in Rust. The C++ code we call into can just raise exceptions anywhere implicitly; there are a hell of a lot of things you can accidentally do wrong without warning; class/method syntax is excessively verbose, etc.

stodor89 · 7 months ago
Failure is not an option, it's a Result<T,E>
0x1ceb00da · 7 months ago
Proper error handling is the biggest problem in a vast majority of programs and rust makes that straightforward by providing a framework that works really well. I hate the `?` shortcut though. It's used horribly in many rust programs that I've seen because the programmers just use it as a half assed replacement for exceptions. Another gripe I have is that most library authors don't document what errors are returned in what situations and you're left making guesses or navigating through the library code to figure this out.
ryandrake · 7 months ago
Error handling and propagation is one of those things I found the most irritating and struggled[1] with the most as I learned Rust, and to be honest, I'm still not sure I understand or like Rust's way. Decades of C++ and Python has strongly biased me towards the try/except pattern.

1: https://news.ycombinator.com/item?id=41543183

zaphar · 7 months ago
Counterpoint: Decades of C++/Python/Java/... has strongly biased me against the try/except pattern.

It's obviously subjective in many ways. However, what I dislike the most is that try/except hides the error path from me when I'm reading code. Decades of trying to figure out why that stacktrace is happening in production suddenly has given me a strong dislike for that path being hidden from me when I'm writing my code.

skrtskrt · 7 months ago
there are answers in the thread you linked that show how easy and clean the error handling can be.

it can look just like a more-efficient `except` clauses with all the safety, clarity, and convenience that enums provide.

Here's an example:

* Implementing an error type with enums: https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/main/... * Which derives from a more general error type with even more helpful enums: https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/main/... * then some straightforward handling of the error: https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/main/...

cmrdporcupine · 7 months ago
abseil's "StatusOr" is roughly like Rust's Result type, and is what is used inside Google's C++ codebases (where exceptions are mostly forbidden)

https://github.com/abseil/abseil-cpp/blob/master/absl/status...

dabinat · 7 months ago
I wish Option and Result weren’t exclusive. Sometimes a method can return an error, no result or a valid result. Some crates return an error for “no result”, which feels wrong to me. My solution is to wrap Result<Option>, but it still feels clunky.

I could of course create my own type for this, but then it won’t work with the ? operator.

atoav · 7 months ago
I think Result<Option> is the way to go. It describes precisely that: was it Ok? if yes, was there a value?

I could imagine situations where an empty return value would constitute an Error, but in 99% of cases returning None would be better.

Result<Option> may feel clunky, but if I can give one recommendation when it comes to Rust, is that you should not value your own code-aesthetical feelings too much as it will lead to a lot of pain in many cases — work with the grain of the language not against it even if the result does not satisfy you. In this case I'd highly recommend just using Result<Option> and stop worrying about it.

You being able to compose/nest those base types and unwraping or matching them in different sections of your code is a strength not a weakness.

Arnavion · 7 months ago
Result<Option> is the correct way to represent this, and if you need further convincing, libstd uses it for the same reason: https://doc.rust-lang.org/stable/std/primitive.slice.html?se...
estebank · 7 months ago
For things like this I find that ? still works well enough, but I tend to write code like

    match x(y) {
        Ok(None) => "not found".into(),
        Ok(Some(x)) => x,
        Err(e) => handle_error(e),
    }
Because of pattern matching, I often also have one arm for specific errors to handle them specifically in the same way as the ok branches above.

dicytea · 7 months ago
> I could of course create my own type for this, but then it won’t work with the ? operator.

This is what the Try[^1] trait is aiming to solve, but it's not stabilized yet.

[^1]: https://rust-lang.github.io/rfcs/3058-try-trait-v2.html

vjerancrnjak · 7 months ago
This sounds valid. Lookup in a db can be something or nothing or error.

Just need a function that allows lifting option to result.

0x457 · 7 months ago
Well, I think returning "not found" when action performed was an "update X" and X doesn't exist. Result<Option> is totally normal where it makes sense, tho.
divan · 7 months ago
Convention-wise Go is even better. On the one hand, there is zero magic in error handling ("Errors are values" and interface type 'error' is nothing special), on the other hand it's kind of a convention (slightly enforced by linters) that functions that return errors use this type and it's the last return parameter.

Nothing prevents people from doing their own way (error int codes, bool handling, Result types, etc, panic), but it's just an easiest way that handles well 99% of the error handling cases, so it sticks and gives a nice feeling of predictability of error handling patterns in Go codebases.

ttfkam · 7 months ago
It's also highly dependent upon the team's skill and diligence. You can easily ignore errors and skip error handling in Go with predictably hilarious results.

In Rust, you can't just skip error handling. You have to proactively do something generally unwise (and highly visible!) like call .unwrap() or you have to actually handle the error condition.

Go still relies on goodwill and a good night's sleep. The Rust compiler will guard against laziness and sleep deprivation, because ultimately programming languages are about people, not the computers.

flohofwoe · 7 months ago
IMHO the ugly thing about Result and Option (and a couple of other Rust features) is that they are stdlib types, basic functionality like this should be language syntax (this is also my main critique of 'modern C++').

And those 'special' stdlib types wouldn't be half as useful without supporting language syntax, so why not go the full way and just implement everything in the language?

choeger · 7 months ago
Uh, nope. Your language needs to be able to define these types. So they belong into the stdlib because they are useful, not because they are special.

You might add syntactic sugar on top, but you don't want these kinds of things in your fundamental language definition.

scotty79 · 7 months ago
> Then you top it on with `?` shortcut

I really wish java used `?` as a shorthand to declare and propagate checked exceptions of called function.

fooker · 7 months ago
One of the strengths of C++ is the ability to build features like this as a library, and not hardcode it into the language design.

Unless you specifically want the ‘?’ operator, you can get pretty close to this with some clever use of templates and operator overloading.

If universal function call syntax becomes standardized, this will look even more functional and elegant.

steveklabnik · 7 months ago
Rust also started with it as a library, as try!, before ?. There were reasons why it was worth making syntax, after years of experience with it as a macro.
chickenzzzzu · 7 months ago
why not just read the function you are calling to determine the way it expects you to handle errors?

after all, if a library exposes too many functions to you, it isn't a good library.

what good is it for me to have a result type if i have to call 27 functions with 27 different result types just to rotate a cube?

bena · 7 months ago
Ok, I'm at like 0 knowledge on the Rust side, so bear that in mind. Also, to note that I'm genuinely curious about this answer.

Why can't I return an integer on error? What's preventing me from writing Rust like C++?

tczMUFlmoNk · 7 months ago
You can write a Rust function that returns `i32` where a negative value indicates an error case. Nothing in Rust prevents you from doing that. But Rust does have facilities that may offer a nicer way of solving your underlying problem.

For instance, a common example of the "integer on error" pattern in other languages is `array.index_of(element)`, returning a non-negative index if found or a negative value if not found. In Rust, the return type of `Iterator::position` is instead `Option<usize>`. You can't accidentally forget to check whether it's present. You could still write your own `index_of(&self, element: &T) -> isize /* negative if not found */` if that's your preference.

https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho...

bonzini · 7 months ago
Nothing prevents you, you just get uglier code and more possibility of confusion.
tomp · 7 months ago
Did you ever actually program in Rust?

In my experience, a lot of the code is dedicated to "correctly transforming between different Result / Error types".

Much more verbose than exceptions, despite most of the time pretending they're just exceptions (i.e. the `?` operator).

Why not just implement exceptions instead?

(TBH I fully expect this comment to be downvoted, then Rust to implement exceptions in 10 years... Something similar happened when I suggested generics in Go.)

nomel · 7 months ago
I've only worked in exceptions, so I can't really comprehend the book-keeping required without them. To me it's a separation of concerns: the happy path only involves happy code. The "side channel" for the unhappy path is an exception, with an exception handler at a layer of the abstraction where it's meaningful, yet happy, code. By "happy" I mean code that's simply the direct practical work that's trying to accomplished something, so doesn't need to worry about when things go terribly wrong.

Being blind to the alternative, and mostly authoring lower level libraries, what's the benefit of not having exceptions? I understand how they're completely inappropriate for an OS, a realtime system, etc, but what about the rest? Or is that the problem: once you have the concept, you've polluted everything?

throw10920 · 7 months ago
The result type is obviously insufficient for writing nontrivial programs, because nontrivial programs fail in nontrivial ways that need exceptional control flow. The result type does not work because you have to choose between immediate callers handling failures (they don't always have the context to do so because they're not aware of the context of callers higher up on the call stack) or between propagating all of your error values all the way up the stack to the error handling point and making your program fantastically brittle and insanely hard to refactor.
ttfkam · 7 months ago
The Result type works for an awful lot of people. Be careful with absolute statements like "does not work." When it works for many others, they might just assume it's a skill issue.
nextaccountic · 7 months ago
You can inspect error values in Rust, handle some errors, and bubble up others, with an ordinary match statement.

Exactly like try catch

steveklabnik · 7 months ago
> The result type is obviously insufficient for writing nontrivial programs

Counterpoint: there are many non-trivial programs written in Rust, and they use Result for error handling.

90s_dev · 7 months ago
I like so much about Rust.

But I hear compiling is too slow.

Is it a serious problem in practice?

Seattle3503 · 7 months ago
Absolutely, the compile times are the biggest drawback IMO. Everywhere I've been that built large systems in Rust eventually ends up spending a good amount of dev time trying to get CI/CD pipeline times to something sane.

Besides developer productivity it can be an issue when you need a critical fix to go out quickly and your pipelines take 60+ minutes.

juliangmp · 7 months ago
I can't speak for a bigger rust project, but my experience with C++ (mostly with cmake) is so awful that I don't think it can get any worse.

Like with any bigger C++ project there's like 3 build tools, two different packaging systems and likely one or even multiple code generators.

conradludgate · 7 months ago
It is slow, and yes it is a problem, but given that typical Rust code generally needs fewer full compiles to get working tests (with more time spent active in the editor, with an incremental compiler like Rust Analyzer) it usually balances out.

Cargo also has good caching out of the box. While cargo is not the best build system, it's an easy to use good system, so you generally get good compile times for development when you edit just one file. This is along made heavy use of with docker workflows like cargo-chef.

throwaway76455 · 7 months ago
Compile times are the reason why I'm sticking with C++, especially with the recent progress on modules. I want people with weaker computers to be able to build and contribute to the software I write, and Rust is not the language for that.
cmrdporcupine · 7 months ago
I worked in the chromium C++ source tree for years and compiling there was orders of magnitude slower than any Rust source tree I've worked in so far.

Granted, there aren't any Rust projects that large yet, but I feel like compilation speeds are something that can be worked around with tooling (distributed build farms, etc.). C++'s lack of safety and a proclivity for "use after free" errors is harder to fix.

mynameisash · 7 months ago
It depends on where you're coming from. For me, Rust has replaced a lot of Python code and a lot of C# code, so yes, the Rust compilation is slow by comparison. However, it really hasn't adversely affected (AFAICT) my/our iteration speed on projects, and there are aspects of Rust that have significantly sped things up (eg, compilation failures help detect bugs before they make it into code that we're testing/running).

Is it a serious problem? I'd say 'no', but YMMV.

ttfkam · 7 months ago
Yes, Rust compiling is slow. Then again, I wouldn't say that C++ is exactly speedy in that area either. Nor Java. None of those are even in the same zip code to Go's compile speed.

So if you're cool with C++ or Java compile times, Rust will generally be fine. If you're coming from Go, Rust compiles will fell positively glacial.

kelnos · 7 months ago
Compilation is indeed slow, and I do find it frustrating sometimes, but all the other benefits Rust brings more than make up for it in my book.
zozbot234 · 7 months ago
People who say "Rust compiling is so slow" have never experienced what building large projects was like in the mid-1990s or so. It's totally fine. Besides, there's also https://xkcd.com/303/
tubs · 7 months ago
And panics?
epage · 7 months ago
Those are generally used as asserts, not control flow / error handling.
hoppp · 7 months ago
Its true but using unwrap is a bit boring , I mean...boring is good but its also boring.
craftkiller · 7 months ago
You shouldn't be using unwrap.
choeger · 7 months ago
All this has been known in the PL design community for decades if not half a century by now.

Two things are incredibly frustrating when it comes to safety in software engineering:

1. The arrogance that "practitioners" have against "theorists" (everyone with a PhD in programming languages)

2. The slowness of the adoption of well-tested and thoroughly researched language concepts (think of Haskell type classes, aka, Rust traits)

I like that Rust can pick good concepts and design coherent language from them without inventing its own "pragmatic" solution that breaks horribly in some use cases that some "practitioners" deem "too theoretical."

bigbuppo · 7 months ago
It's weird that this sort of debate around C++ often leaves out the fact that many of the problems with C++ were known before C++ even existed. Outside of a few specific buckets, there is no reason to use C++ for any new projects, and really, there never has been. If you can't stomach Rust for some reason, and I'm one of those people, there are plenty of choices out there without all the pitfalls of C++ or C.
ivmaykov · 7 months ago
> If you can't stomach Rust for some reason, and I'm one of those people, there are plenty of choices out there without all the pitfalls of C++ or C.

Unless you are doing embedded programming ...

Ygg2 · 7 months ago
> I like that Rust can pick good concepts and design coherent language from them without inventing its own "pragmatic" solution that breaks horribly in some use cases that some "practitioners" deem "too theoretical."

I've thought Rust picked some pretty nifty middle ground. On one side, it's not mindfucking unsafe like C. It picked to remove a set of problems like memory safety. On the other side, Rust didn't go for the highest of theoretical grounds. It's not guaranteeing much outside of it, and it also relies a bit on human help (unsafe blocks).

eptcyka · 7 months ago
As per the article, Rust has benefits beyond the ones afforded by the borrow checker.
sanderjd · 7 months ago
Yep, this article is a good example of one way that c++ is bad, but it's not really a great example of rust being particularly good; many other languages support this well. I'm very glad Rust is one of those languages though!
groos · 7 months ago
I had the same thought - what Matt's examples required was strong typing and that has existed for very long time outside of the C family world.
blub · 7 months ago
If the practitioners haven’t adopted what you’re offering for 50+ years, that thing can’t be good.

Rust is also struggling with its “too theoretical” concepts by the way. The attempts of the community to gaslight the practitioners that the concepts are in fact easy to learn and straightforward are only enjoying mild success, if I may call it that.

db48x · 7 months ago
I disagree. The advertising and hype pushing people to use C++ is insane. There are hundreds of magazines that exist solely to absorb the advertising budget of Microsoft (and to a lesser extent Intel). Hundreds of conferences every year. You could be writing code in ML at your startup with no complaints and demonstrable success but as soon as your company gets big enough to send your CEO to an industry conference you’ll be switching to C++. The in–flight magazine will extol the benefits of MSVC, speakers like Matt Godbolt will preach Correct by Construction in C++, etc, etc. By the time he gets back he’s been brainwashed into thinking that C++ is the next best thing.
fsloth · 7 months ago
”If the practitioners haven’t adopted what you’re offering for 50+ years, that thing can’t be good.”

I don’t think what features are popular in C++ is good indication of anything. The language is good only due to the insane amounts of investment to the ecosystem, not because of the language features due to design.

For an industrial language inventory of ”nice features to have” F# and C# are mostly my personal gold standard.

”Too theoretical” is IMO not the correct lens to use. I would propose as a better lens a) which patterns you often use b) how to implement them in language design itself.

A case in point is the gang-of-four book. It mostly gives names to things in C++ that are language features in better languages.

ordu · 7 months ago
> If the practitioners haven’t adopted what you’re offering for 50+ years, that thing can’t be good.

I wouldn't trust opinion of practitioners. Not after they have chosen javascript and, God forbid, PHP. Practitioners choose not what is inherently good, but what is popular. It is a very practical choice that brings a lot of benefits, so it is just practitioners being practitioners. It can be good or bad, I don't care now, it doesn't matter for my argument. The issue that a good thing can be overlooked by practitioners for decades, because there is nothing popular giving them this thing.

quietbritishjim · 7 months ago
I always enjoy reading articles like this. But the truth is, having written several 100s of KLOC in C++ (i.e., not an enormous amount but certainly my fair share) I just almost never have problems with this sort accidental conversion in practice. Perhaps it might trip me up occasionally, but will be noticed by literally just running the code once. Yes, that is an extra hurdle to trip over and resolve but that is trivial compared to the alternative of creating and using wrapper types - regardless of whether I'm using Rust or C++. And the cost of visual noise of wrapper types, already higher just at the writing stage, then continues to be a cost every time you read the code. It's just not worth it for the very minor benefit it brings.

(Named parameters would definitely be great, though. I use little structs of parameters where I think that's useful, and set their members one line at a time.)

I know that this is an extremist view, but: I feel the same way about Rust's borrow checker. I just very rarely have problems with memory errors in C++ code bases with a little thought applied to lifetimes and use of smart pointers. Certainly, lifetime bugs are massively overshadowed by logic and algorithmic bugs. Why would I want to totally reshape the way that I code in order to fix one of the least significant problems I encounter? I actually wish there were a variant of Rust with all its nice clean improvements over C++ except for lifetime annotations and the borrow checker.

Perhaps this is a symptom of the code I tend to write: code that has a lot of tricky mathematical algorithms in it, rather than just "plumbing" data between different sources. But actually I doubt it, so I'm surprised this isn't a more common view.

lmm · 7 months ago
> I just almost never have problems with this sort accidental conversion in practice.

95% of C++ programmers claim this, but C++ programs continue to be full of bugs, and they're usually exactly this kind of dumb bug.

> will be noticed by literally just running the code once.

Maybe. If what you're doing is "tricky mathematical algorithms", how would you even know if you were making these mistakes and not noticing them?

> the cost of visual noise of wrapper types, already higher just at the writing stage, then continues to be a cost every time you read the code. It's just not worth it for the very minor benefit it brings.

I find wrapper types are not a cost but a benefit for readability. They make it so much easier to see what's going on. Often you can read a function's prototype and immediately know what it does.

> Certainly, lifetime bugs are massively overshadowed by logic and algorithmic bugs.

Everyone claims this, but the best available research shows exactly the opposite, at least when it comes to security bugs (which in most domains - perhaps not yours - are vastly more costly): the most common bugs are still the really dumb ones, null pointer dereferences, array out of bounds, and double frees.

blub · 7 months ago
It’s the sociology of software development.

The guild of software developers has no real standards, no certification, no proven practices outside <book> and <what $company is doing> while continuing to depend on the whims of project managers, POs and so-caled technical leaders and others which can’t tell quality code from their own ass.

There’s usually no money in writing high-quality software and almost everything in a software development project conspires against quality. Languages like Rust are a desperate attempt at fixing that with technology.

I guess it works, in a way, but these kind of blog posts just show us how inept most programmers are and why the Rust band-aid was needed in the first place.

davemp · 7 months ago
My current project is a huge C++ physics sim written over 15+ years. The most common and difficult to diagnose bug I’ve found is unit conversation mistakes. We likely wouldn’t even find them if we didn’t have concrete data to compare against.
viraptor · 7 months ago
> but will be noticed by literally just running the code once.

I assure you that's not the case. Maybe you didn't make that mistake, but if you did I'm sure it sometimes went unnoticed. I've found those issues in my code and in other projects. Sometimes they even temporarily don't matter, because someone did a func(CONST, 0) instead of func(0, CONST) and it turns out CONST is 0 - however the next person gets a crash because they change 0 to 1. A lot of similar issues come from the last line effect https://medium.com/@Code_Analysis/the-last-line-effect-7b1cb... and can last for years without being noticed.

kevincox · 7 months ago
I had a friend who noticed that people were often mixing up the arguments to some std constructor (I think it was string with a char and other integer argument getting swapped.) He searched across Google's codebase and found many (I don't remember the exact number) cases of this, many that he could confirm to be real bugs. He spent months fixing them and I think eventually got some check added to prevent this in the future.

So this definitely isn't some theoretical problem. I wouldn't even be surprised if you had made this mistake just hadn't noticed.

humanrebar · 7 months ago
I understand this concern, but at the same time it's not hard to write clang-query statements for the ones you care about. Sometimes it is even a regex! And it's not too expensive to upstream universally relevant checks to clang-tidy.

The main problem is that too many C++ engineers don't do any of that. They have some sort of learned helplessness when it comes to tooling. Rust for now seems to have core engineers in place that will do this sort of on behalf of everyone else. Language design aside, if it can find a way to sustain that kind of solid engineering, it will be hard to argue against.

devnullbrain · 7 months ago
>code that has a lot of tricky mathematical algorithms in it, rather than just "plumbing" data between different sources

Your hierarchy is backwards. Borrowing for algorithmic code is easy, it's for writing libraries that can be used by others where it's hard. Rust lets you - makes you - encode in in the API in a way C++ can't yet express.

> I just very rarely have problems with memory errors in C++ code bases with a little thought applied to lifetimes and use of smart pointers

If these are sparing you C++ bugs but causing you to struggle with the borrow checker, it's because you're writing code that depends on constraints that you can't force other contributors (or future you) to stick to. For example, objects are thread-unsafe by default. You can use expensive locks, or you can pray that nobody uses it wrong, but you can't design it so it can only be used correctly and efficiently.

blub · 7 months ago
This article presents something I’d expect competent C++ programmers with a few years of experience to know.

Unfortunately, many programmers are not competent. And the typical modern company will do anything in its power to outsource to often the lowest bidder, mismanage projects and generally reduce quality to the minimum acceptable to make money. That’s why one needs tools like Rust, Java, TypeScript, etc.

Unfortunately, Rust is still too hard for the average programmer, but at least it will hit them over the hands with a stick when they do something stupid. Another funny thing about Rust is that it’s attracting the functional programming/metaprogramming astronauts in droves, which is at odds with it being the people’s programming language.

I still don’t think it’s a valuable skill. Before it was lack of jobs and projects, which is still a problem. Now it’s the concern that it’s as fun as <activity>, except in a straitjacket.

pjmlp · 7 months ago
Just like it happened with Scala and Kotlin, it is the Haskell they are allowed to use at work, and that is how you get such libraries.
Tempest1981 · 7 months ago
This is my observation too:

> Rust is still too hard for the average programmer

What's the best way to "onboard" average programmers to Rust? Is it possible to avoid the more complex syntax and still be productive?

Or does Rust require us to fire and replace our "average c++ programmers" with super-smart Rust programmers?

spacechild1 · 7 months ago
> Why would I want to totally reshape the way that I code in order to fix one of the least significant problems I encounter?

I feel the same. Rust certainly has many nice properties and features, but the borrow checker is a huge turn-off for me.

socalgal2 · 7 months ago
I already hated C++ (having written 100s of thousands of lines of it in games and at FAANG)

I'd be curious to know what if any true fixes are coming down the line.

This talk: "To Int or to Uint, This is the Question - Alex Dathskovsky - CppCon 2024" https://www.youtube.com/watch?v=pnaZ0x9Mmm0

Seems to make it clear C++ is just broken. That said, and I wish he'd covered this, he didn't mention if the flags he brings up would warn/fix these issues.

I don't want a C++ where I have to remember 1000 rules and if I get one wrong my code is exploitable. I want a C++ where I just can't break the rules except when I explicitly opt into breaking them.

speaking of which, according to another C++ talk, something like 60% of rust crates are dependent on unsafe rust. The point isn't to diss rust. The point is that a safe C++ with opt into unsafe could be similar to rust's opt into unsafe

aw1621107 · 7 months ago
> speaking of which, according to another C++ talk, something like 60% of rust crates are dependent on unsafe rust.

It's probably not the source of the stats you had in mind since it's discussing something slightly different, but the Rust Foundation built a tool called Painter [0] for this kind of analysis. According to that [1]:

> As of May 2024, there are about 145,000 crates; of which, approximately 127,000 contain significant code. Of those 127,000 crates, 24,362 make use of the unsafe keyword, which is 19.11% of all crates. And 34.35% make a direct function call into another crate that uses the unsafe keyword. Nearly 20% of all crates have at least one instance of the unsafe keyword, a non-trivial number.

> Most of these Unsafe Rust uses are calls into existing third-party non-Rust language code or libraries, such as C or C++.

To be honest, I would have expected that 60% number to be higher if it were counting unsafe anywhere due to unsafe in the stdlib for vocabulary types and for (presumably) common operations like iterator chains. There's also a whole other argument that the hardware is unsafe so all Rust code will depend on unsafe somewhere or another to run on actual hardware, but that's probably getting a bit into the weeds.

[0]: https://github.com/rustfoundation/painter

[1]: https://rustfoundation.org/media/unsafe-rust-in-the-wild-not...

eptcyka · 7 months ago
Memory allocation is unsafe, so any container in the stdlib end up using unsafe at some point. This does not mean that safe Rust is useless without unsafe - the utility of Rust is that it allows one to create sage interfaces around an unsafe construct.
Ygg2 · 7 months ago
> there's also a whole other argument that the hardware is unsafe so all Rust code will depend on unsafe somewhere or another to run on actual hardware, but that's probably getting a bit into the weeds.

That's not going into the weeds, by that logic (Nirvana fallacy) no language is safe, you're going to die, so why bother about anything? Just lie down and wait for bugs to eat you.

eslaught · 7 months ago
There has been talk of new language frontends for C++:

Cpp2 (Herb Sutter's brainchild): https://hsutter.github.io/cppfront/

Carbon (from Google): https://github.com/carbon-language/carbon-lang

In principle those could enable a safe subset by default, which would (except when explicitly opted-out) provide similar safety guarantees to Rust, at least at the language level. It's still up to the community to design safe APIs around those features, even if the languages exist. Rust has a massive advantage here that the community built the ecosystem with safety in mind from day 1, so it's not just the language that's safe, but the APIs of various libraries are often designed in an abuse-resistant way. C++ is too much of a zoo to ever do that in a coherent way. And even if you wanted to, the "safe" variants are still in their infancy, so the foundations aren't there yet to build upon.

I don't know what chance Cpp2 or Carbon have, but I think you need something as radical as one of these options to ever stand a chance of meaningfully making C++ safer. Whether they'll take off (and before Rust eats the world) is anyone's guess.

aw1621107 · 7 months ago
I don't think Carbon is a C++ frontend like cppfront. My impression is that cppfront supports C++ interop by transpiling/compiling to C++, but Carbon compiles straight to LLVM and supports C++ interop through built-in language mechanisms.
thrwyexecbrain · 7 months ago
The C++ code I write these days is actually pretty similar to Rust: everything is explicit, lots of strong types, very simple and clear lifetimes (arenas, pools), non-owning handles instead of pointers. The only difference in practice is that the build systems are different and that the Rust compiler is more helpful (both in catching bugs and reporting errors). Neither a huge deal if you have a proper build and testing setup and when everybody on your team is pretty experienced.

By the way, using "atoi" in a code snippet in 2025 and complaining that it is "not ideal" is, well, not ideal.

mountainriver · 7 months ago
I still find it basically impossible to get started with a C++ project.

I tried again recently for a proxy I was writing thinking surely things have evolved at this point. Every single package manager couldn’t handle my very basic and very popular dependencies. I mean I tried every single one. This is completely insane to me.

Not to mention just figuring out how to build it after that which was a massive headache and an ongoing one.

Compared to Rust it’s just night and day.

Outside of embedded programming or some special use cases I have literally no idea why anyone would ever write C++. I’m convinced it’s a bunch of masochists

morsecodist · 7 months ago
Agreed. I have had almost the same experience. The package management and building alone makes Rust worth it for me.
runevault · 7 months ago
When I've dabbled in C++ recently it has felt like using CMake fetching github repos has been the least painful thing I've tried (dabbled in vcpkg and conan a bit), since most libraries are cmake projects.

I am no expert so take it with a grain of salt, but that was how it felt for me.

almostgotcaught · 7 months ago
> Every single package manager couldn’t handle my very basic and very popular dependencies

Well there's your problem - no serious project uses one.

> I’m convinced it’s a bunch of masochists

People use cpp because it's a mature language with mature tooling and an enormous number of mature libraries. Same exact reason anyone uses any language for serious work.

ValtteriL · 7 months ago
Felt the same pain with vcpkg. Ended up using OS packages and occasionally simply downloading a pure header based dependency.

With Nix, the package selection is great and repackaging is fairly straight forward.

kanbankaren · 7 months ago
The C++ code I wrote 20 years ago also had strong typing and clear lifetimes.

Modern C++ has reduced a lot of typing through type inference, but otherwise the language is still strongly typed and essentially the same.

pjmlp · 7 months ago
Unfortunely thanks to the "code C in C++ crowd", there is this urban myth that goodies like proper encapsulation, stronger types, RAII, were not even available in pre-C++98, aka C++ARM.

Meanwhile it was one of the reasons after Turbo Pascal, my next favourite programming language became C++.

For me mastering C, after 1992, only became mattered because as professional, that is something that occasionally I have to delve into so better know your tools even if the grip itself has sharp corners, otherwise everytime the option was constrained to either C or C++, I always pick C++.

simonask · 7 months ago
The strong/weak distinction is a bit fuzzy, but reasonable people can have the opinion that C++ is, in fact, loosely/weakly typed. There are countless ways to bypass the type system, and there are implicit conversions everywhere.

It _is_ statically typed, though, so it falls in a weird category of loosely _and_ statically typed languages.

mathw · 7 months ago
Foundationally though C++ still allows a lot of implicit casts that can and will shoot you in the foot.

You CAN write nice modern code in C++, but the ability to force yourself and all your colleagues to do so in perpetuity isn't really there yet.

Although it might be in the future, which would be nice.

yodsanklai · 7 months ago
> The C++ code I write these days

Meaning you're in a context where you have control on the C++ code you get to write. In my company, lots of people get to update code without strict guidelines. As a result, the code is going to be complex. I'd rather have a simpler and more restrictive language and I'll always favor Rust projects to C++ ones.

bluGill · 7 months ago
That is easy to say today, but I guarantee in 30 year Rust will have rough edges too. People always want some new feature and eventually one comes in that cannot be accommodated nicely.

Of course it will probably not be as bad as C++, but still it will be complex and people will be looking for a simpler language.

andrepd · 7 months ago
Lack of pattern matching and move only types means you physically cannot code in C++ as you would in Rust, even ignoring all the memory safety stuff.
taylorallred · 7 months ago
Cool that you're using areas/pools for lifetimes. Are you also using custom data structures or stl (out of curiosity)?
thrwyexecbrain · 7 months ago
Nothing fancy, I found that one can do almost anything with std::vector, a good enough hash map and a simple hand-rolled intrusive list.
ModernMech · 7 months ago
What sold me on Rust is that I'm a very bad programmer and I make a lot of mistakes. Given C++, I can't help but hold things wrong and shoot myself in the foot. My media C++ coding session is me writing code, getting a segfault immediately, and then spending time chasing down the reason for that happening, rinse and repeat.

My median Rust coding session isn't much different, I also write code that doesn't work, but it's caught by the compiler. Now, most people call this "fighting with the borrow checker" but I call it "avoiding segfaults before they happen" because when I finally get through the compiler my code usually "just works". It's that magical property Haskell has, Rust also has it to a large extent.

So then what's different about Rust vs. C++? Well Rust actually provides me a path to get to a working program whereas C++ just leaves me with an error message and a treasure map.

What this means is that although I'm a bad programmer, Rust gives me the support I need to build quite large programs on my own. And that extends to the crate ecosystem as well, where they make it very easy to build and link third party libraries, whereas with C++ ld just tells you that it had a problem and you're left on your own to figure out exactly what.

jpc0 · 7 months ago
Using your media example since I have a decent amount of experience there. Did you just use off the shelf libraries, because effectively all the libraries are written in or expose a C api. So now you not only need to deal with Rust, you need to also deal with rust ffi.

There are some places I won’t be excited to use rust, and media heavy code is one of those places…

sophacles · 7 months ago
Given that the second paragraph starts with "my median rust..." i assume the "media C++" is actually a typo for "median C++".
kanbankaren · 7 months ago
> Given C++, I can't help but hold things wrong and shoot myself

Give an example. I have been programming in C/C++ for close to 30 years and the places where I worked had very strict guidelines on C++ usage. We could count the number of times we shot ourselves due to the language.

mb7733 · 7 months ago
Isn't that their point though? They don't have 30 years of C/C++ experience and a workplace with very strict guidelines. They are just trying to write some code, and they run into trouble on C++'s sharper edges.
bunderbunder · 7 months ago
This is actually the point where Rust starts to frustrate me a little bit.

Not because Rust is doing anything wrong here, but because the first well-known language to really get some of these things right also happens to be a fairly low-level systems language with manual memory management.

A lot of my colleagues seem to primarily be falling in love with Rust because it's doing a good job at some basic things that have been well-known among us "academic" functional programming nerds for decades, and that's good. It arguably made inroads where functional programming languages could not because it's really more of a procedural language, and that's also good. Procedural programming is a criminally underrated and misunderstood paradigm. (As much as I love FP, that level of standoffishness about mutation and state isn't any more pragmatic than OOP being so hype about late binding that every type must support it regardless of whether it makes sense in that case.)

But they're also thoroughly nerdsniped by the borrow checker. I get it, you have to get cozy with the borrow checker if you want to use Rust. But it seems like the moral opposite of sour grapes to me. The honest truth is that, for most the software we're writing, a garbage collected heap is fine. Better, even. Shared-nothing multithreading is fine. Better, even.

So now we're doing more and more things in Rust. Which I understand. But I keep wishing that I could also have a Rust-like language that just lets me have a garbage collector for the 95% of my work where the occasional 50ms pause during run-time just isn't a big enough problem to justify a 50% increase in development and maintenance effort. And then save Rust for the things that actually do need to be unmanaged. Which is maybe 5% of my actual work, even if I have to admit that it often feels like 95% of the fun.

Starlevel004 · 7 months ago
> Not because Rust is doing anything wrong here, but because the first well-known language to really get some of these things right also happens to be a fairly low-level systems language with manual memory management.

It also has half implementations of all the useful features (no distinct enum variant types, traits only half-exist) because you have to code to the second, hidden language that it actually compiles to.

whytevuhuni · 7 months ago
Yeah, I've hit the enum variant type issue myself (e.g. a function guaranteed to return one of the variants), but in practice it hasn't been that big of an issue. There's also std::mem::discriminant [1] which helps a bit.

What do you mean by traits only half-existing?

[1] https://doc.rust-lang.org/stable/std/mem/fn.discriminant.htm...

int_19h · 7 months ago
Well, Rust is specifically targeting the "C++ and thereabouts" bucket, so they need the borrow checker to make all those nifty abstractions zero-cost or nearly so.

If that's not the goal and you really are perfectly fine with GC, then the baseline wouldn't be C++ but rather e.g. C# and Java. At which point you'd look at F# and Kotlin. Or even C# itself, which has been acquiring more and more FP-like languages over the years.

legobmw99 · 7 months ago
Based on the rest of your comment I suspect you're already familiar, but a decent candidate for "Rust with a GC" is OCaml, the language the first Rust compiler was written in.
bunderbunder · 7 months ago
It's close. Perhaps there's an interesting conversation to be had about why OCaml hasn't taken over the world the way Rust has.

The toolchain might be a first candidate. Rust's toolchain feels so very modern, and OCaml's gives me flashbacks to late nights trying to get my homework done on the department's HP-UX server back in college.

klipilicica · 7 months ago
I suggest taking a look at Gleam. It's a fairly new language but it's gaining traction.

https://gleam.run/

pjmlp · 7 months ago
Personally I am more of an D, Delphi, Modula-3, Oberon kind of systems language kind of person, and currently I would say C# with Native AOT is good enough for me.

Eventually Java with Valhala + GraalVM, if it ever comes to be.

I would already be happy with Go, if it wasn't for their anti-intelectual attitude regarding programming language design.

jpc0 · 7 months ago
Amazing example of how easy it is to get sucked into the rust love. Really sincerely these are really annoying parts of C++.

The conversation function is more language issue. I don’t think there is a simple way of creating a rust equivalent version because C++ has implicit conversions. You could probably create a C++ style turbofish though, parse<uint32_t>([your string]) and have it throw or return std::expected. But you would need to implement that yourself, unless there is some stdlib version I don’t know of.

Don’t conflate language features with library features.

And -Wconversion might be useful for this but I haven’t personally tried it since what Matt is describing with explicit types is the accepted best practice.

ujkiolp · 7 months ago
meh, rust is still better cos it’s friendlier
jpc0 · 7 months ago
I don’t disagree. Rust learnt a ton from C++.

I have my gripes with rust, more it’s ecosystem and community that the core language though. I won’t ever say it’s a worse language than C++.