I find it strange that the article doesn't talk about the alternative to checked arithmetic: explicit Wrapping [0] and Saturating [1] types, also provided as methods on numeric types (e.g. `usize::MAX.saturating_add(1)`). Regarding `as` casting, I completely agree. I am trying to use safe `From::from` instead. However, this is a bit noisy: `usize::from(n)` vs `n as usize`. [0] https://doc.rust-lang.org/std/num/struct…
> I am trying to use safe `From::from` instead. However, this is a bit noisy: `usize::from(n)` vs `n as usize`. If there's enough information in the surrounding code for type inference to do its thing, you can shorten it to `n.into()`.
Pitfalls of Safe Rust
131–140 of 145 posts
Re: Pitfalls of Safe Rust
#132Earlier quoted context omitted.
I'd prefer for Rust to opt for correctness/bug-freeness over performance, even in release builds. If you are doing number crunching you should have to opt out of these checks.
> I'd prefer for Rust to opt for correctness/bug-freeness over performance, even in release builds. If you are doing number crunching you should have to opt out of these checks. You can turn those checks on, in release mode, of course: https://doc.rust-lang.org/rustc/codegen-options/index.html#o... But I think the behavior on overflow is to "panic!()" (terminate immediately)? So -- I guess from my POV I wouldn't in r…
Re: Pitfalls of Safe Rust
#133Earlier quoted context omitted.
> I'd prefer for Rust to opt for correctness/bug-freeness over performance, even in release builds. If you are doing number crunching you should have to opt out of these checks. You can turn those checks on, in release mode, of course: https://doc.rust-lang.org/rustc/codegen-options/index.html#o... But I think the behavior on overflow is to "panic!()" (terminate immediately)? So -- I guess from my POV I wouldn't in r…
panics do not terminate immediately; they unwind the stack, and if they’re not caught, they terminate the current thread, not the process.
I don't disagree though this point is a little pedantic. I suppose the docs also need an update? See: https://doc.rust-lang.org/std/macro.panic.html
"This allows a *program to terminate immediately* and provide feedback to the caller of the program."
Now, I don't think so, because program death is usually what this type of panic means.And my point remains, without more, this probably isn't the behavior one wants in release mode. But, yes, also perhaps an even better behavior is turning on checks, catching the panic, and logging it with others.
Re: Pitfalls of Safe Rust
#134Earlier quoted context omitted.
panics do not terminate immediately; they unwind the stack, and if they’re not caught, they terminate the current thread, not the process.
> panics do not terminate immediately; they unwind the stack, and if they’re not caught, they terminate the current thread, not the process. I don't disagree though this point is a little pedantic. I suppose the docs also need an update? See: https://doc.rust-lang.org/std/macro.panic.html "This allows a *program to terminate immediately* and provide feedback to the caller of the program." Now, I don't think so, becau…
Re: Pitfalls of Safe Rust
#135For integer overflows and array out of bounds I'm quite optimistic about Flux https://github.com/flux-rs/flux I haven't actually used it but I do have experience of refinement types / liquid types (don't ask me about the nomenclature) and IMO they occupy a very nice space just before you get to "proper" formal verification and having to deal with loop invariants and all of that complexity.
There's a nice FOSDEM presentation "Understanding liquid types, contracts and formal verification with Ada/SPARK" by Fernando Oleo Blanco (Irvise): https://fosdem.org/2025/schedule/event/fosdem-2025-4879-unde.... One of the slides says:
Liquid types!
Logically Qualified Types, aka, Types with Logic! Aka dependent types, etc...Re: Pitfalls of Safe Rust
#136Earlier quoted context omitted.
There is, since the zero is used as a niche value optimisation for enums, so that Option > occupies the same amount of memory as u32. But this can be used with other enums too, and in those cases, having a zero NonZero would essentially transmute the enum into an unexpected variant, which may cause an invariant to break, thus potentially causing memory unsafety in whatever required that invariant.
> which may cause an invariant to break, thus potentially causing memory unsafety in whatever required that invariant By that standard anything and everything might be tainted as "unsafe", which is precisely GP's point. Whether the unsafety should be blamed on the outside code that's allowed to create a 0-valued NonZero or on the code that requires this purported invariant in the first place is ultimately a matter of…
It's not, though. NonZero has an invariant that a zero value is undefined behavior. Therefore, any API which allows for the ability to create one must be unsafe. This is a very straightforward case.
Re: Pitfalls of Safe Rust
#137Earlier quoted context omitted.
No, the Rust community almost universally understands "safe" as referring to memory safety, as per Rust's documentation, and especially the unsafe book, aka Rustonomicon [1]. In that regard, Safe Rust is safe, Unsafe Rust is unsafe, and C++ is also unsafe. I don't think anyone is saying "C++ is all unsafe." You might be talking about "correct", and that's true, Rust generally favors correctness more than most other l…
If a C++ developer decides to use purely containers and smart pointers when starting a new project, how are they going to develop unsafe code? Containers like std::vector and smart pointers like std::unique_ptr seem to offer all of the same statically checked guarantees that Rust does. I just do not see how Rust is a superior language compared to modern C++
#include
#include
int main() {
std::unique_ptr null_ptr;
std::cout
Clang 20 compiles this code with `-std=c++23 -Wall -Werror`. If you add -fsanitize=undefined, it will print ==1==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x55589736d8ea bp 0x7ffe04a94920 sp 0x7ffe04a948d0 T1)
or similar.Re: Pitfalls of Safe Rust
#138Earlier quoted context omitted.
Unfortunately, operator[] on std::vector is inherently unsafe. You can potentially try to ban it (using at() instead), but that has its own problems. There’s a great talk by Louis Brandy called “Curiously Recurring C++ Bugs at Facebook” [0] that covers this really well, along with std::map’s operator[] and some more tricky bugs. An interesting question to ask if you try to watch that talk is: How does Rust design aro…
Thank you for sharing. Seems I still have more to learn! It seems the bug you are flagging here is a null reference bug - I know Rust has Optional as a workaround for “null” Are there any pitfalls in Rust when Optional does not return anything? Or does Optional close this bug altogether? I saw Optional pop up in Java to quiet down complaints on null pointer bugs but remained skeptical whether or not it was better to…
So this is actually why "no null, but optional types" is such a nice spot in the programming language design space. Because by default, you are making sure it "should have been initialized," that is, in Rust:
struct Point {
x: i32,
y: i32,
}
You know that x and y can never be null. You can't construct a Point without those numbers existing.By contrast, here's a point where they could be:
struct Point {
x: Option,
y: Option,
}
You know by looking at the type if it's ever possible for something to be missing or not.> Are there any pitfalls in Rust when Optional does not return anything?
So, Rust will require you to handle both cases. For example:
let x: Option = Some(5); // adding the type for clarity
dbg!(x + 7); // try to debug print the result
This will give you a compile-time error: error[E0369]: cannot add `{integer}` to `Option`
--> src/main.rs:4:12
|
4 | dbg!(x + 7); // try to debug print the result
| - ^ - {integer}
| |
| Option
|
note: the foreign item type `Option` doesn't implement `Add`
It's not so much "pitfalls" exactly, but you can choose to do the same thing you'd get in a language with null: you can choose not to handle that case: let x: Option = Some(5); // adding the type for clarity
let result = match x {
Some(num) => num + 7,
None => panic!("we don't have a number"),
};
dbg!(result); // try to debug print the result
This will successfully print, but if we change `x` to `None`, we'll get a panic, and our current thread dies.Because this pattern is useful, there's a method on Option called `unwrap()` that does this:
let result = x.unwrap();
And so, you can argue that Rust doesn't truly force you to do something different here. It forces you to make an active choice, to handle it or not to handle it, and in what way. Another option, for example, is to return a default value. Here it is written out, and then with the convenience method: let result = match x {
Some(num) => num + 7,
None => 0,
};
let result = x.unwrap_or(0);
And you have other choices, too. These are just two examples.--------------
But to go back to the type thing for a bit, knowing statically you don't have any nulls allows you to do what some dynamic language fans call "confident coding," that is, you don't always need to be checking if something is null: you already know it isn't! This makes code more clear, and more robust.
If you take this strategy to its logical end, you arrive at "parse, don't validate," which uses Haskell examples but applies here too: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
Re: Pitfalls of Safe Rust
#139Earlier quoted context omitted.
I'm a big fan of liberal use of saturating_mul/add/sub whenever there is a conceivable risk of coming withing a couple orders of magnitude of overflow. Or checked_*() or whatever the best behavior in the given case is. For my code it happens to mostly be saturating. Overflow bugs are a real pain, and so easy to prevent in Rust with just a function call. It's pretty high on my list of favorite improvements over C/C++
If you saturate you almost never ever want to use the result. You need to check and if it saturates do something else.
Re: Pitfalls of Safe Rust
#140Earlier quoted context omitted.
The vast majority of code that does arithmetic will not produce a correct result with two's complement. It is simply assuming that the values involved are small enough that it won't matter. Sometimes it is a correct assumption, but whenever it involves anything derived from inputs, it can go very wrong.
This is something that's always bugged me because, yes, this is a real problem that produces real bugs. But at the same time if you really care about this issue then every arithmetic operation is unsafe and there is never a time you should use them without overflow checks. Sometimes you can know something won't overflow but outside of some niche type systems you can't really prove it to the compiler to elide the chec…
Similarly in this case, it's not like we don't have languages that do checked arithmetic throughout by default. VB.NET, for example, does exactly that. Higher-level languages have other strategies to deal with the problem; e.g. unbounded integer types as in Python, which simply never overflow. And, like you say, this sort of thing is considered unacceptable for low-level code on perf grounds, but, given the history with nulls and OOB checking, I think there is a lesson here.