The one thing that sold me on Rust (going from C++) was that there is a single way errors are propagated: the Result type. No need to bother with exceptions, functions returning bool, functions returning 0 on success, functions returning 0 on error, functions returning -1 on error, functions returning negative errno on error, functions taking optional pointer to bool to indicate error (optionally), functions taking reference to std::error_code to set an error (and having an overload with the same name that throws an exception on error if you forget to pass the std::error_code)...I understand there's 30 years of history, but it still is annoying, that even the standard library is not consistent (or striving for consistency).
Then you top it on with `?` shortcut and the functional interface of Result and suddenly error handling becomes fun and easy to deal with, rather than just "return false" with a "TODO: figure out error handling".
jeroenhd · 9h ago
The result type does make for some great API design, but SerenityOS shows that this same paradigm also works fine in C++. That includes something similar to the ? operator, though it's closer to a raw function call.
SerenityOS is the first functional OS (as in "boots on actual hardware and has a GUI") I've seen that dares question the 1970s int main() using modern C++ constructs instead, and the API is simply a lot better.
I can imagine someone writing a better standard library for C++ that works a whole lot like Rust's standard library does. Begone with the archaic integer types, make use of the power your language offers!
If we're comparing C++ and Rust, I think the ease of use of enum classes/structs is probably a bigger difference. You can get pretty close, but Rust avoids a lot of boilerplate that makes them quite usable, especially when combined with the match keyword.
I think c++, the language, is ready for the modern world. However, c++, the community, seems to be struck at least 20 years in the past.
jchw · 8h 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.
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.
jchw · 5h ago
The reason it works this way is there's legitimately no easy way around it. You're not guaranteed a reasonable zero value for any type, so you can't do the slightly better Go thing (defined behavior but still wrong... Not great.) and you certainly can't do the Rust thing, because... There's no pattern matching. You can't conditionally enter a branch based on the presence of a value.
There really is no reasonable workaround here, the language needs to be amended to make this safe and ergonomic. They tried to be cheeky with some of the other APIs, like std::variant, but really the best you can do is chuck the conditional branch into a lambda (or other function-based implementation of visitors) and the ergonomics of that are pretty unimpressive.
Edit: but maybe fortune will change in the future, for anyone who still cares:
You could assert. You could throw. I can’t understand how, this modern age where so many programs end up getting hacked, that introducing more UB seems like a good idea.
This is one odd the major reasons I switched to rust, just to escape spending my whole life worrying about bugs caused by UB.
jchw · 4h ago
Assertions are debug-only. Exceptions are usually not guaranteed to be available and much of the standard library doesn't require them. You could std::abort, and that's about it.
I think the issue is that this just isn't particularly good either. If you do that, then you can't catch it like an exception, but you also can't statically verify that it won't happen.
C++ needs less of both undefined behavior and runtime errors. It needs more compile-time errors. It needs pattern matching.
CJefferson · 2h ago
I agree these things would be better, but I don’t understand how anyone can think UB is better than abort.
(Going to moan for a bit, and I realise you aren’t responsible for the C++ standards mess!)
I have been hearing for about… 20 years now that UB gives compilers and tools the freedom to produce any error catching they like, but all it seems to have done in the main is give them the freedom to produce hard to debug crash code.
You can of course usually turn on some kind of “debug mode” in some compilers, but why not just enforce that as standard? Compilers would still be free to add a “standards non-compliant” go fast mode if they like.
loeg · 5h ago
Facebook's Folly has a similar type: folly::Expected (dating to 2016).
throwaway2037 · 20m ago
If I had to guess, that idea came from Andrei Alexandrescu.
a_t48 · 8h ago
There’s a few backports around, not quite the same as having first class support, though.
jchw · 5h ago
I believe the latest versions of GCC, Clang, MSVC and XCode/AppleClang all support std::expected, in C++23 mode.
jll29 · 1h 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").
Rucadi · 6h 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.
d_tr · 7h ago
C++ carries so much on its back and this makes its evolution over the past decade even more impressive.
zozbot234 · 9h ago
> The one thing that sold me on Rust (going from C++) was that there is a single way errors are propagated: the Result type. No need to bother with exceptions
This isn't really true since Rust has panics. It would be nice to have out-of-the-box support for a "no panics" subset of Rust, which would also make it easier to properly support linear (no auto-drop) types.
kelnos · 8h 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.
wyager · 4h 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.
tcfhgj · 8h 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
nextaccountic · 2h ago
Obviously yes. For the same reason it's acceptable that myvec[i] panics (it will panic if i is out of bounds - but you already figured out that i is in bounds) and a / b panic for a and b integers (it will panic if b is zero, but if your code is not buggy you already tested if b is zero prior to dividing right?)
Panic is absolutely fine for bugs, and it's indeed what should happen when code is buggy. That's because buggy code can make absolutely no guarantees on whether it is okay to continue (arbitrary data structures may be corrupted for instance)
Indeed it's hard to "treat an error" when the error means code is buggy. Because you can rarely do anything meaningful about that.
This is of course a problem for code that can't be interrupted.. which include the Linux kernel (they note the bug, but continue anyway) and embedded systems.
Note that if panic=unwind you have the opportunity to catch the panic. This is usually done by systems that process multiple unrelated requests in the same program: in this case it's okay if only one such request will be aborted (in HTTP, it would return a 5xx error), provided you manually verify that no data structure shared by requests would possibly get corrupted. If you do one thread per request, Rust does this automatically; if you have a smaller threadpool with an async runtime, then the runtime need to catch panics for this to work.
conradludgate · 7h 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.
pdimitar · 8h ago
I don't speak for anyone else but I'm not using `unwrap` and `expect`. I understand the scenario you outlined but I've accepted it as a compromise and will `match` on a map's fetching function and will have an `Err` branch.
I will fight against program aborts as hard as I possibly can. I don't mind boilerplate to be the price paid and will provide detailed error messages even in such obscure error branches.
Again, speaking only for myself. My philosophy is: the program is no good for me dead.
von_lohengramm · 8h ago
> the program is no good for me dead
That may be true, but the program may actually be bad for you if it does something unexpected due to an unforeseen state.
pdimitar · 8h ago
Agreed, that's why I don't catch panics either -- if we get to that point I'm viewing the program as corrupted. I'm simply saying that I do my utmost to never use potentially panicking Rust API and prefer to add boilerplate for `Err` branching.
codedokode · 8h 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?
Arnavion · 8h 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?
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.
kllrnohj · 1h ago
> Rust unfortunately picked the wrong default here for the sake of convenience, along with the default of assuming a global allocator. [...] Zig for example does it right by having explicit allocators from the start
Rust picked the right default for applications that run in an OS whereas Zig picked the right default for embedded. Both are good for their respective domains, neither is good at both domains. Zig's choice is verbose and useless on a typical desktop OS, especially with overcommit, whereas Rust's choice is problematic for embedded where things just work differently.
Arnavion · 1h ago
Various kind of "desktop" applications like databases and video games use custom non-global allocators - per-thread, per arena, etc - because they have specific memory allocation and usage patterns that a generic allocator does not handle as well as targeted ones can.
My current $dayjob involves a "server" application that needs to run in a strict memory limit. We had to write our own allocator and collections because the default ones' insistence on using GlobalAlloc infallibly doesn't work for us.
Thinking that only "embedded" cares about custom allocators is just naive.
smj-edison · 7h ago
> Rust unfortunately picked the wrong default here
I partially disagree with this. Using Zig style allocators doesn't really fit with Rust ergonomics, as it would require pretty extensive lifetime annotations. With no_std, you absolutely can roll your own allocation styles, at the price of more manual lifetime annotations.
I do hope though that some library comes along that allows for Zig style collections, with the associated lifetimes... (It's been a bit painful rolling my own local allocator for audio processing).
Arnavion · 6h ago
Explicit allocators do work with Rust, as evidenced by them already working for libstd's types, as I said. The mistake was to not have them from day one which has caused most code to assume GlobalAlloc.
As long as the type is generic on the allocator, the lifetimes of the allocator don't appear in the type. So eg if your allocator is using a stack array in main then your allocator happens to be backed by `&'a [MaybeUninit<u8>]`, but things like Vec<T, A> instantiated with A = YourAllocator<'a> don't need to be concerned with 'a themselves.
If by Zig-style allocators you specifically mean type-erased allocators, as a way to not have to parameterize everything on A:Allocator, then yes the equivalent in Rust would be a &'a dyn Allocator that has an infectious 'a lifetime parameter instead. Given the choice between an infectious type parameter and infectious lifetime parameter I'd take the former.
smj-edison · 6h ago
Ah, my bad, I guess I've been misunderstanding how the Allocator proposal works all along (I thought it was only for 'static allocators, this actually makes a lot more sense!).
I guess all that to say, I agree then that this should've been in std from day one.
steveklabnik · 4h ago
The problem is, everything should have been there since day 1. It’s still unclear which API Rust should end up with, even today, which is why it isn’t stable yet.
JBits · 2h ago
Looking forward to the API when it's stabilised.
Have there been any updates on the progress of allocators of this general area of Rust over the past year?
steveklabnik · 2h ago
I haven’t paid that close of attention, but there have been two major APIs that people seem to be deciding between. We’ll see.
johnisgood · 4h ago
> Zig for example does it right by having explicit allocators from the start
Odin has them, too, optionally (and usually).
nicce · 8h 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.
codedokode · 7h ago
Wrapping on overflow is wrong because this is not the math we expect. As a result, errors and vulnerabilities occur (look at Linux kernel for example).
nicce · 7h ago
It depends on the context. Of course the result may cause vulnerabilities if the program logic in bad context depends on it. But yeah, generally I would agree.
PhilipRoman · 7h 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.
tialaramex · 2h ago
You can tell Linux that you don't want overcommit. You will probably discover that you're now even more miserable and change it back, but it's an option.
kllrnohj · 1h 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...
brooke2k · 14m 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.
pdimitar · 8h 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.
tialaramex · 2h 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.
wahern · 6h ago
Dealing with integer overflow is much more burdensome than dealing with allocation failure, IME. Relatively speaking, allocation failure is closer to file descriptor limits in terms of how it effects code structure. But then I mostly use C when I'm not using a scripting language. In languages like Rust and C++ there's alot of hidden allocation in the high-level libraries that seem to be popular, perhaps because the notion that "there's nothing you can do" has infected too many minds.
Of course, just like with opening files or integer arithmetic, if you don't pay any attention to handling the errors up front when writing your code, it can be an onerous if not impossible to task to refactor things after the fact.
pdimitar · 6h ago
Oh I agree, don't get me wrong. Both are pretty gnarly.
I was approaching these problems strictly from the point of view of what can Rust do today really, nothing else. To me having checked and non-panicking API for integer overflows / underflows at least gives you some agency.
If you don't have memory, well, usually you are cooked. Though one area where Rust can become even better there is to give us some API to reserve more memory upfront, maybe? Or I don't know, maybe adopt some of the memory-arena crates in stdlib.
But yeah, agreed. Not the types of problems I want to have anymore (because I did have them in the past).
arijun · 9h 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.
josephg · 5h 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.
nicce · 9h ago
I would say that Segmentation Fault is better comparison with C++ :-D
marcosdumay · 8h 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.
not sure what the latest is in the space, if I recall there are some subtleties
zozbot234 · 9h ago
That's a neat hack, but it would be a lot nicer to have explicit support as part of the language.
kbolino · 8h ago
That's going to be difficult because the language itself requires panic support to properly implement indexing, slicing, and integer division. There are checked methods that can be used instead, but to truly eliminate panics, the ordinary operators would have to be banned when used with non-const arguments, and this restriction would have to propagate to all dependencies as well.
josephg · 5h ago
Yes that’s right. The feature really wants compiler support for that reason. The simplest version wouldn’t be too hard to implement. Every function just exports a flag on whether or not it (or any callees) can panic. Then we have a nopanic keyword which emits a compiler error if the function (or any callee) panics.
It would be annoying to use - as you say, you couldn’t even add regular numbers together or index into an array in nopanic code. But there are ways to work around it (like the wrapping types).
One problem is that implicit nopanic would add a new way to break semver compatibility in APIs. Eg, imagine a public api that just happens to not be able to panic. If the code is changed subtly, it could easily start panicing again. That could break callers, so it has to be a major version bump. You’d probably have to require explicit nopanic at api boundaries. (Else assume all public functions from other crates can panic). And because of that, public APIs like std would need to be plastered with nopanic markers everywhere. It’s also not clear how that works through trait impls.
nicce · 8h 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?
johnisgood · 4h ago
I do not want a library to panic though, I want to handle the error myself.
dvt · 9h ago
Maybe contrarian, but imo the `Result` type, while kind of nice, still suffers from plenty of annoyances, including sometimes not working with the (manpages-approved) `dyn Error`, sometimes having to `into()` weird library errors that don't propagate properly, or worse: `map_err()` them; I mean, at this point, the `anyhow` crate is basically mandatory from an ergonomics standpoint in every Rust project I start. Also, `?` doesn't work in closures, etc.
So, while this is an improvement over C++ (and that is not saying much at all), it's still implemented in a pretty clumsy way.
singingboyo · 8h 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.
josephg · 4h 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:
> 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#.
dwattttt · 18m 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.
jbritton · 8h 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.
XorNot · 5h 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.
Yoric · 7h 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.
efnx · 8h ago
Yes. I prefer ‘snafu’ but there are a few, and you could always roll your own.
smj-edison · 7h ago
+1 for snafu. It lets you blend anyhow style errors for application code with precise errors for library code. .context/.with_context is also a lovely way to propagate errors between different Result types.
bonzini · 7h ago
How does that compare to "this error for libraries and anyhow for applications"?
smj-edison · 7h ago
You don't have to keep converting between error types :)
shepmaster · 8h ago
Yeah, with SNAFU I try to encourage people going all-in on very fine-grained error types. I love it (unsurprisingly).
maplant · 9h 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<_>>
No comments yet
skrtskrt · 6h ago
A couple of those annoyances are just library developers being too lazy to give informative error types which is far from a Rust-specific problem
mdf · 9h 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.
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.
kccqzy · 8h ago
The Result type isn't really enough for fun and easy error handling. I usually also need to reach for libraries like anyhow https://docs.rs/anyhow/latest/anyhow/. Otherwise, you still need to think about the different error types returned by different libraries.
Back at Google, it was truly an error handling nirvana because they had StatusOr which makes sure that the error type is just Status, a standardized company-wide type that stills allows significant custom errors that map to standardized error categories.
jasonjmcghee · 9h ago
unfortunately it's not so simple. that's the convention. depending on the library you're using it might be a special type of Error, or special type of Result, something needs to be transformed, `?` might not work in that case (unless you transform/map it), etc.
I like rust, but its not as clean in practice, as you describe
ryandv · 8h 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:
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:
You can use anyhow::Result, and the ? will work for any Error.
loeg · 5h 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.
fpoling · 8h ago
Result type still requires quite a few lines of boilerplate if one needs to add custom data to it. And as a replacement of exceptions with automatic stack trace attachment it is relatively poor.
In any case I will take Rust Result over C++ mess at any time especially given that we have two C++, one with exception support and one without making code incompatible between two.
jandrewrogers · 1h 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.
0x1ceb00da · 8h 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.
dabinat · 9h 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.
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.
vjerancrnjak · 9h 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.
ryandrake · 7h 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.
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.
tubs · 9h ago
And panics?
90s_dev · 9h ago
I like so much about Rust.
But I hear compiling is too slow.
Is it a serious problem in practice?
juliangmp · 8h 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.
Seattle3503 · 9h 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.
nicoburns · 6h 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.
Seattle3503 · 4h ago
Yes, this is often the best "low-hanging fruit" option, but it can get expensive. It depends how you value your developer time.
lilyball · 8h 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.
Seattle3503 · 8h ago
Every project I've worked on used a workspace with many crates. Generally that only gets you so far on large projects.
mixmastamyk · 8h ago
It compiles different files separately, right?
With some exceptions for core data structures, it seems that if you only modified a few files in a large project the total compilation time would be quick no matter how slow the compiler was.
conradludgate · 8h ago
Sorta. The "compilation unit" is a single crate, but rustc is now also parallel, and LLVM can also be configured to run in parallel IIRC.
Rust compile times have been improving over time as the compiler gets incrementally rewritten and optimised.
conradludgate · 8h 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.
mynameisash · 9h 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.
throwaway76455 · 6h 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.
kelnos · 8h ago
Compilation is indeed slow, and I do find it frustrating sometimes, but all the other benefits Rust brings more than make up for it in my book.
zozbot234 · 9h 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/
creata · 8h ago
Or maybe they have experienced what it was like and they don't want to go back.
kelnos · 8h 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?
cmrdporcupine · 9h 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.
gpderetta · 6h ago
Are there rust projects that are within orders of magnitude of Chromium?
cmrdporcupine · 9h 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)
Ok, I'm at like 0 knowledge on the Rust side, so bear that in mind. Also, to note that I'm genuinely curious about this answer.
Why can't I return an integer on error? What's preventing me from writing Rust like C++?
tczMUFlmoNk · 7h 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.
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
thrwyexecbrain · 8h ago
The C++ code I write these days is actually pretty similar to Rust: everything is explicit, lots of strong types, very simple and clear lifetimes (arenas, pools), non-owning handles instead of pointers. The only difference in practice is that the build systems are different and that the Rust compiler is more helpful (both in catching bugs and reporting errors). Neither a huge deal if you have a proper build and testing setup and when everybody on your team is pretty experienced.
By the way, using "atoi" in a code snippet in 2025 and complaining that it is "not ideal" is, well, not ideal.
mountainriver · 4h ago
I still find it basically impossible to get started with a C++ project.
I tried again recently for a proxy I was writing thinking surely things have evolved at this point. Every single package manager couldn’t handle my very basic and very popular dependencies. I mean I tried every single one. This is completely insane to me.
Not to mention just figuring out how to build it after that which was a massive headache and an ongoing one.
Compared to Rust it’s just night and day.
Outside of embedded programming or some special use cases I have literally no idea why anyone would ever write C++. I’m convinced it’s a bunch of masochists
morsecodist · 35m ago
Agreed. I have had almost the same experience. The package management and building alone makes Rust worth it for me.
runevault · 1h ago
When I've dabbled in C++ recently it has felt like using CMake fetching github repos has been the least painful thing I've tried (dabbled in vcpkg and conan a bit), since most libraries are cmake projects.
I am no expert so take it with a grain of salt, but that was how it felt for me.
almostgotcaught · 1h 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.
cratermoon · 1h 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?
const_cast · 9m ago
Package managers per language are a (relatively) new endeavor. The oldest language I can think of that widely adopted it was Perl. Although, perl was quite ahead of it's time in a lot of ways, and php undid some the work of perl and went back to popularizing include type dependencies instead of formal modules with a package manager.
C++ "gets away" with it because of templates. Many (most?) libraries are mostly templates, or at the very least contain templates. So you're forced into include-style dependencies and it's pretty painless. For a good library, it's often downloading a single file and just #include-ing it.
C++ is getting modules now, and maybe that will spur a new interest in package managers. Or maybe not, it might be too late.
almostgotcaught · 18m ago
Do you people really not realize how completely asinine you sound with these lowbrow comments? I'll give you a hint: did you know that C also has no package manager?
adgjlsfhk1 · 3m ago
Yeah, and it's also much worse for it. There's a reason everyone in C uses their own linked list implementation and it's not because it's a platonic ideal of perfect software.
hlpn · 3m ago
is this a troll account?
kanbankaren · 6h 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.
taylorallred · 7h ago
Cool that you're using areas/pools for lifetimes. Are you also using custom data structures or stl (out of curiosity)?
yodsanklai · 7h ago
> The C++ code I write these days
Meaning you're in a context where you have control on the C++ code you get to write. In my company, lots of people get to update code without strict guidelines. As a result, the code is going to be complex. I'd rather have a simpler and more restrictive language and I'll always favor Rust projects to C++ ones.
bluGill · 6h 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.
timbit42 · 5h ago
How many rough edges will C++ have in another 30 years?
jpc0 · 9h ago
Amazing example of how easy it is to get sucked into the rust love. Really sincerely these are really annoying parts of C++.
The conversation function is more language issue. I don’t think there is a simple way of creating a rust equivalent version because C++ has implicit conversions. You could probably create a C++ style turbofish though, parse<uint32_t>([your string]) and have it throw or return std::expected. But you would need to implement that yourself, unless there is some stdlib version I don’t know of.
Don’t conflate language features with library features.
And -Wconversion might be useful for this but I haven’t personally tried it since what Matt is describing with explicit types is the accepted best practice.
ujkiolp · 8h ago
meh, rust is still better cos it’s friendlier
jpc0 · 8h 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++.
noelnh · 5h 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.
jpc0 · 2h ago
Rust libraries tend to over abstract and then need large refactors when those abstractions fall apart. When I’ve complained about it in the past I’ve been met with “You would need the abstraction eventually”. Maybe, but I’m also capable of building it myself it it gets to that.
Maybe that’s more of a bias with rust media stuff, seems to be going deeper into that rabbit hole though.
The community was at least, may still be, very sensitive to rust being criticised. I genuinely brought an example of a provably correct piece of code that the borrow checker wouldn’t accept, interior mutability problem. I was I should build a massive abstraction to avoid the problem and that I’m holding it wrong… Put me off the language for a few years, it shouldn’t have, I should have just ignored the people and continued on but we all get older and learn things.
bigstrat2003 · 3h ago
The Rust community is helpful... but also quite political and extremely hostile to anyone who doesn't share those politics. Even something as anodyne as saying "let's keep politics out of technical discussion" is frequently met with hostility (because many community members believe that tech is inherently political and that trying to keep politics out is really just a bad faith attempt to frame things in terms of the requester's preferred politics). It's also full of drama in a way that other communities online simply aren't. For example, the drama that happened when the guy behind thephd.dev got invited to give the keynote at rustconf, then his talk was downgraded from the keynote - everyone involved in that mess (including other bloggers who weighed in) came off as immature and not someone you would ever want to work with.
I like the Rust language quite a bit. I find the Rust community to be one of the most toxic places in the entire tech business. Your mileage may vary and that's fine of course - but plenty of people want to stay far away from a community that acts like the Rust community does.
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.
kasajian · 8h 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.
lytedev · 8h 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!
sdenton4 · 8h 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.
cbsmith · 8h 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.
Etheryte · 8h 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.
Maxatar · 3h ago
The Sapir Whorf hypothesis has long been debunked:
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.
LinXitoW · 6h 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?
cbsmith · 8h ago
Yeah, I kept thinking, "doesn't mp-units basically address this entirely"?
GardenLetter27 · 9h 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.
dcdgo · 21m 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....
frankus · 9h 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.
jsat · 8h ago
Had the same thought... It's backwards that any language isn't using named parameters at this point.
Ygg2 · 7h ago
Named parameters do come with a large footgun. Renaming your parameters is a breaking change.
Especially if you're coming from different langs.
const_cast · 6m 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.
shpongled · 9h ago
Yep, I would love anonymous record types, ala StandardML/OCaml
grumbel · 9h 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'.
Arnavion · 9h ago
The reason to introduce the Quantity wrapper is to not be able to swap the quantity and price arguments.
b5n · 9h 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.
kelnos · 8h 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!).
b5n · 7h 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.
Arnavion · 5h ago
To be clear, the only difference between Rust and C here is whether the conversion happens by default or not. Rust doesn't do the conversion by default but will let you do it if you want to, with `as`.
There are also more type-safe conversion methods that perform a more focused conversion. Eg a widening conversion from i8 -> i16 can be done with .into(), a narrowing conversion from i16 -> i8 can be done with .try_into() (which returns a Result and forces you to handle the overflow case), a signed to unsigned reinterpretation like i64 -> u64 can be done with .cast_unsigned(), and so on. Unlike `as` these have the advantage that they stop compiling if the original value changes type; eg if you refactor something and the i8 in the first example becomes an i32, the i32 -> i16 conversion is no longer a widening conversion so the `.into()` will fail to compile.
jpc0 · 8h 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.
throwaway76455 · 6h 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.
xarope · 55m 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)
nailer · 5m 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.
writebetterc · 10h 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.
titzer · 9h 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.
plus · 9h 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).
mananaysiempre · 9h 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.
titzer · 5h 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).
titzer · 5h ago
Yep, Virgil only implicitly promotes integers to float when rounding won't change the value.
This also applies to casts, which are dynamically checked.
// runtime error if rounding alters value
def x5: i24;
def f5: float = float.!(x5);
dzaima · 50m 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.
roelschroeven · 5h 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.
im3w1l · 5h 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.
thesuperbigfrog · 5h ago
The idiomatic Rust way to do conversions is using the From and TryFrom traits:
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.
im3w1l · 4h ago
Thanks yeah I probably misremembered or misunderstood.
favorited · 10h ago
Side note, if anyone is interested in hearing more from Matt, he has a programming podcast with Ben Rady called Two's Complement.
+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.
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
jsat · 8h 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.
kelnos · 8h 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.
codedokode · 8h ago
When there are 3-4 parameters it is too much trouble to write the names.
noitpmeder · 6h ago
Not OP, but I imagine he's arguing for something like python's optional named arguments.
bsder · 6h 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.
sophacles · 6h 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.
simpaticoder · 5h 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.
brundolf · 4h 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
simpaticoder · 4h 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.
siev · 1h ago
Rust also has the standard NonZero<T> type for that use case.
morning-coffee · 9h ago
Reading "The Rust Book" sold me on Rust (after programming in C++ for over 20 years)
mixmastamyk · 8h ago
Am about finished, but several chapters near the end seriously put me to sleep. Will try again some other day I suppose.
markus_zhang · 10h 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.
LorenDB · 10h ago
Walter Bright will probably show up soon to plug D's BetterC mode, but if he doesn't, still check it out.
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.
o11c · 9h 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.
wffurr · 10h ago
This seems like such an obvious thing to have - where is it? Zig, Odin, etc. all seem much more ambitious.
> 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.
IshKebab · 9h 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.
zyedidia · 9h 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.).
Zambyte · 7h 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.
mamcx · 9h 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.
nitwit005 · 8h 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.
Certhas · 7h ago
Maybe just unsafe rust?
dlachausse · 9h 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.
kelnos · 7h 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.
hmry · 6h 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.
neonsunset · 5h 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.
> 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).
smt88 · 9h 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.
dlachausse · 9h 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.
alexchamberlain · 10h ago
I'm inferring that you think Rust adds too much brain drain? If so, what?
> 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!
leonheld · 9h 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.
adamc · 9h 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.
rq1 · 9h 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.
jpc0 · 8h 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.
rq1 · 7h 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)
jpc0 · 2h ago
My point is a Width type is usually not the sound type you are looking for. What probably wanted was a size type which is width and height. Or a dimensions type which is width and height. The problem was maybe not two arguments being confused but in reality a single thing with two elements…
antirez · 7h 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.
tumdum_ · 8h ago
The one thing that sold me on Rust was that I no longer had to chase down heisenbugs caused by memory corruption.
nyanpasu64 · 5h 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.
What sold me on Rust is that I'm a very bad programmer and I make a lot of mistakes. Given C++, I can't help but hold things wrong and shoot myself in the foot. My media C++ coding session is me writing code, getting a segfault immediately, and then spending time chasing down the reason for that happening, rinse and repeat.
My median Rust coding session isn't much different, I also write code that doesn't work, but it's caught by the compiler. Now, most people call this "fighting with the borrow checker" but I call it "avoiding segfaults before they happen" because when I finally get through the compiler my code usually "just works". It's that magical property Haskell has, Rust also has it to a large extent.
So then what's different about Rust vs. C++? Well Rust actually provides me a path to get to a working program whereas C++ just leaves me with an error message and a treasure map.
What this means is that although I'm a bad programmer, Rust gives me the support I need to build quite large programs on my own. And that extends to the crate ecosystem as well, where they make it very easy to build and link third party libraries, whereas with C++ ld just tells you that it had a problem and you're left on your own to figure out exactly what.
jpc0 · 8h ago
Using your media example since I have a decent amount of experience there. Did you just use off the shelf libraries, because effectively all the libraries are written in or expose a C api. So now you not only need to deal with Rust, you need to also deal with rust ffi.
There are some places I won’t be excited to use rust, and media heavy code is one of those places…
sophacles · 6h ago
Given that the second paragraph starts with "my median rust..." i assume the "media C++" is actually a typo for "median C++".
kanbankaren · 6h ago
> Given C++, I can't help but hold things wrong and shoot myself
Give an example. I have been programming in C/C++ for close to 30 years and the places where I worked had very strict guidelines on C++ usage. We could count the number of times we shot ourselves due to the language.
mb7733 · 5h 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.
kanbankaren · 4h 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.
zaphar · 3h ago
And yet, Rust doesn't sacrifice performance and it has all kinds of those guardrails.
Wow that guy, eh? He seems to turn up everywhere :D
gbin · 8h ago
Interestingly in Rust I would immediately use an Enum for the Order! Way more powerful semantically.
hacker_homie · 9h ago
Wconversion And werror?
spyrja · 6h 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:
Rust of course leaves "less footguns laying around", but I still prefer to use C++ if I have my druthers.
mempko · 8h 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.
atemerev · 9h 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.
kelnos · 7h 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++.
atemerev · 6h 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.
0x1ceb00da · 8h ago
> Borrow checker killed me.
You gotta get your timing right. Right hook followed by kidney shot works every time.
sunshowers · 9h ago
I apologize for the naive question, but that sounds like a heap?
jpc0 · 8h 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…
bigstrat2003 · 8h 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.
(filled with boilerplate, strange Rust idioms, borrow_unchecked, phantomdata, and you still have to manage lifetimes annotations).
bigstrat2003 · 4h ago
And? I don't really see the issue. It works, it is sound, and it has a nice clean interface for safe code to use. That's all I really ask for. Lots of useful things in programming are quite gnarly under the hood, but that doesn't mean those things aren't worth using.
sunshowers · 7h ago
I agree in general Rust makes you use arrays and indexes, but heaps are traditionally implemented that way in any language.
atemerev · 6h 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.
sunshowers · 5h 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.
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.
lytedev · 8h 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.
skippyboxedhero · 8h 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).
lytedev · 7h ago
The idea with rust is that you _can_ have safety with no performance cost if you need it, but depending on what you're building, of course, that may imply extra work.
The pragmatism of Rust means that you can use reference counting if it suits your use case.
Unsafe also doesn't mean throwing out the Rustiness of Rust, but others have written more extensively about that and I have no personal experience with it.
> 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.
My understanding is that this is the purpose of unsafe, but again, I can't argue against these points from a standpoint of experience, having stuck pretty strictly to safe Rust.
Definitely agree that there are issues with the language, no argument there! So do the maintainers!
> if I need to manage memory then I would look elsewhere atm
Haha I have the exact opposite feeling! I wouldn't try to manage memory any other way, and I'm guessing it's because memory management is more intuitive and well understood by you than by me. I'm lazy and very much like having the compiler do the bulk of the thinking for me. I'm also happy that Rust allows for folks like me to pay a little performance cost and do things a little bit easier while maintaining correctness. For the turbo-coders out there that want the speed and the correctness, Rust has the capability, but depending on your use case (like linked lists) it can definitely be more difficult to express correctness to the compiler.
skippyboxedhero · 7h ago
Agree, that is the purpose of unsafe but there is a degree of irrationality there, which I am guilty of, about using unsafe in Rust. I also worry about unsafe leaking if I am using raw pointer on a struct...but stdlib uses a lot of unsafe code, so I should be too.
I think the issue that people have is that they come into Rust with the expectation that these problems are actually solved. As I said, it would be nice if lifetimes weren't so impossible to use.
The compiler isn't doing the thinking if you have to change your code so the compiler is happy. The problem with Rust is too much thinking: you try something, compiler complains, what is the issue here, can i try this, still complain, what about this, etc. There are specific categories of bugs that Rust is trying to fix that don't require the changes that Rust requires in order to ensure correctness...if you use reference counter, you can have more bugs.
codedokode · 8h ago
What about catching integer overflow? Free open-source languages still cannot do it unlike they commercial competitors like Swift?
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.
trealira · 7h ago
But, by default, normal arithmetic operations trap on overflow in debug mode, although they wrap with optimizations on.
lytedev · 8h ago
I'm not sure if this is what you mean, exactly, but Rust indeed catches this at compile time.
I meant panic if during any addition (including in runtime) an overflow occurs.
genrilz · 7h 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].
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".
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.
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
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.
There really is no reasonable workaround here, the language needs to be amended to make this safe and ergonomic. They tried to be cheeky with some of the other APIs, like std::variant, but really the best you can do is chuck the conditional branch into a lambda (or other function-based implementation of visitors) and the ergonomics of that are pretty unimpressive.
Edit: but maybe fortune will change in the future, for anyone who still cares:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p26...
This is one odd the major reasons I switched to rust, just to escape spending my whole life worrying about bugs caused by UB.
I think the issue is that this just isn't particularly good either. If you do that, then you can't catch it like an exception, but you also can't statically verify that it won't happen.
C++ needs less of both undefined behavior and runtime errors. It needs more compile-time errors. It needs pattern matching.
(Going to moan for a bit, and I realise you aren’t responsible for the C++ standards mess!)
I have been hearing for about… 20 years now that UB gives compilers and tools the freedom to produce any error catching they like, but all it seems to have done in the main is give them the freedom to produce hard to debug crash code.
You can of course usually turn on some kind of “debug mode” in some compilers, but why not just enforce that as standard? Compilers would still be free to add a “standards non-compliant” go fast mode if they like.
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").
However it seems like C++ wants to only provide this kind of pattern via monadic operations.
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.
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.
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.
you could have a panic though, if you wrongly make assumptions
Panic is absolutely fine for bugs, and it's indeed what should happen when code is buggy. That's because buggy code can make absolutely no guarantees on whether it is okay to continue (arbitrary data structures may be corrupted for instance)
Indeed it's hard to "treat an error" when the error means code is buggy. Because you can rarely do anything meaningful about that.
This is of course a problem for code that can't be interrupted.. which include the Linux kernel (they note the bug, but continue anyway) and embedded systems.
Note that if panic=unwind you have the opportunity to catch the panic. This is usually done by systems that process multiple unrelated requests in the same program: in this case it's okay if only one such request will be aborted (in HTTP, it would return a 5xx error), provided you manually verify that no data structure shared by requests would possibly get corrupted. If you do one thread per request, Rust does this automatically; if you have a smaller threadpool with an async runtime, then the runtime need to catch panics for this to work.
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.
I will fight against program aborts as hard as I possibly can. I don't mind boilerplate to be the price paid and will provide detailed error messages even in such obscure error branches.
Again, speaking only for myself. My philosophy is: the program is no good for me dead.
That may be true, but the program may actually be bad for you if it does something unexpected due to an unforeseen state.
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.
Rust picked the right default for applications that run in an OS whereas Zig picked the right default for embedded. Both are good for their respective domains, neither is good at both domains. Zig's choice is verbose and useless on a typical desktop OS, especially with overcommit, whereas Rust's choice is problematic for embedded where things just work differently.
My current $dayjob involves a "server" application that needs to run in a strict memory limit. We had to write our own allocator and collections because the default ones' insistence on using GlobalAlloc infallibly doesn't work for us.
Thinking that only "embedded" cares about custom allocators is just naive.
I partially disagree with this. Using Zig style allocators doesn't really fit with Rust ergonomics, as it would require pretty extensive lifetime annotations. With no_std, you absolutely can roll your own allocation styles, at the price of more manual lifetime annotations.
I do hope though that some library comes along that allows for Zig style collections, with the associated lifetimes... (It's been a bit painful rolling my own local allocator for audio processing).
As long as the type is generic on the allocator, the lifetimes of the allocator don't appear in the type. So eg if your allocator is using a stack array in main then your allocator happens to be backed by `&'a [MaybeUninit<u8>]`, but things like Vec<T, A> instantiated with A = YourAllocator<'a> don't need to be concerned with 'a themselves.
Eg: https://play.rust-lang.org/?version=nightly&mode=debug&editi... do_something_with doesn't need to have any lifetimes from the allocator.
If by Zig-style allocators you specifically mean type-erased allocators, as a way to not have to parameterize everything on A:Allocator, then yes the equivalent in Rust would be a &'a dyn Allocator that has an infectious 'a lifetime parameter instead. Given the choice between an infectious type parameter and infectious lifetime parameter I'd take the former.
I guess all that to say, I agree then that this should've been in std from day one.
Odin has them, too, optionally (and usually).
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.
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.
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...
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.
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.
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.
Of course, just like with opening files or integer arithmetic, if you don't pay any attention to handling the errors up front when writing your code, it can be an onerous if not impossible to task to refactor things after the fact.
I was approaching these problems strictly from the point of view of what can Rust do today really, nothing else. To me having checked and non-panicking API for integer overflows / underflows at least gives you some agency.
If you don't have memory, well, usually you are cooked. Though one area where Rust can become even better there is to give us some API to reserve more memory upfront, maybe? Or I don't know, maybe adopt some of the memory-arena crates in stdlib.
But yeah, agreed. Not the types of problems I want to have anymore (because I did have them in the past).
Also there is the no_panic crate, which uses macros to require the compiler to prove that a given function cannot panic.
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.
But on those places, you better know exactly what you are doing.
not sure what the latest is in the space, if I recall there are some subtleties
It would be annoying to use - as you say, you couldn’t even add regular numbers together or index into an array in nopanic code. But there are ways to work around it (like the wrapping types).
One problem is that implicit nopanic would add a new way to break semver compatibility in APIs. Eg, imagine a public api that just happens to not be able to panic. If the code is changed subtly, it could easily start panicing again. That could break callers, so it has to be a major version bump. You’d probably have to require explicit nopanic at api boundaries. (Else assume all public functions from other crates can panic). And because of that, public APIs like std would need to be plastered with nopanic markers everywhere. It’s also not clear how that works through trait impls.
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.
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.
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...
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#.
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.
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.
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.
No comments yet
[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
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.
I like rust, but its not as clean in practice, as you describe
Then implement the `From` trait to map errors from third-party libraries to your own custom Error space:
Now you can convert any result into a single type that you control by transforming the errors: There is a little boilerplate and mapping between error spaces that is required but I don't find it that onerous.I would rather have what OCaml has: https://ocaml.org/docs/error-handling.
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.
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.
I use completely custom error handling stacks in C++ and they are quite slick these days, thanks to improvements in the language.
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
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.
Just need a function that allows lifting option to result.
1: https://news.ycombinator.com/item?id=41543183
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/...
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.
But I hear compiling is too slow.
Is it a serious problem in practice?
Like with any bigger C++ project there's like 3 build tools, two different packaging systems and likely one or even multiple code generators.
Besides developer productivity it can be an issue when you need a critical fix to go out quickly and your pipelines take 60+ minutes.
With some exceptions for core data structures, it seems that if you only modified a few files in a large project the total compilation time would be quick no matter how slow the compiler was.
Rust compile times have been improving over time as the compiler gets incrementally rewritten and optimised.
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.
Is it a serious problem? I'd say 'no', but YMMV.
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.
https://github.com/abseil/abseil-cpp/blob/master/absl/status...
Why can't I return an integer on error? What's preventing me from writing Rust like C++?
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...
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
By the way, using "atoi" in a code snippet in 2025 and complaining that it is "not ideal" is, well, not ideal.
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
I am no expert so take it with a grain of salt, but that was how it felt for me.
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.
C++ "gets away" with it because of templates. Many (most?) libraries are mostly templates, or at the very least contain templates. So you're forced into include-style dependencies and it's pretty painless. For a good library, it's often downloading a single file and just #include-ing it.
C++ is getting modules now, and maybe that will spur a new interest in package managers. Or maybe not, it might be too late.
Modern C++ has reduced a lot of typing through type inference, but otherwise the language is still strongly typed and essentially the same.
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.
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.
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.
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++.
Maybe that’s more of a bias with rust media stuff, seems to be going deeper into that rabbit hole though.
The community was at least, may still be, very sensitive to rust being criticised. I genuinely brought an example of a provably correct piece of code that the borrow checker wouldn’t accept, interior mutability problem. I was I should build a massive abstraction to avoid the problem and that I’m holding it wrong… Put me off the language for a few years, it shouldn’t have, I should have just ignored the people and continued on but we all get older and learn things.
I like the Rust language quite a bit. I find the Rust community to be one of the most toxic places in the entire tech business. Your mileage may vary and that's fine of course - but plenty of people want to stay far away from a community that acts like the Rust community does.
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.
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!
https://en.m.wikipedia.org/wiki/Linguistic_relativity
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.
And while we're at it, why not use assembly? It's all just "syntactic sugar" over bits, doesn't make any difference, right?
Especially if you're coming from different langs.
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'.
Additionally, `clang-tidy` catches this via `bugprone-narrowing-conversions` and your linter will alert if properly configured.
I do run clippy on my Rust projects, but that's a matter of style and readability, not correctness (for the most part!).
I appreciate that there are guardrails in a tool like rust, I also appreciate that sharp tools like c exist, they both have advantages.
There are also more type-safe conversion methods that perform a more focused conversion. Eg a widening conversion from i8 -> i16 can be done with .into(), a narrowing conversion from i16 -> i8 can be done with .try_into() (which returns a Result and forces you to handle the overflow case), a signed to unsigned reinterpretation like i64 -> u64 can be done with .cast_unsigned(), and so on. Unlike `as` these have the advantage that they stop compiling if the original value changes type; eg if you refactor something and the i8 in the first example becomes an i32, the i32 -> i16 conversion is no longer a widening conversion so the `.into()` will fail to compile.
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.
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)
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.
I love Rust, but this is artificial.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.
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.
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.
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.
https://www.twoscomplement.org
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
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.
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.
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
So no implicit type conversions, safer strings, etc.
https://dlang.org/spec/betterc.html
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.
* "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.
> 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.
The core of Rust is actually very simple: Struct, Enum, Functions, Traits.
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.
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.
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).
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.
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!
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.
Even Rust's types aren't going to help you if two arguments simply have the same types.
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.
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)
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.
There are some places I won’t be excited to use rust, and media heavy code is one of those places…
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.
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.
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.
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++.
You gotta get your timing right. Right hook followed by kidney shot works every time.
(filled with boilerplate, strange Rust idioms, borrow_unchecked, phantomdata, and you still have to manage lifetimes annotations).
[1] https://doc.rust-lang.org/std/cmp/struct.Reverse.html
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.
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).
The pragmatism of Rust means that you can use reference counting if it suits your use case.
Unsafe also doesn't mean throwing out the Rustiness of Rust, but others have written more extensively about that and I have no personal experience with it.
> 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.
My understanding is that this is the purpose of unsafe, but again, I can't argue against these points from a standpoint of experience, having stuck pretty strictly to safe Rust.
Definitely agree that there are issues with the language, no argument there! So do the maintainers!
> if I need to manage memory then I would look elsewhere atm
Haha I have the exact opposite feeling! I wouldn't try to manage memory any other way, and I'm guessing it's because memory management is more intuitive and well understood by you than by me. I'm lazy and very much like having the compiler do the bulk of the thinking for me. I'm also happy that Rust allows for folks like me to pay a little performance cost and do things a little bit easier while maintaining correctness. For the turbo-coders out there that want the speed and the correctness, Rust has the capability, but depending on your use case (like linked lists) it can definitely be more difficult to express correctness to the compiler.
I think the issue that people have is that they come into Rust with the expectation that these problems are actually solved. As I said, it would be nice if lifetimes weren't so impossible to use.
The compiler isn't doing the thinking if you have to change your code so the compiler is happy. The problem with Rust is too much thinking: you try something, compiler complains, what is the issue here, can i try this, still complain, what about this, etc. There are specific categories of bugs that Rust is trying to fix that don't require the changes that Rust requires in order to ensure correctness...if you use reference counter, you can have more bugs.
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/
https://play.rust-lang.org/?version=stable&mode=debug&editio... https://play.rust-lang.org/?version=stable&mode=debug&editio...
By default, they're on during debug mode and off in release mode.