Most active commenters
  • bombela(3)
  • mustache_kimono(3)

←back to thread

Pitfalls of Safe Rust

(corrode.dev)
168 points pjmlp | 27 comments | | HN request time: 0.879s | source | bottom
1. woah ◴[] No.43603395[source]
Is "as" an uneccesary footgun?
replies(4): >>43603560 #>>43603887 #>>43603998 #>>43605135 #
2. whytevuhuni ◴[] No.43603560[source]
That was my first impression as well. So much of Rust’s language and standard library enforces correctness, that gaps start to feel way more visible.

“as” is a good example. Floats are pretty much the only reason PartialEq exists, so why can’t we have a guaranteed-not-NaN-nor-inf type in std and use that everywhere? Why not make wrapping integers a panic even in release mode? Why not have proper dependent types (e.g. to remove bound checks), and proper linear types (to enforce that object destructors always run)?

It’s easy to forget that Rust is not an ideal language, but rather a very pragmatic one, and sometimes correctness loses in favour of some other goals.

replies(6): >>43603934 #>>43604031 #>>43604722 #>>43604743 #>>43605449 #>>43609077 #
3. eptcyka ◴[] No.43603887[source]
When fitting larger types into smaller ones? Yes.
4. bombela ◴[] No.43603934[source]
I have been following rust very closely since 2013.

As Rust is both evolving and spreading wide, we; the programmers, users of Rust; are also leveling up in how we approach correctness and design with it.

Maybe the next evolution will be something like Haskell but fast like Rust is fast like C without the pain of C++.

But it takes a while for the world to catch up, and for everybody to explore and find ways to work with or around the abstractions that helps with correctness.

It's a bit like the evolution from a pointer to some malloc memory, then the shared/unique pointer of C++, to the fully safe box/(a)rc of Rust.

It might be obvious today how much more efficient it is programming with those abstractions.

I see some similarities with functional programming that still seems so niche. Even though the enlighteneds swears by it. And now we actually seem to be slowly merging the best parts of functional and imperative together somehow.

So maybe we are actually evolving programming as a species. And Rust happens to be one of the best scaffold at this point in history.

Thank you for reading my essay.

replies(1): >>43604131 #
5. bombela ◴[] No.43603998[source]
Compared to C/C++ "as" feels so much safe r. Now that Rust and we the programmers have evolved with it, I too feel that "as" for narrowing conversion is a small foot gun.
replies(1): >>43605165 #
6. hansvm ◴[] No.43604031[source]
> guaranteed-not-NaN-nor-inf

Nor negative zero

7. pjmlp ◴[] No.43604131{3}[source]
There is hardly any evolution from pointer to malloc, C is one of the few systems languages, including those that predated it, where one needs math to allocate heap memory.

I do agree that the evolution is most likely a language that combines automatic resource management with affine/linear/effects/dependent/proofs.

Or AIs improve to the point to render all existing programming languages a thing from the past, replaced by regular natural languages and regular math.

replies(1): >>43607700 #
8. adgjlsfhk1 ◴[] No.43604722[source]
The other option would be to change how floating point works. IEEE specifies operations, not names, so it would be totally valid to have <= on floats be a total order (using integer cpu instructions), and make a function called IEEEAreIdiotsWhoThinkThisIsFloatingPointLessThan which is the partial order that sucks.
replies(2): >>43605401 #>>43605437 #
9. thayne ◴[] No.43604743[source]
There is some movement towards deprecating "as", and lints that will recommend using alternatives when possible, but there are a couple of cases, such as intentional truncation, where there isn't a stable alternative yet.
10. bigstrat2003 ◴[] No.43605135[source]
I wouldn't say so. I quite like "as". It can have sharp edges but I think the language would be significantly worse off without it.
replies(2): >>43605381 #>>43605444 #
11. bigstrat2003 ◴[] No.43605165[source]
I'm struggling to see how you would implement narrowing conversion in a way that is harder for programmers to misuse when they aren't being mindful, while also being pleasant to use when you really do want to just drop higher bits. Like, you could conceivably have something like a "try_narrow" trait which wraps the truncated value inside an Err when it doesn't fit, and it would probably be harder to accidentally misuse, but that's also really cumbersome to use when you are trying to truncate things.
replies(2): >>43605460 #>>43606023 #
12. zozbot234 ◴[] No.43605381[source]
The question is not whether the language should include such a facility, but whether 'as' should be the syntax for it. 'as' is better than the auto conversions of C but it's still extremely obscure. It would be better to have some kind of explicit operator marking this kind of possibly unintended modulo conversion. Rust will gain safe transmute operations in the near future so that will perhaps be a chance to revise this whole area as well.
13. wongarsu ◴[] No.43605401{3}[source]
For purposes of sorting, Rust does offer a non-IEEE order as f64::total_cmp. You can easily build a wrapper type that uses that for all comparisons, or use a crate that does it for you

https://doc.rust-lang.org/std/primitive.f64.html#method.tota...

replies(1): >>43605489 #
14. ◴[] No.43605437{3}[source]
15. wongarsu ◴[] No.43605444[source]
It's useful to have something that does the job of "as", but I dislike how the most dangerous tool for type conversions has the nicest syntax.

Most of the time I want the behavior of ".try_into().unwrap()" (with the compiler optimizing the checks away if it's always safe) or would even prefer a version that only works if the conversion is safe and lossless (something I can reason about right now, but want to ensure even after refactorings). The latter is really hard to achieve, and ".try_into.unwrap()" is 20 characters where "as" is 2. Not a big deal to type with autocomplete, but a lot of visual clutter.

16. int_19h ◴[] No.43605449[source]
Some of these don't strike me as particularly pragmatic. E.g. are overflow checks really that expensive, given that it's a well-known footgun that is often exploitable? Sure, you don't want, say, 10% overhead in your number-crunching codec or whatever, but surely it's better to have those cases opt in for better perf as needed, as opposed to a default behavior that silently produces invalid results?
replies(1): >>43606593 #
17. woah ◴[] No.43605460{3}[source]
let foo: u8 = bar<u64>.truncate_to()?
18. zozbot234 ◴[] No.43605489{4}[source]
total_cmp is precisely IEEE's separately specified total order for floats. It's just that the more common operators do something different, and that's perhaps better for most uses where NaN are inherently unexpected and generally indicate that some kind of error condition has occurred.
19. recursivecaveat ◴[] No.43606023{3}[source]
I don't really want narrowing conversion to be harder, I just want checked conversion to be at least nearly as convenient. `x as usize` vs `x.try_into().unwrap()` becomes `x tiu usize` or something even. I'm not picky. It's kindof funny that this is the exact mistake C++ made, where the safe version of every container operation is the verbose one: `vector[]` vs `vector.at()` or `*optional` vs `optional.value()`, which results in tons and tons of memory problems for code that has absolutely no performance need for unchecked operations.
20. mustache_kimono ◴[] No.43606593{3}[source]
> Some of these don't strike me as particularly pragmatic. E.g. are overflow checks really that expensive

Did you read the article? Rust includes overflow checks in debug builds, and then about a dozen methods (checked_mul, checked_add, etc.) which explicitly provide for checks in release builds.

Pragmatism, for me, is this help when you need it approach.

TBF Rust forces certain choices on one in other instances, like SipHash as the default Hasher for HashMap. But again opting out, like opting in, isn't hard.

replies(1): >>43607313 #
21. bobbylarrybobby ◴[] No.43607313{4}[source]
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.
replies(1): >>43608490 #
22. bombela ◴[] No.43607700{4}[source]
Sorry, my wording was not great. You got it right. I was saying that the evolution started with (a pointer from) malloc, then uniqueptr, then box.
23. mustache_kimono ◴[] No.43608490{5}[source]
> 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 release mode. I just think that tradeoff isn't generally worth it, but again, you can turn that behavior on.

replies(1): >>43611712 #
24. FreezyLemon ◴[] No.43609077[source]
Regarding the not-NAN float type, there was actually a proposal for it which was shot down: https://github.com/rust-lang/libs-team/issues/238.

I don't remember every argument in there but it seemed that there are good reasons not to add it unlike a NonZero integer type which seems to have no real downsides.

25. steveklabnik ◴[] No.43611712{6}[source]
panics do not terminate immediately; they unwind the stack, and if they’re not caught, they terminate the current thread, not the process.
replies(1): >>43612007 #
26. mustache_kimono ◴[] No.43612007{7}[source]
> 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.

replies(1): >>43612222 #
27. steveklabnik ◴[] No.43612222{8}[source]
I don't disagree that it could use revising, but it's technically correct: it allows but does not require. If you've configured panic=abort, it will abort the program instead of unwind, but that's not the default.