Back

Matt Godbolt sold me on Rust by showing me C++

611 points13 dayscollabora.com
dvratil13 days 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".

jeroenhd13 days 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.

jchw13 days 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

CJefferson13 days ago

I had a look. In classic C++ style, if you use *x to get the ‘expected’ value, when it’s an error object (you forgot to check first and return the error), it’s undefined behaviour!

Messing up error handling isn’t hard to do, so putting undefined behaviour here feels very dangerous to me, but it is the C++ way.

+2
jchw13 days ago
dietr1ch12 days ago

`StatusOr<T>::operator` there is akin to `Result<T, _>::unwrap()`. On C++ unwrapping looks like dereferencing a pointer which is scary and likely UB already.

But as you learn to work with StatusOr you'll end up just using just ASSIGN_OR_RETURN everytime and dereferencing remains scary. I guess the complaint is that C++ won't guarantee that the execution will stop, but that's the C++ way after you drop all safety checks in `StatusOr::operator` to gain performance.

sagarm11 days ago

This is the idiomatic way in C++. I'm not even sure what your proposed alternative is -- as other commenters have noted, an exception or "panic" are not actual options.

Every pointer dereference, array access, and even integer truncation is UB in C++. This isn't rust.

A static analyzer can and does catch these errors and others internally. Typical usage of StatusOr is via macros like ASSIGN_OR_RETURN and RETURN_IF_ERROR; actually using the * operator would definitely draw my attention in code review.

loeg9 days ago

Very similar footgun on std::optional::operator*. The big C++ libraries do at least have (debug-only) assertions on misuse.

a_t4813 days ago

There’s a few backports around, not quite the same as having first class support, though.

jchw13 days ago

I believe the latest versions of GCC, Clang, MSVC and XCode/AppleClang all support std::expected, in C++23 mode.

loeg13 days ago

Facebook's Folly has a similar type: folly::Expected (dating to 2016).

+1
throwaway203712 days ago
jll2913 days 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").

pjmlp12 days ago

Just give Rust 36 years of field use, to see how it goes.

+2
timschmidt12 days ago
mgaunard12 days ago

A lot of people using C++ don't actually use any libraries. I've observed the opposite with Rust.

People choose C++ because it's a flexible language that lets you do whatever you want. Meanwhile Rust is a constrained and opinionated thing that only works if you do things "the right way".

+1
tialaramex12 days ago
moomin12 days 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.

AndrewStephens12 days ago

This is the truth. I interview a lot of C++ programmers and it amazes me how many have gone their whole careers barely touching C++11 let alone anything later. The extreme reach of C++ software (embedded, UIs, apps, high-speed networking, services, gaming) is both a blessing and a curse and I understand why the committee is hesitant to introduce breaking changes at the expense of slow progress on things like reflection.

d_tr13 days ago

C++ carries so much on its back and this makes its evolution over the past decade even more impressive.

pjmlp12 days ago

Yes, people keep forgeting C++ was made public with CFront 2.0 back in 1989, 36 years of backwards compatibility, to certain extent.

+2
bluGill12 days ago
Rucadi13 days 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.

tialaramex12 days ago

You can't really do Try (which is that operator's name in Rust) because C++ lacks a ControlFlow type which is how Try reflects the type's decision about whether to exit early.

You can imitate the beginner experience of the ? operator as magically handling trivial error cases by "just knowing" what should happen, but it's not the same thing as the current Try feature.

Barry Revzin has a proposal for some future C++ (lets say C++ 29) to introduce statement expressions, the syntax is very ugly even by C++ standards but it would semantically solve the problem you had.

zozbot23413 days 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.

kelnos13 days 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.

wyager13 days ago

> only for absolutely unrecoverable errors

Unfortunately even the Rust core language doesn't treat them this way.

I think it's arguably the single biggest design mistake in the Rust language. It prevents a ton of useful stuff like temporarily moving out of mutable references.

They've done a shockingly good job with the language overall, but this is definitely a wart.

monkeyelite12 days ago

> I probably really would prefer only that request to abort, not for the entire process to be torn down,

This is a sign you are writing an operating system instead of using one. Your web server should be handling requests from a pool of processes - so that you get real memory isolation and can crash when there is a problem.

+1
tsimionescu12 days ago
willtemperley12 days ago

Using a Rust lib from Swift on macOS I definitely want to catch panics - to access security scoped resources in Rust I need the Rust code to execute in process (I believe) but I’d also like it not to crash the entire app.

tcfhgj13 days ago

would you consider panics acceptable when you think it cannot panic in practice? e.g. unwraping/expecting a value for a key in a map when you inserted that value before and know it hasn't been removed?

you could have a panic though, if you wrongly make assumptions

+1
nextaccountic13 days ago
conradludgate13 days ago

Not the same person, but I first try and figure out an API that allows me to not panic in the first place.

Panics are a runtime memory safe way to encode an invariant, but I will generally prefer a compile time invariant if possible and not too cumbersome.

However, yes I will panic if I'm not already using unsafe and I can clearly prove the invariant I'm working with.

+2
pdimitar13 days ago
j-krieger12 days ago

Honestly, I don't think libraries should ever panic. Just return an UnspecifiedError with some sort of string. I work daily with rust, but I wish no_std and an arbitrary no_panic would have better support.

+2
burntsushi12 days ago
bionhoward12 days 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...

weegee10112 days ago

The issue with this in practice is that there are always cases where panics are absolutely the correct course of action. When program state is bad enough that you can't safely continue, you need to panic (and core dump in dev). Otherwise you are likely just creating an integrity minefield for you to debug later.

Not saying there aren't applications where using these lints couldn't be alright (web servers maybe), but at least in my experiences (mostly doing CLI, graphics, and embedded stuff) trying to keep the program alive leads to more problems than less.

0x45712 days ago

The comment you're replying to specifically wanted "no panics" version of rust.

It's totally normal practice for a library to have this as a standard.

aaronmdjones12 days ago

Indent by 4 spaces to get code blocks on HN.

    Like
    this
craftkiller12 days ago
steveklabnik12 days ago

You only need 2. https://news.ycombinator.com/formatdoc

> Text after a blank line that is indented by two or more spaces is reproduced verbatim. (This is intended for code.)

bionhoward12 days ago

  Thank
  you
J_Shelby_J12 days ago

But can deny the use of all operations that might panic like indexing an array?

bionhoward12 days ago

Yes, looks like you can, try indexing_slicing

https://rust-lang.github.io/rust-clippy/master/#indexing_sli...

DreadY2K10 days ago

There's a lint for indexing an array, but not for all maybe-panicking operations. For example, the `copy_from_slice` method on slices (https://doc.rust-lang.org/std/primitive.slice.html#method.co...) doesn't have a clippy lint for it, even though it will panic if given the wrong length.

codedokode13 days 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?

Arnavion13 days ago

>many functions allocate memory and what are they supposed to do when there is no memory left?

Return an AllocationError. Rust unfortunately picked the wrong default here for the sake of convenience, along with the default of assuming a global allocator. It's now trying to add in explicit allocators and allocation failure handling (A:Allocator type param) at the cost of splitting the ecosystem (all third-party code, including parts of libstd itself like std::io::Read::read_to_end, only work with A=GlobalAlloc).

Zig for example does it right by having explicit allocators from the start, plus good support for having the allocator outside the type (ArrayList vs ArrayListUnmanaged) so that multiple values within a composite type can all use the same allocator.

>Also many functions use addition and what is one supposed to do in case of overflow?

Return an error ( https://doc.rust-lang.org/stable/std/primitive.i64.html#meth... ) or a signal that overflow occurred ( https://doc.rust-lang.org/stable/std/primitive.i64.html#meth... ). Or use wrapping addition ( https://doc.rust-lang.org/stable/std/primitive.i64.html#meth... ) if that was intended.

Note that for the checked case, it is possible to have a newtype wrapper that impls std::ops::Add etc, so that you can continue using the compact `+` etc instead of the cumbersome `.checked_add(...)` etc. For the wrapping case libstd already has such a newtype: std::num::Wrapping.

Also, there is a clippy lint for disallowing `+` etc ( https://rust-lang.github.io/rust-clippy/master/index.html#ar... ), though I assume only the most masochistic people enable it. I actually tried to enable it once for some parsing code where I wanted to enforce checked arithmetic, but it pointlessly triggered on my Checked wrapper (as described in the previous paragraph) so I ended up disabling it.

+1
kllrnohj13 days ago
johnisgood13 days ago

> Zig for example does it right by having explicit allocators from the start

Odin has them, too, optionally (and usually).

+1
smj-edison13 days ago
+1
imtringued12 days ago
PhilipRoman13 days ago

>what are they supposed to do when there is no memory left

Well on Linux they are apparently supposed to return memory anyway and at some point in the future possibly SEGV your process when you happen to dereference some unrelated pointer.

+2
tialaramex13 days ago
pdimitar13 days ago

Don't know about your parent poster but I didn't take it 100% literally. Obviously if there's no memory left then you crash; the kernel would likely murder your program half a second later anyway.

But for arithmetics Rust has non-aborting bound checking API, if my memory serves.

And that's what I'm trying hard to do in my Rust code f.ex. don't frivolously use `unwrap` or `expect`, ever. And just generally try hard to never use an API that can crash. You can write a few error branches that might never get triggered. It's not the end of the world.

+2
wahern13 days ago
tialaramex13 days ago

Rust provides a default integer of each common size and signedness, for which overflow is prohibited [but this prohibition may not be enforced in release compiled binaries depending on your chosen settings for the compiler, in this case what happens is not promised but today it will wrap - it's wrong to write code which does this on purpose - see the wrapping types below if you want that - but it won't cause UB if you do it anyway]

Rust also provides Wrapping and Saturating wrapper types for these integers, which wrap (255 + 1 == 0) or saturate (255 + 1 == 255). Depending on your CPU either or both of these might just be "how the computer works anyway" and will accordingly be very fast. Neither of them is how humans normally think about arithmetic.

Furthermore, Rust also provides operations which do all of the above, as well as the more fundamental "with carry" type operations where you get two results from the operation and must write your algorithms accordingly, and explicitly fallible operations where if you would overflow your operation reports that it did not succeed.

nicce13 days ago

Additions are easy. By default they are wrapped, and you can make them explicit with checked_ methods.

Assuming that you are not using much recursion, you can eliminate most of the heap related memory panics by adding limited reservation checks for dynamic data, which is allocated based on user input/external data. You should also use statically sized types whennever possible. They are also faster.

+1
codedokode13 days ago
kevin_thibedeau12 days ago

> what are they supposed to do when there is no memory left?

You abandon the current activity and bubble up the error to a stage where that effort can be tossed out or retried sometime later. i.e. Use the same error handling approach you would have to use for any other unreliable operation like networking.

kllrnohj13 days ago

> Also many functions use addition and what is one supposed to do in case of overflow?

Honestly this is where you'd throw an exception. It's a shame Rust refuses to have them, they are absolutely perfect for things like this...

brooke2k12 days ago

I'm confused by this, because a panic is essentially an exception. They can be thrown and caught (although it's extremely discouraged to do so).

The only place where it would be different is if you explicitly set panics to abort instead of unwind, but that's not default behavior.

arijun13 days 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.

josephg13 days ago

You can handle panics. It’s for unrecoverable errors, but internally it does stack unwinding by default like exceptions in C++.

You see this whenever you use cargo test. If a single test panics, it doesn’t abort the whole program. The panic is “caught”. It still runs all the other tests and reports the failure.

swiftcoder12 days ago

> but internally it does stack unwinding by default

Although as a library vendor, you kind have to assume your library could be compiled into an app configured with panic=abort, in which case it will not do that

marcosdumay13 days ago

Well, kinda. It's more similar to RuntimeException in Java, in that there are times where you do actually want to catch and recover from them.

But on those places, you better know exactly what you are doing.

nicce13 days ago

I would say that Segmentation Fault is better comparison with C++ :-D

alexeldeib13 days 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

zozbot23413 days ago

That's a neat hack, but it would be a lot nicer to have explicit support as part of the language.

+2
kbolino13 days ago
nicce13 days ago

The problem is with false positives. Even if you clearly see that some function will never panic (but it uses some feature which may panic), compiler might not always see that. If compiler says that there are no panics, then there are no panics, but is it enough to add as part of the language if you need to mostly avoid using features that might panic?

johnisgood13 days ago

I do not want a library to panic though, I want to handle the error myself.

int_19h12 days ago

Let's say the library panics because there was an out-of-bounds array access on some internal (to that library) array due to a bug in their code. How will you handle this error yourself, and how is the library supposed to propagate it to you in the first place without unwinding?

johnisgood11 days ago

Ensure all bounds and invariants are checked, and return Result<T, E> or a custom error or something. As I said, I do not want a library to panic. It should be up to the user of the library. When I write libraries, I make sure that the users of the library are able to handle the errors themselves. Imagine using some library, but they use assert() or panic() instead of returning an error for you to handle, that would frustrate me.

dvt13 days 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.

singingboyo13 days 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.

josephg13 days ago

I agree with you, but it’s definitely inconvenient. Result also doesn’t capture a stack trace. I spent a long time tracking down bugs in some custom binary parsing code awhile ago because I had no idea which stack trace my Result::Err’s were coming from. I could have switched to another library - but I didn’t want to inflict extra dependencies on people using my crate.

As you say, it’s not “batteries included”. I think that’s a fine answer given rust is a systems language. But in application code I want batteries to be included. I don’t want to need to opt in to the right 3rd party library.

I think rust could learn a thing or two from Swift here. Swift’s equivalent is better thought through. Result is more part of the language, and less just bolted on:

https://docs.swift.org/swift-book/documentation/the-swift-pr...

ackfoobar13 days 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#.

Yoric13 days ago

Yeah, `anyhow` is basically Go error handling.

Better than C, sufficient in most cases if you're writing an app, to be avoided if you're writing a lib. There are alternatives such as `snafu` or `thiserror` that are better if you need to actually catch the error.

dwattttt12 days ago

I find myself working through a hierarchy of error handling maturity as a project matures.

Initial proof of concepts just get panics (usually with a message).

Then functions start to be fallible, by adding anyhow & considering all errors to still be fatal, but at least nicely report backtraces (or other things! context doesn't have to just be a message)

Then if a project is around long enough, swap anyhow to thiserror to express what failure modes a function has.

jbritton13 days ago

I know a ‘C’ code base that treats all socket errors the same and just retries for a limited time. However there are errors that make no sense to retry, like invalid socket or socket not connected. It is necessary to know what socket error occurred. I like how the Posix API defines an errno and documents the values. Of course this depends on accurate documentation.

XorNot13 days ago

This is an IDE/documentation problem in a lot of cases though. No one writes code badly intentionally, but we are time constrained - tracking down every type of error which can happen and what it means is time consuming and you're likely to get it wrong.

Whereas going with "I probably want to retry a few times" is guessing that most of your problems are the common case, but you're not entirely sure the platform you're on will emit non-commoncases with sane semantics.

efnx13 days ago

Yes. I prefer ‘snafu’ but there are a few, and you could always roll your own.

+1
smj-edison13 days ago
shepmaster13 days ago

Yeah, with SNAFU I try to encourage people going all-in on very fine-grained error types. I love it (unsurprisingly).

maplant13 days 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<_>>

ninkendo11 days ago

Mapping a Vec<T> to Result<U, E> and collecting them into a single Result<Vec<U>, E> made me feel like a ninja when I first learned it was supported. I’m a little worried it’s too confusing to read for others, but it works so well.

Combined with futures::try_join_all for async closures and you can use it to do a bunch of failable tasks in parallel too, it’s great.

skrtskrt13 days 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

mdf13 days 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

ekidd13 days 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.

fpoling13 days 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.

jandrewrogers13 days 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.

fpoling12 days ago

What I really like to see is stack traces annotated with values of selected local values. A few years ago I tried that in a C++ code base where exceptions were disabled using macros and something like error context passed by references. But the result was ugly and I realized that I had zero chances to adopt it.

With Rust Result and powerful macros it easier to implement.

kccqzy13 days 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.

jasonjmcghee13 days 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

ryandv13 days 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.
johnisgood13 days ago

I scratch my head when people try to justify Rust's implementation of X. It looks absolutely horrendous, IMO.

I would rather have what OCaml has: https://ocaml.org/docs/error-handling.

Cloudef12 days ago

You can use anyhow, but yeah zig generally does errors better IMO

ziml7712 days ago

Errors are where I find zig severely lacking. They can't carry context. Like if you're parsing a JSON file and it fails, you can know that it failed but not where it failed within the file. Their solution in the standard library for cases like this was to handle printing to stderr internally, but that is incredibly hacky.

+1
Cloudef11 days ago
koakuma-chan13 days ago

You can use anyhow::Result, and the ? will work for any Error.

loeg13 days 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.

stodor8912 days ago

Failure is not an option, it's a Result<T,E>

0x1ceb00da13 days 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.

ryandrake13 days 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

zaphar13 days 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.

sham112 days ago

There should be a way to have the function/method document what sort of stuff can go wrong, and what kinds of exceptions you can get out of it.

It could be some kind of an exception check thing, where you would either have to make sure that you handle the error locally somehow, or propagate it upwards. Sadly programming is not ready for such ideas yet.

---

I jest, but this is exactly what checked exceptions are for. And the irony of stuff like Rust's use of `Result<T, E>` and similarly ML-ey stuff is that in practice they end up with what are essentially just checked exceptions, except with the error type information being somewhere else.

Of course, people might argue that checked exceptions suck because they've seen the way Java has handled them, but like... that's Java. And I'm sorry, but Java isn't the definition of how checked exceptions can work. But because of Java having "tainted" the idea, it's not explored any further, because we instead just assume that it's bad by construction and then end up doing the same thing anyway, only slightly different.

Analemma_12 days ago

> There should be a way to have the function/method document what sort of stuff can go wrong, and what kinds of exceptions you can get out of it.

The key phrase you're looking for is "algebraic effect systems". Right now they're a pretty esoteric thing only really seen in PL research, but at one point so was most of the stuff we now take for granted in Rust. Maybe someday they'll make their way to mainstream languages in an ergonomic way.

SJMG12 days ago

I totally agree with you that Java checked exceptions suck. IME exceptions are far more ergonomic than Result.

Nim has a good take on exception tracking that's elegant, performant, and only on request (unlike Java's attempt).

+1
int_19h12 days ago
skrtskrt13 days 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/...

cmrdporcupine13 days 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...

dabinat13 days 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.

atoav13 days 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.

Arnavion13 days 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...

estebank12 days 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.
dicytea13 days 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

vjerancrnjak13 days 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.

0x45711 days 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.

divan11 days 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.

ttfkam11 days 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.

flohofwoe12 days 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?

choeger12 days 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.

scotty7912 days 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.

fooker12 days 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.

steveklabnik12 days 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.

chickenzzzzu12 days 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?

bena13 days 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++?

tczMUFlmoNk13 days 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...

bonzini13 days ago

Nothing prevents you, you just get uglier code and more possibility of confusion.

tomp12 days 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.)

nomel12 days 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?

tomp12 days ago

There's mostly drawback of having exceptions, I'm not really aware of any benefits of not having them.

People often complain about:

- performance impact, either speed (most languages) or binary size (C++); this, however, is mostly an implementation concern, and doesn't impact Rust at all, as exceptions can simply be syntax-level compiler sugar, and can be compiled exactly the same as Result type is currently (having said that, the optional stack trace is another potential issue, which would have a performance impact even in Rust)

- checked exceptions - this is particularly a concern in Java, which has a fairly poor type system (closed subtyping only) and no type inference, so declaring all unhandled exceptions is tedious

- non-checked exceptions - in this case, "every exception can happen anywhere" so it's ostensibly unsafe (well it's just how life is, and even Java has special unchecked exceptions such as OutOfMemory that can happen anywhere) - some people claim that "exceptions can't just randomly jump out of code" is a benefit of not having exceptions but usually those people sweep OutOfMemory and DivisionByZero under the rug (e.g. Rust, where they just "crash" the program)

Rust would obviously fit the checked exceptions path, as the Result implementatoin basically is "poor-man's checked exceptions". It only needs to flip the syntax sugar - propagate by default (and implicitly), "catch" and materialize using `?` - as well as making Error an open enum (such that you can add cases implicitly - see e.g. OCaml's exception type `exn` [1]) - and that's basically it!

[1] https://ocaml.org/manual/5.3/extensiblevariants.html

alpaca12812 days ago

In places where you only want to pass an error to the caller, Rust lets you just add "?" to the call that returns a Result/Option. And the Rust compiler will check that every potential error is handled.

I wouldn't say that it's the tedious part of the language.

throw1092011 days 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.

ttfkam11 days 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.

throw1092011 days ago

When I say "it doesn't work" I mean that it doesn't allow you to write good code, not "doesn't work" as in the sense that people don't like it. That latter one doesn't make any sense, as languages like PHP "work" for many tens (hundreds?) of thousands of people.

I'm well aware of the tendency of Rust programmers to write bad code, constrained by the language, and then be deluded into thinking that that's good code.

+1
ttfkam11 days ago
nextaccountic10 days ago

You can inspect error values in Rust, handle some errors, and bubble up others, with an ordinary match statement.

Exactly like try catch

steveklabnik11 days 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.

throw1092011 days ago

Pointing to programs written with design flaws caused by the flaw in the programming language does invalidate the claim that the flaw exists and negatively affects those programs.

You can write any program in COBOL. Most people would say that it's an insufficient language for doing so.

"Insufficient" here obviously does not mean that it's impossible to write non-trivial programs, just that they'll have bad code.

90s_dev13 days ago

I like so much about Rust.

But I hear compiling is too slow.

Is it a serious problem in practice?

Seattle350313 days 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.

nicoburns13 days ago

If you have the money to throw at it, you can get a long way optimising CI pipelines just by throwing faster hardware at it. The sort of server you could rent for ~$150/month might easily be ~5x faster than your typical Github Actions hosted runner.

hobofan12 days ago

Besides faster hardware, one of the main features (and drawbacks) you get with self-hosted runners is the option to break through build isolation, and have performant caches between builds.

With many other build systems I'd be hesitant to do that, but since Cargo is very good about what to rebuild for incremental builds, keeping the cache around is a huge speed boost.

Seattle350313 days ago

Yes, this is often the best "low-hanging fruit" option, but it can get expensive. It depends how you value your developer time.

lilyball13 days ago

Don't use a single monolithic crate. Break your project up into multiple crates. Not only does this help with compile time (the individual crate compiles can be parallelized), it also tends to help with API design as well.

Seattle350313 days ago

Every project I've worked on used a workspace with many crates. Generally that only gets you so far on large projects.

+1
mixmastamyk13 days ago
sethammons12 days ago

We have 60 minutes deploy pipelines and are in python. Just mentioning that since, in theory, we are not penalized for long compile times.

Fast ability to quickly test and get feedback is mana from the gods in software development. Organizations should keep it right below customer satisfaction and growth as a driving metric.

juliangmp13 days 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.

thawawaycold12 days ago

that does not answer at all OP's question.

juliangmp11 days ago

"It can't get any worse than C++" That's my response. So just use rust. In the long run you'll save time as well.

conradludgate13 days 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.

throwaway7645513 days 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.

ttfkam11 days ago

C++ compiles quickly? News to me.

cmrdporcupine13 days 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.

gpderetta13 days ago

Are there rust projects that are within orders of magnitude of Chromium?

+1
alpaca12812 days ago
mynameisash13 days 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.

ttfkam11 days 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.

kelnos13 days 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.

zozbot23413 days 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/

creata13 days ago

Or maybe they have experienced what it was like and they don't want to go back.

kelnos13 days ago

Not really relevant. The benchmark is how other language toolchains perform today, not what they failed to do 30 years ago. I don't think we'd find it acceptable to go back to mid-'90s build times in other languages, so why should we be ok with it with something like Rust?

tubs13 days ago

And panics?

epage12 days ago

Those are generally used as asserts, not control flow / error handling.

hoppp12 days ago

Its true but using unwrap is a bit boring , I mean...boring is good but its also boring.

craftkiller12 days ago

You shouldn't be using unwrap.

choeger12 days 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."

bigbuppo12 days 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.

ivmaykov12 days 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 ...

fsloth12 days ago

I think embedded is one of the specific buckets.

You target the compiler your client uses for their platform. There is very little choice there.

+1
int_19h12 days ago
Ygg212 days 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).

eptcyka12 days ago

As per the article, Rust has benefits beyond the ones afforded by the borrow checker.

Ygg212 days ago

Sure, but it is pragmatic in other ways as well :)

It takes ADT, but not function currying, and so on.

+10
andrepd12 days ago
+3
jeffparsons12 days ago
sanderjd12 days 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!

groos12 days 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.

blub12 days 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.

db48x12 days 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.

fsloth12 days 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.

senderista12 days ago

The GoF book uses Smalltalk for the examples as well as C++, and Smalltalk is about the most expressive imperative language you could ask for.

ordu12 days 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.

j_w12 days ago

But being popular can be very important for practitioners. How else are you going to hire other practitioners?

quietbritishjim12 days 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.

lmm12 days 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.

blub12 days 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.

tormeh12 days ago

Maybe. But I wouldn't diss better languages, linters, and other tool inprovements. These systematically increase quality at very low cost. It boggles my mind that the whole industry is not falling over itself to continuously embrace better tools and technology.

+1
benced12 days ago
eftychis12 days ago

This. The industry is a hot-pot of gut feelings/seat of my pants mixed with true engineering and mathematical rigor.

It is all hit or miss. Everyone claims they do high-quality, critical software in public, while in private, they claim the opposite, that they are fast and break things, and programming is an art, not math.

And then you have venture capital firms now pushing "vibe coding."

Software development is likely the highest variance engineering space, sometimes and in some companies, not even being engineering, but "vibes."

It is interesting how this is going to progress forward. Are we going to have a situation like the Quebec Bridge [https://colterreed.com/the-failed-bridge-that-inspired-a-sim...]. The Crowdstrike incident taking down the whole airspace proved that is not enough. Market hacks in "decentralized exchanges," the same. Not sure where we are heading.

I guess we are waiting for some catastrophe that will have some venture capital liable for the vibe coding, and then we will have world wide regulation pushed on us.

pjmlp11 days ago

Software developers no, but Software Engineering does, it is a professional title in many countries, where universities and engineers are only legally allowed to use such titles after being validated.

+1
rowanG07711 days ago
davemp12 days 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.

j16sdiz12 days ago

There are a few unit library in C++.

Type checking in compile time is do-able with templates, even better with constexpr.

The problem is, of course, each library have its own set of rules and they won't interop with each other.

+1
bluGill12 days ago
viraptor12 days 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.

kevincox12 days 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.

humanrebar12 days 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.

devnullbrain12 days 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.

blub12 days 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.

pjmlp11 days 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.

Tempest198111 days 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?

spacechild112 days 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.

socalgal213 days 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

aw162110712 days 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...

eptcyka12 days 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.

aw162110712 days ago

Right, I totally agree. I suppose I was kind of trying to express a bit of confusion at the 60% number due to the lack of specifics of what it encompasses (e.g., how far down the dependency chain is that number looking?), unlike the stats I quoted.

Ygg212 days 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.

aw162110712 days ago

Perhaps I got the quip wrong. I was basically trying to reference discussions I've seen elsewhere along the lines of "Rust is not actually memory-safe because it needs unsafe", sometimes followed by the argument you outlined. Those discussions can get a bit involved and I don't think this is a good time/place for them, so I more or less just wanted to reference it without spending much time/words on actually delving into it.

eslaught12 days 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.

aw162110712 days 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.

thrwyexecbrain13 days 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.

mountainriver13 days 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

morsecodist12 days ago

Agreed. I have had almost the same experience. The package management and building alone makes Rust worth it for me.

runevault13 days 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.

kylereeve12 days ago

Do you have CMake actually run `git clone`, or do you clone separately and point CMake at the `FIND_X` files?

runevault12 days ago

I was using fetch content or the like, there is a package that comes after a certain version of cmake where you can tell it this is a git repo and it handles all that for you. It has been a few months since I did this so I don't remember the details fully

ValtteriL12 days 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.

almostgotcaught13 days 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.

cratermoon13 days ago

How can you simultaneously call cpp a mature language with mature tooling and acknowledge that there's no working package manager used by any "serious" project?

+3
const_cast12 days ago
+1
tdiff12 days ago
guappa12 days ago

apt install xxxxx-dev

+2
almostgotcaught12 days ago
kanbankaren13 days 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.

pjmlp12 days 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++.

simonask12 days 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.

int_19h12 days ago

I think that explicit casts really ought to be discounted, since if you're writing one, you are simply getting what you have asked for. This would be like saying that e.g. Modula-2 is weakly typed because it has bitcast.

That aside, the only remaining footgun in C++ is the implicit numeric conversions. What else did you have in mind?

+1
simonask11 days ago
mathw10 days 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.

yodsanklai13 days 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.

bluGill13 days 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.

simonask12 days ago

Rust has rough edges today. The field of programming is still only a few decades old, and there's no doubt that something even shinier will come along, we just don't know yet what that looks like.

That's not a good reason to stick with inferior tools now, though.

+1
bluGill12 days ago
timbit4213 days ago

How many rough edges will C++ have in another 30 years?

+1
bluGill12 days ago
andrepd12 days 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.

taylorallred13 days ago

Cool that you're using areas/pools for lifetimes. Are you also using custom data structures or stl (out of curiosity)?

thrwyexecbrain12 days 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.

senderista12 days ago

std::vector is the only STL container that makes sense to use in practice.

ModernMech13 days 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.

jpc013 days 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…

sophacles13 days ago

Given that the second paragraph starts with "my median rust..." i assume the "media C++" is actually a typo for "median C++".

kanbankaren13 days 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.

mb773313 days 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.

kanbankaren13 days ago

> with very strict guidelines.

Low level languages always came with a set of usage guidelines. Either you make the language safe for anyone that they can't shoot themselves in the foot and end up sacrificing performance, or provide guidelines on how to use it while retaining the ability to extract maximum performance from the hardware.

C/C++ shouldn't be approached like programming in Javascript/Python/Perl.

+1
zaphar13 days ago
bunderbunder12 days 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.

Starlevel00412 days 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.

whytevuhuni12 days 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_19h12 days 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.

legobmw9912 days 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.

bunderbunder12 days 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.

sn912 days ago

Can I interest you in some Moonbit?

https://www.moonbitlang.com/blog/first-announce

+2
int_19h12 days ago
klipilicica11 days ago

I suggest taking a look at Gleam. It's a fairly new language but it's gaining traction.

https://gleam.run/

pjmlp11 days 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.

jpc013 days 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.

ujkiolp13 days ago

meh, rust is still better cos it’s friendlier

jpc013 days 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++.

noelnh13 days ago

Could you elaborate on those points, I'm genuinely curious? So far, I have found the Rust community to be immensely helpful, much more so than I experienced the C++ community. Granted, that's quite some time ago and might be at least partially caused by me asking fewer downright idiotic questions. But still, I'm interested in hearing about your experiences.

+1
jpc013 days ago
+1
bigstrat200313 days ago
BlueTemplar13 days ago

For where I am concerned, I don't want to have anything to do with the kind of developers that still think that it's acceptable to use Github, VS Code, or Discord in 2025 in a professional setting, much worse teach a new generation of developers to use them : that's like being a doctor and giving out cigarettes to children.

GardenLetter2713 days ago

It's a shame Rust doesn't have keyword arguments or named tuples to make handling some of these things easier without Args/Options structs boilerplate.

frankus13 days ago

I work all day in Swift (which makes you go out of your way to omit argument labels) and I'm surprised they aren't more common.

kevincox12 days ago

Yeah, this is one of the few things that I love about Swift. I think it gets it exactly right that keyword arguments should be the default and you can opt out in cases where the keyword is really unnecessary.

jsat13 days ago

Had the same thought... It's backwards that any language isn't using named parameters at this point.

Ygg213 days ago

Named parameters do come with a large footgun. Renaming your parameters is a breaking change.

Especially if you're coming from different langs.

_flux10 days ago

E.g. OCaml has the ability to separate argument name and variable name for named arguments: let foo ~argument:variable = variable + variable

In fact, I think OCaml has one of the best labeled argument system around there. The only downside is that it doesn't always interact well with currying, but perhaps languages without currying could just copy all the rest.

Just to elaborate a bit, in OCaml you can have functions like:

    let foo ~a ?b c =
      let b = match b with
        | None -> 42
        | Some x -> x
      in a + b + c
And you can then call this like foo ~a:1 ~b:42 55 or foo ~a:2 ?b:None 55. But then forwarding those optional parameters works like:

    let bar ~a ?b c =
      foo ~a ?b c
and the optional parameter b will be forwarded as an optional parameter.

Given Rust's historical relations with OCaml I'm slightly disappointed that it doesn't have the same labeled and optional argument system.

Spivak12 days ago

I guess but you're changing your user-visible API so it should be a breaking change. In languages that don't have this type/arity is all that matters and the name is just nice sugar for the implementor who doesn't have to bind them to useful names.

Even if you don't use keyword args your parameter names are still part of your API surface in Python because callers can directly name positional args. Only recently have you been able enforce unnamed positional only args as well as the opposite.

const_cast12 days ago

This only really applies to languages that don't check this at compile-time. I don't consider compile-time errors a foot gun. I mean, it should be impossible for that kind of bad code to ever get merged in most reasonable CI/CD processes.

+1
Ygg212 days ago
dcdgo12 days ago

Rust Tuples can be destructured into named variables, or Enums can be used as Monads which give a label to a tuple of variables. Rust Enums are real fun to use so I encourage you to dive in. https://doc.rust-lang.org/rust-by-example/custom_types/enum....

shpongled13 days ago

Yep, I would love anonymous record types, ala StandardML/OCaml

Gazoche12 days ago

Agreed, coming from Python it's one of the main things I miss in Rust. You can achieve something similar with the builder pattern or with structs + the Default trait, but it takes much more effort.

grumbel13 days ago

There is '-Wconversion' to catch things like this. It will however not trigger in this specific case since g++ assumes converting 1000.0 to 1000 is ok due to no loss in precision.

Quantity(100) is counterproductive here, as that doesn't narrow the type, it does the opposite, it casts whatever value is given to the type, so even Quantity(100.5) will still work, while just plain 100.5 would have given an error with '-Wconversion'.

Arnavion13 days ago

The reason to introduce the Quantity wrapper is to not be able to swap the quantity and price arguments.

b5n13 days ago

> -Wconversion ... assumes converting 1000.0 to 1000 is ok due to no loss in precision.

Additionally, `clang-tidy` catches this via `bugprone-narrowing-conversions` and your linter will alert if properly configured.

kelnos13 days ago

My opinion is that if you need to run extra tools/linters in order to catch basic errors, the language & its compiler are not doing enough to protect me from correctness bugs.

I do run clippy on my Rust projects, but that's a matter of style and readability, not correctness (for the most part!).

b5n13 days ago

There's a bit more nuance here than 'basic errors', and modern c compilers offer a lot of options _if you need to use them_.

I appreciate that there are guardrails in a tool like rust, I also appreciate that sharp tools like c exist, they both have advantages.

+1
Arnavion13 days ago
throwaway7645513 days ago

Setting up clang-tidy for your IDE isn't really any more trouble than setting up a LSP. If you want the compiler/linter/whatever to reject valid code to protect you from yourself, there are tools you can use for that. Dismissing them just because they aren't part of the language (what, do you expect ISO C++ to enforce clang-tidy usage?) is silly.

uecker12 days ago

The reason certain warnings are on or off by default in compilers in certain warnings modes depends on whether enough people find them useful enough or not. Rust caters to people who want strictness which makes it annoying to use for others, but if you want this you can also - to a large degree - have this in C and C++.

jpc013 days ago

How much of what Rust the language checks is actually linter checks implemented in the compiler?

Conversions may be fine and even useful in many cases, in this case it isn’t. Converting to std::variant or std::optional are some of those cases that are really nice.

pjmlp12 days ago

I beg to differ, the same reasoning applies to Rust, otherwise there would not be a clippy at all.

favorited13 days ago

Side note, if anyone is interested in hearing more from Matt, he has a programming podcast with Ben Rady called Two's Complement.

https://www.twoscomplement.org

badbart1413 days ago

+1, especially loved the episode from a couple months back about using AI tools in development. Really got me thinking differently about the role of AI in a developer's workflow and how software development will evolve.

penguin_booze13 days ago

I can't see the publish date on the episodes.

writebetterc13 days ago

Yes, Rust is better. Implicit numeric conversion is terrible. However, don't use atoi if you're writing C++ :-). The STL has conversion functions that will throw, so separate problem.

roelschroeven13 days ago

The numeric conversion functions in the STL are terrible. They will happily accept strings with non-numeric characters in them: they will convert "123abc" to 123 without giving an error. The std::sto* functions will also ignore leading whitespace.

Yes, you can ask the std::sto* functions for the position where they stopped because of invalid characters and see if that position is the end of the string, but that is much more complex than should be needed for something like that.

These functions don't convert a string to a number, they try to extract a number from a string. I would argue that most of the time, that's not what you want. Or at least, most of the time it's not what I need.

atoi has the same problem of course, but even worse.

nerpaskteipntei12 days ago

Now there is also std::from_chars function

jeroenhd12 days ago

std::from_chars will still accept "123abc". You have to manually check if all parts of the string have been consumed. On the other hand, " 123" is not accepted, because it starts with an invalid character, so the behaviour isn't "take the first acceptable number and parse that" either.

To get the equivalent of Rust's

    if let Ok(x) = input.parse::<i32>() {
         println!("You entered {x}");
    } else {
        eprintln!("You did not enter a number");
    }
you need something like:

     int x{};
     auto [ptr, ec] = std::from_chars(input.data(), input.data() + input.size(), x);
     if (ec == std::errc() && ptr == input.data() + input.size()) { 
         std::cout << "You entered " << x << std::endl;
     } else {
         std::cerr << "You did not enter a valid number" << std::endl;
     }
I find the choice to always require a start and and end position, and not to provide a method that simply passes or fails, to be quite baffling. In C++26, they also added an automatic boolean conversion for from_chars' return type to indicate success, which considers "only consumed half the input from the start" to be a success.

Maybe I'm weird for mostly writing code that does straightforward input-to-number conversions and not partial string parsers, but I have yet to see a good alternative for Rust's parse().

roelschroeven12 days ago

I guess there's a place for functions that extract or parse partially, but IMO there is a real need for an actual conversion function like Rust's parse() or Python's int() or float(). I think it's a real shame C++ (and C as well) only offers the first and not the second.

titzer13 days ago

> Implicit numeric conversion is terrible.

It's bad if it alters values (e.g. rounding). Promotion from one number representation to another (as long as it preserves values) isn't bad. This is trickier than it might seem, but Virgil has a good take on this (https://github.com/titzer/virgil/blob/master/doc/tutorial/Nu...). Essentially, it only implicitly promotes values in ways that don't lose numeric information and thus are always reversible.

In the example, Virgil won't let you pass "1000.00" to an integer argument, but will let you pass "100" to the double argument.

plus13 days ago

Aside from the obvious bit size changes (e.g. i8 -> i16 -> i32 -> i64, or f32 -> f64), there is no "hierarchy" of types. Not all ints are representable as floats. u64 can represent up to 2^64 - 1, but f64 can only represent up to 2^53 with integer-level precision. This issue may be subtle, but Rust is all about preventing subtle footguns, so it does not let you automatically "promote" integers to float - you must be explicit (though usually all you need is an `as f64` to convert).

titzer13 days ago

Yep, Virgil only implicitly promotes integers to float when rounding won't change the value.

     // OK implicit promotions
     def x1: i20;
     def f1: float = x1;
     def x2: i21;
     def f2: float = x2;
     def x3: i22;
     def f3: float = x3;
     def x4: i23;
     def f4: float = x4;

     // compile error!
     def x5: i24;
     def f5: float = x5; // requires rounding

This also applies to casts, which are dynamically checked.

     // runtime error if rounding alters value
     def x5: i24;
     def f5: float = float.!(x5);
mananaysiempre13 days ago

> Aside from the obvious bit size changes (e.g. i8 -> i16 -> i32 -> i64, or f32 -> f64), there is no "hierarchy" of types.

Depends on what you want from such a hierarchy, of course, but there is for example an injection i32 -> f64 (and if you consider the i32 operations to be undefined on overflow, then it’s also a homomorphism wrt addition and multiplication). For a more general view, various Schemes’ takes on the “numeric tower” are informative.

titzer13 days ago

Virgil allows the maximum amount of implicit int->float injections that don't change values and allows casts (in both directions) that check if rounding altered a value. It thus guarantees that promotions and (successful) casts can't alter program behavior. Given any number in representation R, promotion or casting to type N and then casting back to R will return the same value. Even for NaNs with payloads (which can happen with float <-> double).

dzaima13 days ago

Even that is somewhat bad, e.g. it means you miss "some_u64 = some_u32 * 8" losing bits due to promoting after the arith op, not before.

renox12 days ago

I disagree: when you use floats, you implicitly accept the precision loss/roundings that comes with using floats.. IMHO int to float implicit conversion is fine as long as you have explicit float to int conversion.

im3w1l13 days ago

Forcing people to explicitly casts everything all the time means that dangerous casts don't stand out as much. That's an L for rust imo.

thesuperbigfrog13 days ago

The idiomatic Rust way to do conversions is using the From and TryFrom traits:

https://doc.rust-lang.org/stable/rust-by-example/conversion/...

https://doc.rust-lang.org/stable/rust-by-example/conversion/...

If the conversion will always succeed (for example an 8-bit unsigned integer to a 32-bit unsigned integer), the From trait would be used to allow the conversion to feel implicit.

If the conversion could fail (for example a 32-bit unsigned integer to an 8-bit unsigned integer), the TryFrom trait would be used so that an appropriate error could be returned in the Result.

These traits prevent errors when converting between types and clearly mark conversions that might fail since they return Result instead of the output type.

im3w1l13 days ago

Thanks yeah I probably misremembered or misunderstood.

kasajian13 days ago

This seems a big silly. This is not a language issue. You can have a C++ library that does exactly all the things being shown here so that the application developer doesn't worry about. There would no C++ language features missing that would accomplish what you're able to do on the Rust side.

So is this really a language comparison, or what libraries are available for each language platform? If the latter, that's fine. But let's be clear about what the issue is. It's not the language, it's what libraries are included out of the box.

lytedev13 days ago

The core of this argument taken to its extreme kind of makes the whole discussion pointless, right? All the languages can do all the things, so why bother differentiating them?

To entertain the argument, though, it may not be a language issue, but it certainly is a selling point for the language (which to me indicates a "language issue") to me if the language takes care of this "library" (or good defaults as I might call them) for you with no additional effort -- including tight compiler and tooling integration. That's not to say Rust always has good defaults, but I think the author's point is that if you compare them apples-to-oranges, it does highlight the different focuses and feature sets.

I'm not a C++ expert by any stretch, so it's certainly a possibility that such a library exists that makes Rust's type system obsolete in this discussion around correctness, but I'm not aware of it. And I would be incredibly surprised if it held its ground in comparison to Rust in every respect!

Etheryte13 days ago

Just like language shapes the way we think and talk about things, programming languages shape both what libraries are written and how. You could write anything in anything so long as it's Turing complete, but in real life we see clearly that certain design decisions at the language level either advantage or disadvantage certain types of solutions. Everyone could in theory write C without any memory issues, but we all know how that turns out in practice. The language matters.

Maxatar13 days ago

The Sapir Whorf hypothesis has long been debunked:

https://en.m.wikipedia.org/wiki/Linguistic_relativity

oasisaimlessly13 days ago

The strong hypothesis has been debunked, yes, but nobody is asserting it.

From your link:

> Nevertheless, research has produced positive empirical evidence supporting a weaker version of linguistic relativity:[5][4] that a language's structures influence a speaker's perceptions, without strictly limiting or obstructing them.

milesrout12 days ago

[dead]

sdenton413 days ago

If the default is a loaded gun pointed at your foot, you're going to end up with lots of people missing a foot. "just git gud" isn't a solution.

cbsmith13 days ago

That's an entirely different line of reasoning from the article though, and "just git gud" isn't really the solution here any more than it is to use Rust. There are facilities for avoiding these problems that you don't have to learn how to construct yourself in either language.

LinXitoW13 days ago

Sure, you can emulate some of the features and hope that everyone using your library is doing it "right". Just like you could just use a dynamic language, tag every variable with a type, and hope everyone using your library does the MANUAL work of always doing it correct. Guess we don't need types either.

And while we're at it, why not use assembly? It's all just "syntactic sugar" over bits, doesn't make any difference, right?

cbsmith13 days ago

Yeah, I kept thinking, "doesn't mp-units basically address this entirely"?

jsat13 days ago

I see an article about how strict typing is better, but what would really be nice here is named parameters. I never want to go back to anonymous parameters.

sophacles13 days ago

Why? In 2025 we have tooling available for most every editor that will annotate that information into the display without needing them present in the file. When I autocomplete a function name, all the parameters are there for me to fill in, and annotated into the display afterwards. It seems like an unnecessary step to reify it and force the bytes to be present in the the saved file.

skywhopper12 days ago

So those editors could just insert the names for you. The bytes in the source file are not a serious concern, are they?

kelnos13 days ago

Yes, this is one of the few things that I think was a big mistake in Rust's language design. I used to do a lot of Scala, and really liked named parameters there.

I suppose it could still be added in the future; there are probably several syntax options that would be fully backward-compatible, without even needing a new Rust edition.

quietbritishjim12 days ago

I suppose the sense it is backwards incompatible is that library authors have named their parameters without intended to make them part of the public interface that they commit to maintaining. Perhaps it could be made backwards compatible by being opt in function declarations but that would seem like a bit of a pain.

codedokode13 days ago

When there are 3-4 parameters it is too much trouble to write the names.

bsder13 days ago

> When there are 3-4 parameters it is too much trouble to write the names.

Sorry, I don't agree.

First, code is read far more often than written. The few seconds it takes to type out the arguments are paid again and again each time you have to read it.

Second, this is one of the few things that autocomplete is really good at.

Third, almost everybody configures their IDE to display the names anyway. So, you might as well put them into the source code so people reading the code without an IDE gain the benefit, too.

Finally, yes, they are redundant. That's the point. If the upstream changes something and renames the argument without changing the type I probably want to review it anyway.

int_19h12 days ago

They aren't even necessarily redundant. If you have argument names as part of the function name, they can be overloaded on - and this is much more readable than type-based overloading because it's all explicit. Swift uses this to great effect, e.g. here are some different ways to construct a string:

   String(repeating: "foo", count: 42);

   String(cString: zeroTerminatedBuffer); 

   String(42, radix: 16);

   String(contentsOfFile: "foo.txt", encoding: .utf8);
bsder11 days ago

Oooh. Nice. I forgot about the Smalltalk / ObjC / Swift usage of keywords for messages.

monkeyelite12 days ago

Making something longer doesn’t make it easier to read, especially in repetition.

+1
int_19h12 days ago
noitpmeder13 days ago

Not OP, but I imagine he's arguing for something like python's optional named arguments.

mattgodbolt13 days ago

Wow that guy, eh? He seems to turn up everywhere :D

oconnor66312 days ago

This account's been pretending to be Matt Godbolt since 2014. Knows him better than he knows himself. Absolute commitment to the bit.

karel-3d12 days ago

Well, if you don't want to confuse parameters, you should use Objective-C.

You would do

[orderbook sendOrderWithSymbol:"foo" buy:true quantity:100 price:1000.00]

Cannot confuse that!

(I never used swift, I think it retains this?)

int_19h12 days ago

Swift does retain this, but it makes it look much more readable. The example above would be something like:

  orderbook.sendOrder(symbol: "foo", buy: true, quantity: 100, price: 1000.00);
It also has a nifty syntax for function declarations to allow decoupling the keyword from the variable name when that leads to more readable code:

  func parseInt(from s: String) -> Int {
    /* the variable is named 's' here */
  }

  parseInt(from: "123")
brundolf13 days ago

Wait, Godbolt is someone's name?

frankwiles13 days ago

Yes and he’s a really cool nice guy to boot!

brundolf13 days ago

I almost want to call this nominative determinism

I always thought it was called godbolt because it's like... Zeus blowing away the layers of compilation with his cosmic power, or something. Like it's a herculean task

tialaramex12 days ago

Another example: eBay is because its founders solo consulting business "Echo Bay Technology Group" owned ebay.com and so when he built his auction web site on that domain everybody just called the auction site "eBay" anyway.

morning-coffee13 days ago

Reading "The Rust Book" sold me on Rust (after programming in C++ for over 20 years)

mixmastamyk13 days ago

Am about finished, but several chapters near the end seriously put me to sleep. Will try again some other day I suppose.

tumdum_13 days ago

The one thing that sold me on Rust was that I no longer had to chase down heisenbugs caused by memory corruption.

monkeyelite12 days ago

What are you responding to from the article?

Winsaucerer12 days ago

The title.

adamc13 days ago

Coming from python (or Common Lisp, or...), I wasn't too impressed. In Python I normally make args for any function with more than a couple be keyword arguments, which guarantees that you are aware of how the arguments are being mapped to inputs.

Even Rust's types aren't going to help you if two arguments simply have the same types.

rq113 days ago

Just create dummy wrappers to make a type level distinction. A Height and a a Width can be two separate types even if they’re only floats basically.

Or another (dummy) example transfer(accountA, accountB). Make two types that wrap the same type but one being a TargetAccount and the other SourceAccount.

Use the type system to help you, don’t fight it.

jpc013 days ago

Do you really want width and height or do you actually want dimensions or size? Same with transfer, maybe you wanted a transaction that gets executed. Worst case here use a builder with explicit function names.

rq113 days ago

I don’t really understand your point there.

Sound type systems are equivalent to proof systems.

You can use them to design data structures where their mere eventual existence guarantee the coherence and validity of your program’s state.

The basic example is “Fin n” that carries at compile time the proof that you made the necessary bounds checks at runtime or by construction that you never exceeded some bound.

Some languages allow you to build entire type level state machines! (eg. to represent these transactions and transitions)

+1
jpc013 days ago
nailer12 days ago

> Before we go any further, let me just say he acknowledges floating point is not right for price and later talks about how he usually deals with it. But it makes for a nice example, bear with us.

OK but "this makes for a nice example" is silly, given that the only reason the example throws an error is that you used a float here, when both `quantity` and `price` would have been ints.

    error[E0308]: arguments to this function are incorrect
    --> order/order-1.rs:7:5
      |
    7 |     send_order("GOOG", false, 1000.00, 100); // Wrong
      |     ^^^^^^^^^^                -------  --- expected `f64`, found `{integer}`
      |                               |
      |                               expected `i64`, found `{float}`
I love Rust, but this is artificial.
tialaramex12 days ago

Why are they "ints" ? One of the first things we realise, in both C++ and Rust, is that we mostly don't want these primitives like "int", we want our own user defined types, and Rust is better at that in practice.

In the C and C++ people tend to actually write the file descriptor will be an int, and the timeout will be an int, and the user account number will be an int, and the error code will be an int... because the language doesn't help much when you don't want that.

In the Rust people actually write the file descriptor will be an OwnedFd (from the stdlib) and the timeout will be a Duration (from the stdlib), and user account number might be their own AcctNo and that error code is maybe MyCustomError

This is a language ethos thing, C++ got string slices after Rust despite the language being much older. String slices which are a really basic central idea, but eh, C++ programmers a decade ago just had char * pointers instead and tried not to think about it too much. Still today plenty of C++ APIs don't use string slices, don't work with a real duration type, and so on. It's technically possible but the language doesn't encourage this.

What C++ does encourage is magic implicit conversion, as with this f64 versus i64 case.

nailer12 days ago

Hah, it’s interesting - I program Rust mainly for resource constrained environments and everyone uses primitives. This was a good insight into how other people are thinking.

markus_zhang13 days ago

What if we have a C that removes the quirks without adding too much brain drain?

So no implicit type conversions, safer strings, etc.

LorenDB13 days ago

Walter Bright will probably show up soon to plug D's BetterC mode, but if he doesn't, still check it out.

https://dlang.org/spec/betterc.html

cogman1013 days ago

I've seen this concept tried a few times (For example, MS tried it with Managed C++). The inevitable problem you run into is any such language isn't C++. Because of that, you end up needing to ask, "why pick this unpopular half C/C++ implementation and not Rust/go/D/Java/python/common lisp/haskell."

A big hard to solve problem is you are likely using a C because of the ecosystem and/or the performance characteristics. Because of the C header/macro situation that becomes just a huge headache. All the sudden you can't bring in, say, boost because the header uses the quirks excluded from your smaller C language.

o11c13 days ago

I too have been thinking a lot about a minimum viable improvement over C. This requires actually being able to incrementally port your code across:

* "No implicit type conversions" is trivial, and hardly worth mentioning. Trapping on both signed and unsigned overflow is viable but for hash-like code opting in to wrapping is important.

* "Safer strings" means completely different things to different people. Unfortunately, the need to support porting to the new language means there is little we can do by default, given the huge amount of existing code. We can however, add new string types that act relatively uniformly so that the code can be ported incrementally.

* For the particular case of arrays, remember that there are at least 3 different ways to compute its length (sentinel, size, end-pointer). All of these will need proper typing support. Particularly remember functions that take things like `(begin, middle end)`, or `(len, arr1[len], arr2[len])`.

* Support for nontrivial trailing array-or-other datums, and also other kinds of "multiple objects packed within a single allocation", is essential. Again, most attempted replacements fail badly.

* Unions, unfortunately, will require much fixing. Most only need a tag logic (or else replacement with bitcasting), but `sigval` and others like it are fundamentally global in nature.

* `va_list` is also essential to support since it is very widely used.

* The lack of proper C99 floating-point support, even in $CURRENTYEAR, means that compile-to-C implementations will not be able to support it properly either, even if the relevant operations are all properly defined in the new frontend to take an extra "rounding mode" argument. Note that the platform ABI matters here.

* There are quite a few things that macros are used for, but ultimately this probably is a finite set so should be possible to automatically convert with a SMOC.

Failure to provide a good porting story is the #1 mistake most new languages make.

uecker12 days ago

I have a plan for a safe C and also type-safe generic and bounds-checked containers. Here is some experimental (!) example: https://godbolt.org/z/G4ncoYjfW

Except for some missing pieces, this is safe and I have a prototype based on GCC that would warn about any unsafe features. va_list can be safely used at least with format strings and for union I need an annotations. Life times are the bigger outstanding issue.

trealira12 days ago

> The lack of proper C99 floating-point support, even in $CURRENTYEAR

What do you mean? What's wrong with floating point numbers in C99?

o11c12 days ago

I mean things like: compilers don't support the pragmas, and if the compiler can "see" constants they are often evaluated with the wrong rounding mode.

I'm far from an expert but I've seen enough to know it's wrong.

trealira12 days ago

Oh, I see. I didn't realize that happened.

mamcx13 days ago

If you can live without much of the ecosystem (specially if has async) there is way to write rust very simple.

The core of Rust is actually very simple: Struct, Enum, Functions, Traits.

monkeyelite12 days ago

But unfortunately you will encounter async in libraries you want to use, so this approach is difficult

wffurr13 days ago

This seems like such an obvious thing to have - where is it? Zig, Odin, etc. all seem much more ambitious.

steveklabnik13 days ago

There have been attempts over the years. See here, a decade ago: https://blog.regehr.org/archives/1287

> eventually I came to the depressing conclusion that there’s no way to get a group of C experts — even if they are knowledgable, intelligent, and otherwise reasonable — to agree on the Friendly C dialect. There are just too many variations, each with its own set of performance tradeoffs, for consensus to be possible.

wffurr12 days ago

That was fascinating reading and a graveyard of abandoned "better C" dialects: SaferC, Friendly C, Checked C, etc.

IshKebab13 days ago

I think if you are going to fix C's footguns you'll have to change so much you end up with a totally new language anyway, and then why not be ambitious? It costs a lot to learn a new language and people aren't going to bother if the only benefit it brings is things that can sort of mostly be caught with compiler warnings and static analysis.

zyedidia13 days ago

I think the only "C replacement" that is comparable in complexity to C is [Hare](https://harelang.org/), but several shortcomings make it unsuitable as an actual C replacement in many cases (little/no multithreading, no support for macOS/Windows, no LLVM or GCC support, etc.).

Zambyte13 days ago

And why do you think Zig (and Odin, but I'm not really familiar with that one) is not comparable in complexity to C? If you start with C, replace the preprocessor language with the host language, replace undefined behavior with illegal behavior (panics in debug builds), add different pointer types for different types of pointers (single object pointers, many object pointers, fat many object pointers (slices), nullable pointers), and make a few syntactic changes (types go after the names of values in declarations, pointer dereference is a postfix operator, add defer to move expressions like deallocation to the end of the scope) and write a new standard library, you pretty much have Zig.

dlachausse13 days ago

Swift is really great these days and supports Windows and Linux. It almost feels like a scripting language other than the compile time of course.

kelnos13 days ago

I still have a hard time adopting a language/ecosystem that was originally tied to a particular platform, and is still "owned" by the owners of that platform.

Sun actually did it right with Java, recognizing that if they mainly targeted SunOS/Solaris, no one would use it. And even though Oracle owns it now, it's not really feasible for them to make it proprietary.

Apple didn't care about other platforms (as usual) for quite a long time in Swift's history. Microsoft was for years actively hostile toward attempts to run .NET programs on platforms other than Windows. Regardless of Apple's or MS's current stance, I can't see myself ever bothering with Swift or C#/F#/etc. There are too many other great choices with broad platform and community support, that aren't closely tied to a corporation.

hmry13 days ago

.NET recently had a (very) minor controversy for inserting what amounts to a GitHub Copilot ad into their docs. So yeah, it sure feels like "once a corporate language, always a corporate language", even if it's transferred to a nominally independent org. It might not be entirely rational, but I certainly feel uncomfortable using Swift or .NET.

neonsunset13 days ago

> Microsoft was for years actively hostile toward attempts to run .NET programs on platforms other than Windows

It's been 10 years. Even before that, no action was ever taken against Mono nor any restriction put or anything else. FWIW Swift shares a similar story, except Apple started to care only quite recently about it working anywhere else beyond their platforms.

Oh, and by the way, you need to look at these metrics: https://dotnet.microsoft.com/en-us/platform/telemetry

Maybe take off the conspiracy hat?

> There are too many other great choices with broad platform and community support

:) No, thanks, I'm good. You know why I stayed in .NET land and didn't switch to, say, Go? It's not that it's so good, it's because most alternatives are so bad in one or another area (often many at the same time).

smt8813 days ago

There is no universe where I'm doing to use Apple tooling on a day to day basis. Their DX is the worst among big tech companies by far.

dlachausse13 days ago

They have quite robust command line tooling and a good VS Code plugin now. You don’t need to use Xcode anymore for Swift.

uecker12 days ago

I have a plan for a safe subset of C which would just require a compiler to warn about certain constructs. I also have a proposal for a safe string type. I am not so sure about type conversions though, you get useful warnings already with existing compiler flags and you can solve the problem in the article already just by wrapping the types in structs.

nitwit00513 days ago

Because it's easier to add a warning or error. Don't like implicit conversions? Add a compiler flag, and the issue is basically gone.

Safer strings is harder, as it gets into the general memory safety problem, but people have tried adding safer variants of all the classic functions, and warnings around them.

Certhas13 days ago

Maybe just unsafe rust?

alexchamberlain13 days ago

I'm inferring that you think Rust adds too much brain drain? If so, what?

GardenLetter2713 days ago

The borrow checker rejects loads of sound programs - just read https://rust-unofficial.github.io/too-many-lists/

Aliasing rules can also be problematic in some circumstances (but also beneficial for compiler optimisations).

And the orphan rule is also quite restrictive for adapting imported types, if you're coming from an interpreted language.

https://loglog.games/blog/leaving-rust-gamedev/ sums up the main issues nicely tbh.

IshKebab13 days ago

> The borrow checker rejects loads of sound programs

I bet assembly programmers said the same about C!

Every language has relatively minor issues like these. Seriously pick a language and I can make a similar list. For C it will be a very long list!

oconnor66312 days ago

> The borrow checker rejects loads of sound programs - just read https://rust-unofficial.github.io/too-many-lists/

It's important to be careful here: a lot (most? all?) of these rejections are programs that could be sound in a hypothetical Rust variant that didn't assert the unique/"noalias" nature of &mut reference, but are in fact unsound in actual Rust.

leonheld13 days ago

I love Rust, but I after doing it for a little while, I completely understand the "brain drain" aspect... yes, I get significantly better programs, but it is tiring to fight the borrow-checker sometimes. Heck, I currently am procrastinating instead of going into the ring.

Anyhow, I won't go back to C++ land. Better this than whatever arcane, 1000-line, template-hell error message that kept me fed when I was there.

amai12 days ago

ADA has solved these issues since the 80s:

https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...

TinkersW12 days ago

Dunno about this, the example C++ code is so obviously bad that I had no desire to watch the video.

Creating strong types for currency seems like common sense, and isn't hard to do. Even the Rust code shouldn't be using basic types.

jk300012 days ago

Somehow this is the pattern when comparing C++ to Rust: write outrageously bad C++ in the first place, then complain about it.

simpaticoder13 days ago

I don't get it. Isn't this a runtime problem and not a compile-time problem? buy() or sell() is going to be called with dynamic parameters at runtime, in general. That is, calls with concrete values are NOT going to be hard-coded into your program. I would write the function to assert() invariants within the function, and avoid chasing compile-time safety entirely. If parameter order was a concern, then I'd modify the function to take a struct, or similar.

brundolf13 days ago

> Isn't this a runtime problem and not a compile-time problem? buy() or sell() is going to be called with dynamic parameters at runtime, in general.

Yes, but the strength of Rust's type system means you're forced to handle those bad dynamic values up front (or get a crash, if you don't). That means the rest of your code can rest safe, knowing exactly what it's working with. You can see this in OP's parsing example, but it also applies to database clients and such

simpaticoder13 days ago

What if the valid input for quantity must be greater than 0? A reasonable constraint, I think. The OP's example is contrived to line up with Rust's built-in types, and ignores the general problem.

brundolf12 days ago

It's a common fallacy to equate "there's a limit to how much we can guarantee" with "guaranteeing anything is a waste of time". Each guarantee we can make eliminates a whole class of possible bugs

That said, Rust also makes it very easy to define your own types that can only be constructed/unpacked in limited ways, which can enforce special constraints on their contents. And it has a cultural norm of doing this in the standard library and elsewhere

Eg: a sibling poster noted the NonZero<T> type. Another example is that Rust's string types are guarantees to always contain valid UTF-8, because whenever you try and convert a byte array into a string, it gets checked and possibly rejected.

siev13 days ago

Rust also has the standard NonZero<T> type for that use case.

mempko13 days ago

Rust does seem to have a lot of nice features. My biggest blocker for me going to Rust from C++ is that C++ has much better support for generic programming. And now that Concepts have landed, I'm not aware of any language that can compete in this area.

juliangmp11 days ago

> No luck! Both clang 19 and gcc 14 will take that and not complain - even with -std=c++23 -Wall -Wextra -Wpedantic, which I use for all of the C++ code in this article!

I keep a list of "additional" error codes for my work with GCC and its at least two dozen parameters. The fact that some of these aren't the default is outrageous to me. And Wconversion is one of those things that seemingly no one uses so every open source library I include gives me those errors.

mmaniac11 days ago

> You know what's the most interesting part of this whole article? The thing Rust is very famous for, memory safety, did not feature at all.

I think that's the essential point, really... It'd be hard to argue that the rest of Rust isn't overall "better" than C++, but the compromises made to flexibility and ergonomics to achieve memory safety in Rust are the biggest points of contention for Rust critics.

DrBazza12 days ago

The implicit problem here (pun intended) in the given examples are implicitness vs. explicitness.

Rust chose (intentionally or otherwise) to do the opposite of the many things that C++ does, because C++ does it wrong. And C++ does it wrong because we didn't know any better at the time, and the world, pre-internet, was much less connected. Someone had to do it first (or first-ish).

The main thing I like about Rust is the tooling. C++ is death by a thousand build systems and sanitizers.

nyanpasu6413 days ago

The problem I've always had with unit type wrappers is you can't convert between a &[f32] and a &[Amplitude<f32>] like you can convert a single scalar value.

conradludgate12 days ago

As long as the wrapper is marked as transparent, it's safe to transmute the wrappers.

If you don't want unsafe, you can make use of this safe derive: https://docs.rs/bytemuck/latest/bytemuck/trait.TransparentWr...

fvncc13 days ago

There are libraries that help with these conversions. See e.g.: https://docs.rs/bytemuck/latest/bytemuck/trait.TransparentWr...

zombot12 days ago

Types are a catastrophe in C++ that cannot be fixed, no matter how much cruft you bolt on after the fact. It's time to leave this dinosaur and use a sane language.

antirez13 days ago

You can have two arguments that are semantically as distinct and important as quantity and price and be both integers, and if you swap them is a big issue anyway. And you would be forced, if you like this kind of programming, to create distinct types anyway. But I never trust this kind of "toy" defensive programming. The value comes from testing very well the code, from a rigorous quality focus.

feverzsj12 days ago

The C++ code is just pure nonsense. If you really want to be fool-proofing, C++ offers concept, which is far superior to what rust offers.

atemerev13 days ago

Right. I attempted using Rust for trading-related code as well. However, I failed to write a dynamically linked always sorted order book where you can splice orders in the middle. It is just too dynamic for Rust. Borrow checker killed me.

And don't get me started on dynamic graphs.

I would happily use Rust over C++ if it had all other improvements but similar memory management. I am completely unproductive with Rust model.

kelnos13 days ago

The nice thing is that you can always drop down to unsafe and use raw pointers if your data structure is truly not suited to Rust's ownership rules.

And while unsafe Rust does have some gotchas that vanilla modern C++ does not, I would much rather have a 99% memory-safe code base in Rust than a 100% "who knows" code base in C++.

atemerev13 days ago

I have read the "too many linked lists" story and I think the other commenters here are right; the less pointers the better. Even with unsafe, there's just too much ceremony.

0x1ceb00da13 days ago

> Borrow checker killed me.

You gotta get your timing right. Right hook followed by kidney shot works every time.

hacker_homie13 days ago

I have run into similar issues trying to build real applications. You end up spending more time arguing with the borrow checker than writing code.

lytedev13 days ago

I think this is true initially and Rust didn't "click" for me for a long time.

But once you are _maintaining_ applications, man it really does feel like absolute magic. It's amazing how worry-free it feels in many respects.

Plus, once you do embrace it, become familiar, and start forward-thinking about these things, especially in areas that aren't every-nanosecond-counts performance-wise and can simply `Arc<>` and `.clone()` where you need to, it is really quite lovely and you do dramatically less fighting.

Rust is still missing a lot of features that other more-modern languages have, no doubt, but it's been a great ride in my experience.

skippyboxedhero13 days ago

Using reference counts is a real issue.

The idea with Rust is that you get safety...not that you get safety at the cost of performance. The language forces you into paying a performance cost for using patterns when it is relatively easy for a human to reason about safety (imo).

You can use `unsafe` but you naturally ask yourself why I am using Rust (not rational, but true). You can use lifetimes but, personally, every time I have tried to use them I haven't been able to indicate to the compiler that my code is actually safe.

In particular, the protections for double-free and free before use are extremely limiting, and it is possible to reason about these particular bugs in other ways (i.e. defer in Go and Zig) in a way that doesn't force you to change the way you code.

Rust is good in many ways but the specific problem mentioned at the top of this chain is a big issue. Just saying: don't use this type of data structure unless you pay performance cost isn't an actual solution to the problem. The problem with Rust is that it tries to force safety but doesn't have good ways for devs to tell the compiler code is safe...that is a fundamental weakness.

I use Rust quite a bit, it isn't a terrible language and is worth learning but these are big issues. I would have reservations using the language in my own company, rather than someone else's, and if I need to manage memory then I would look elsewhere atm. Due to the size of the community, it is very hard not to use Rust too (for example, Zig is great...but no-one uses it).

+1
lytedev13 days ago
sunshowers13 days ago

I apologize for the naive question, but that sounds like a heap?

atemerev13 days ago

We have to do arbitrary insertions/deletions from the middle, many of them. I think it is more like BTreeMap, but we need either sorting direction or rev(), and there were some problems with both approaches I tried to solve, but eventually gave up.

sunshowers13 days ago

I see! The big issue I've run into with BTreeMap is that you can't provide an external comparator. If comparisons only require data that the keys already have, then the Reverse wrapper [1] has worked well for me.

[1] https://doc.rust-lang.org/std/cmp/struct.Reverse.html

jpc013 days ago

In my experience you need to approach this with vec or arrays of some sort and pass indices around… “We have pointers at home” behaviour. This is fine but coming from C++ it definitely feels weird…

bigstrat200313 days ago

Why not just use pointers? Rust has them, they aren't evil or anything. If you need to make a data structure that isn't feasible with references due to the borrow checker (such as a linked list), there's absolutely nothing wrong with using pointers.

+1
atemerev13 days ago
sunshowers13 days ago

I agree in general Rust makes you use arrays and indexes, but heaps are traditionally implemented that way in any language.

xarope13 days ago

This reminds me of SQL's constraints, or pydantic's custom types and validators, which can validate that a value should be an int, and between 0-999, and not exceed, e.g. -1 or 1000.

pydantic is a library for python, but I'm not aware of anything similar in rust or golang that can do this yet? (i.e. not just schema validation, but value range validation too)

kstrauser12 days ago

The semantics are a little different in that you have to explicitly call .validate(), but Rust's "validator" crate[0] feels a lot like Pydantic to me.

[0]https://docs.rs/validator/latest/validator/

xarope10 days ago

I'm aware of this as well as go(lang)'s https://github.com/go-playground/validator, so you are right, I'm wondering about a more implicit validator like what Pydantic does when a value is assigned, versus an explicit call to validate().

jayong9312 days ago

[dead]

gbin13 days ago

Interestingly in Rust I would immediately use an Enum for the Order! Way more powerful semantically.

skywal_l13 days ago

I love his videos on computerphile. One was just dropped a few hours ago: https://www.youtube.com/watch?v=1su3lAh-k4o

TnS-hun12 days ago

Examples miss angle brackets. For example:

  template  explicit Quantity(T quantity) : m_quantity(quantity) {

  sendOrder("GOOG", false, Quantity(static_cast(atoi("-100"))),
nmeofthestate12 days ago

If I tried to repro this problem in C++ code I believe it would fail clang tidy checks because of the implicit numeric casts.

jovial_cavalier12 days ago

Hey, check this out:

#include <iostream>

struct Price { double x; };

struct Quantity { int x; };

void sendOrder(const char *symbol, bool buy, Quantity quantity, Price price) {

    std::cout << symbol << " " << buy << " " << quantity.x << " " << price.x

    << std::endl;
}

int main(void) {

    sendOrder("GOOG", false, Quantity{100}, Price{1000.00}); // Correct

    sendOrder("GOOG", false, Price{1000.00}, Quantity{100}); // compiler error
}

If you're trying to get it to type check, you have to make a type first.

I don't appreciate these arguments, and view them as disingenuous.

legobmw9912 days ago

That still has the issue of Quantity{-100} being a-ok as far as the compiler is concerned, but there are other things one can do (as the article alludes to).

My reading of this article wasn't to say that these things are impossible in C++, just that they're not the default or the first thing you try as a beginner is perfectly wrong.

monkeyelite12 days ago

This article compares onlye one specific feature of C++ with Rust - integral type conversions.

tialaramex12 days ago

All of C++ has implicit type conversions. The language even has a keyword (explicit) to try to reign this in because it's so obviously a bad idea.

In Rust what they'd do if they realised there's a problem like this is make explicit conversion the default, with a language Edition, and so within a few years just everybody is used to the improved language. In C++ instead you have to learn to write all the appropriate bugfix keywords all over your software, forever.

monkeyelite12 days ago

> because it's so obviously a bad idea.

Agreed. The history here is compatibility with C type conversion.

I just expected a more compelling Rust /C++ comparison but we got an emphasis of a poorly designed feature which the standard has taken steps to improve already.

tialaramex12 days ago

No, implicit conversion is a deliberate C++ feature and no analog existed in C. Like a lot of awful things about C++ this is their own choice and it's frustrating that they try to blame C for their choices.

In C++ when we define a class Foo (a thing which doesn't exist in C) and we write a constructor Foo(Bar x) (which doesn't exist in C) which takes a single parameter [in this case a Bar named x], that is implicitly adopted as a conversion for your new user defined type and by default without any action on your part the compiler will just invoke that constructor to make a Bar into a Foo whenever it thinks that would compile.

This is a bad choice, and it's not a C choice, it's not about "compatibility".

+1
seanhunter11 days ago
+1
monkeyelite12 days ago
time4tea12 days ago

aka use Tiny Types, they will help you.

Been true in all statically typed languages for decades!

It's good advice.

mrsofty12 days ago

Godbolt is a legend and I don't care what the others say. I believe him when he said he was "just helping" that sheep through the hedge. Well deserved praise Matty-poo and well written article.

hacker_homie13 days ago

Wconversion And werror?

mansoor_12 days ago

FYI newer builds of GCC have this functionality.

Horffupolde12 days ago

Would this also work for Zig?

spyrja13 days ago

To be fair, this sort of thing doesn't have to be so much worse in C++ (yes, it would have been nice if it had been built into the language itself to begin with). You just need a function to do a back-and-forth conversion which then double-check the results, ie:

  #include <exception>  
  #include <sstream>

  template <typename From, typename To>
  void convert_safely_helper_(From const& value, To& result) {
    std::stringstream sst;
    sst << value;
    sst >> result;
  }

  // Doesn't throw, just fails
  template <typename From, typename To>
  bool convert_safely(From const& value, To* result) {
    From check;
    convert_safely_helper_(value, *result);
    convert_safely_helper_(*result, check);
    if (check != value) {
      *result = To();
      return false;
    }
    return true;
  }

  // Throws on error
  template <typename To, typename From>
  To convert_safely(From const& value) {
    To result;
    if (!convert_safely(value, &result))
      throw std::logic_error("invalid conversion");
    return result;
  }

  #include <iostream>

  template <typename Buy, typename Quantity, typename Price>
  void sendOrder(const char* symbol, Buy buy, Quantity quantity, Price price) {
    std::cout << symbol << " " << convert_safely<bool>(buy) << " "
            << convert_safely<unsigned>(quantity) << " " << convert_safely<double>(price)
            << std::endl;
  }

  #define DISPLAY(expression)         \
    std::cout << #expression << ": "; \
    expression

  template <typename Function>
  void test(Function attempt) {
    try {
      attempt();
    } catch (const std::exception& error) {
      std::cout << "[Error: " << error.what() << "]" << std::endl;
    }
  }

  int main(void) {
    test([&] { DISPLAY(sendOrder("GOOG", true, 100, 1000.0)); });
    test([&] { DISPLAY(sendOrder("GOOG", true, 100.0, 1000)); });
    test([&] { DISPLAY(sendOrder("GOOG", true, -100, 1000)); });
    test([&] { DISPLAY(sendOrder("GOOG", true, 100.5, 1000)); });
    test([&] { DISPLAY(sendOrder("GOOG", 2, 100, 1000)); });
  }

Output:

  sendOrder("GOOG", true, 100, 1000.0): GOOG 1 100 1000
  sendOrder("GOOG", true, 100.0, 1000): GOOG 1 100 1000
  sendOrder("GOOG", true, -100, 1000): GOOG 1 [Error: invalid conversion]
  sendOrder("GOOG", true, 100.5, 1000): GOOG 1 [Error: invalid conversion]
  sendOrder("GOOG", 2, 100, 1000): GOOG [Error: invalid conversion]

Rust of course leaves "less footguns laying around", but I still prefer to use C++ if I have my druthers.
pjmlp12 days ago

Yes, from safety point of view Rust is much better option, however from the ecosystems I care about (language runtimes and GPU coding), both professionally and as hobby, C++ is the systems language to go, using Rust in such contexts would require me to introduce extra layers and do yak shaving instead of the actual problem that I want to code for.

spyrja12 days ago

Well, precisely. Such is the price of "general-purpose safety". The biggest irony IMO is that the security features of Rust are so often skirted in the name of speed, efficiency, etc, with the end result being a program which isn't much more secure than its C++ equivalent. ¯\_(ツ)_/¯

pjmlp11 days ago

It isn't the safety or lack thereof, rather really adding another toolchain, development and debugging complexity.

Lets say I am doing something with V8, CLR, JVM, GCC, LLVM, CUDA, Metal, outside the managed languages that sit on top of those runtimes, compiler toolchains, GPGPU.

That infrastructure code is written in C++, and making use of Rust means adding yet another layer on how to compile, interop and debug, on top of what is already there.

As for safety in general, I am a believer of systems programming languages with automatic memory management, and polyglot programming, we aren't yet there because industry has the tendency to take decades to finally adopt good ideas, and only on a partial way.

Thus while I appreciate what Rust has achieved bringing affine types into mainstream, not an easy job, I consider this a transition step until we get both approaches in the same language, automatic resource management with affine,linear,effects,dependent types.

However this ultimately won't matter as much, when we eventually get our AI buddies good enough that they can spew executables directly.

steveklabnik12 days ago

> The biggest irony IMO is that the security features of Rust are so often skirted in the name of speed, efficiency, etc, with the end result being a program which isn't much more secure than its C++ equivalent.

Citation needed. The empiric evidence I’ve seen has shown the opposite.

+1
spyrja12 days ago
codedokode13 days ago

What about catching integer overflow? Free open-source languages still cannot do it unlike they commercial competitors like Swift?

thesuperbigfrog13 days ago

Ada has had overflow checks for decades:

https://learn.adacore.com/courses/intro-to-ada/chapters/stro...

And there is an Ada implementation that is part of GCC:

https://www.gnu.org/software/gnat/

ultimaweapon12 days ago

Rust is the only language I can easily control how integer overflow should behave. I can use `var1.wrapping_add(var2)` if I want the result to be wrapped or `var1.checked_add(var2)` if I don't want it to overflow.

codedokode12 days ago

The functions are so verbose and inconvenient that even Rust developers themselves do not use them. For example, in this code [1] they used a wrapping addition instead of "checked_add" because it is faster to write.

For comparison, Swift uses "+" for checked addition and as a result, majority of developers use checked addition by default. And in Rust due to its poor design choices most developers use wrapping addition even where a checked addition should be used.

[1] https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#2010

genrilz12 days ago

The reason '+ 1' is fine in the example you gave is that length is always less than or equal to capacity. If you follow 'grow_one' which was earlier in the function to grow the capacity by one if needed, you will find that it leads to the checked addition in [0], which returns an error that [1] catches and turns into a panic. So using '+1' prevents a redundant check in release mode while still adding the check in debug mode in case future code changes break the 'len <= capacity' invariant.

Of course, if you don't trust the standard library, you can turn on overflow checks in release mode too. However, the standard library is well tested and I think most people would appreciate the speed from eliding redundant checks.

  [0]: https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#651
  [1]: https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#567
ultimaweapon12 days ago

Checked addition by default will have too much overhead and it will hurt performance, which unacceptable in Rust since it was designed as a system language. Swift can use checked add by default since it was designed for application software.

Your example code is not because it is faster to write, it is because it is impossible for its to overflow on that line.

codedokode12 days ago

Why should checked addition have any overhead? You should just use checked addition instruction (on architectures that support it) instead of wrapping addition.

Or just because on Intel CPUs it has overhead, we must forget about writing safer code?

kelnos13 days ago

Rust does have checked arithmetic operations (that return Result), but you have to explicitly opt in to them, of course, and they're not as ergonomic to use as regular arithmetic.

trealira13 days ago

But, by default, normal arithmetic operations trap on overflow in debug mode, although they wrap with optimizations on.

codedokode12 days ago

As a result, Rust developers themselves use wrapping addition where a checked addition should be used: https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#2010

tialaramex12 days ago

That's not a wrapping addition, that addition will never overflow so it has no overflow behaviour.

This line could only overflow after we need to grow the container, so immediately this means the type T isn't a ZST as the Vec for ZSTs doesn't need storage and so it never grows.

Because its not a ZST the maximum capacity in Rust is never bigger than isize::MAX which is an entire binary order of magnitude smaller than usize::MAX, as a result len + 1 can't overflow the unsigned type, so this code is correct as written.

lytedev13 days ago

I'm not sure if this is what you mean, exactly, but Rust indeed catches this at compile time.

https://play.rust-lang.org/?version=stable&mode=debug&editio... https://play.rust-lang.org/?version=stable&mode=debug&editio...

codedokode13 days ago

I meant panic if during any addition (including in runtime) an overflow occurs.

genrilz13 days ago

If you obscure the implementation a bit, you can change GP's example to a runtime overflow [0]. Note that by default the checks will only occur when using the unoptimized development profile. If you want your optimized release build to also have checks, you can put 'overflow-checks = true' in the '[profile.release]' section of your cargo.toml file [1].

  [0]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=847dc401e16fdff14ecf3724a3b15a93
  [1]: https://doc.rust-lang.org/cargo/reference/profiles.html
+1
codedokode12 days ago
trealira13 days ago

You can set a flag for that: https://doc.rust-lang.org/rustc/codegen-options/index.html#o...

By default, they're on during debug mode and off in release mode.

+1
codedokode12 days ago
tialaramex12 days ago

Why do you want a panic? Shift left. Overflow can be rejected at compile time for a price that you might be able to afford - generality.

Just insist that the programmer prove that overflow can't occur, and reject programs where the programmer couldn't or wouldn't do this.

codedokode12 days ago

Programmer has other things to do. Computer or CPU should do the checks.

z_open13 days ago

assembly catches integer overflow. You just need to check the flag.

no-marxism-thx13 days ago

[dead]

codr713 days ago

[flagged]

Calliope112 days ago

[flagged]

kccqzy12 days ago

Undefined behavior can appear with or without templates. But other than that, yes, it is viscerally shocking to see on Compiler Explorer that your entire function has been compiled to a single instruction, ud2.

Calliope112 days ago

Seeing an entire function compiled down to a single ud2 instruction in Compiler Explorer was the moment I truly grasped the power of undefined behavior.

Templates are just tools — but undefined behavior (UB) is the real shadow lord. That seemingly innocent line of C++ code you wrote? The compiler might decide it has no meaning at all and emit ud2 — a deliberate “invalid operation” that crashes the program.

This isn’t just a language “gotcha”; it’s the compiler shouting: “Your code makes no sense to me — so I’m terminating it with prejudice.”

It’s both technically fascinating and philosophically unsettling. Who should bear the burden of balancing performance and safety? Should we be more careful developers, or should the language take more responsibility? Or… is this precisely the case for Rust?

The compiler didn’t betray you. It simply honored your undefined contract.

globalnode12 days ago

My admittedly uninformed impression of Rust is that its a lot like Go (in spirit?), a language invented to shepherd novice programmers into not making mistakes with resource usage.

I imagine faceless shameless mega-corps with thousands of Rust/Go peons coding away on the latest soulless business apps. Designed to funnel the ignorant masses down corridors of dark pattern click bait and confusing UX.

Having exposed my biases, happy to be proven wrong. Why are game studios still using C++? Because that's the language game programmers know and feel comfortable with? Or some other reason?

Embedded is still C, games are C++, scientific and data are Python and R (I'm talking in general here). What is the niche for Rust?

simonask12 days ago

Novice programmers will take longer to be productive in Rust compared to Go. Rust primarily improves the productivity of people who know what they are doing, because it gives them much better tools to manage complexity.

Games are written in C++ because game engines and tooling have person-centuries of work poured into them. Reimplementing Unreal Engine in Rust would require another few person-centuries of work, which is an investment that doesn't really make sense. Economically, dealing with the shortcomings of C++ is much, much cheaper.

But Rust is definitely encroaching in all of these areas. Embedded Rust is doing great, scientific Rust is getting there (check pola.rs). Rust is an obvious candidate for the next big game engine, and it is already quite viable for indie undertakings, though it is still early days.

amai12 days ago

Though in the future people might simply ask an AI to convert a codebase from C++ to Rust.

gwd12 days ago

> novice programmers

I think Rust has too high a learning curve, and too many features, for novice programmers in general.

> Embedded is still C, games are C++, scientific and data are Python and R (I'm talking in general here). What is the niche for Rust?

Rust has already made huge inroads in CLIs and TUIs, as far as I can tell. Embedded is a slow-moving beast by design, but it seems to me (as someone in an adjacent area) that it could be a big win there, particularly in places that need safety certification.

All the stories of people using Rust for game development are about people who tried it and find that it doesn't fit: It makes experimentation and exploration slow enough that the reduction in minor bugs in game logic isn't really worth it.

Havoc12 days ago

Go is seeing more traction in the web space - api backends and other stuff that needs lots of concurrency like that. Seen as easier to learn than rust but not quite as fine grained low level control.

Rust is a bit more systems focused for low level stuff. See inclusions in the Linux kernel. Also seeing some traction in the WASM space given that it’s not GC

They’re both quite versatile though so above are pretty gnarly generalisations.

Zig is in a similar space as these

chickenbuckcar12 days ago

Economic inertia alone can already enough.

Numpy use C/C++ because BLAS use C/C++ Torch originally use Lua, then switch to Python because popularity

imtringued12 days ago

The quality of AMD's software stack speaks for itself. It's all C++ and the quality is exactly poor as you'd expect.

notimetorelax12 days ago

Those mega corps that you talk about use C++ too. It’s just a false dichotomy argument you’re making.

globalnode12 days ago

True enough, I was going down a path there and got a little excited :S