Live data from Hacker News

Pitfalls of Safe Rust

corrode.dev

101–110 of 145 posts

Re: Pitfalls of Safe Rust

#101
post #37

I'd add memory leaks to the list. Sometimes you feel compelled to wrap your data in an Rc or Arc (reference counted pointers for those unfamiliar) to appease the borrow checker. With capture semantics of closures and futures and such it's quite easy to fall into a referential cycle, which won't be freed when dropped.

I don't think it's a common concern in Rust. It used to be a problem in Internet Explorer. It's a footgun in Swift, but Rust's exclusive ownership and immutability make cycles very difficult to create by accident.

If you wrap a Future in Arc, you won't be able to use it. Polling requires exclusive access, which Arc disables. Most combinators and spawn() require exclusive ownership of the bare Future type. This is verified at compile time.

Making a cycle with `Arc` is impossible unless two other criteria are met:

1. You have to have a recursive type. `Arc` can't be recursive unless `Data` already contains `Arc` inside it, or some abstract type that could contain `Arc` in it. Rust doesn't use dynamic types by default, and most data types can be easily shown to never allow such cycle.

It's difficult to make a cycle with a closure too, because you need to have an instance of the closure before you can create an Arc, but your closure can't capture the Arc before it's created. It's a catch-22 that needs extra tricks to work around, which is not something that you can just do by accident.

2. Even if a type can be recursive, it's still not enough, because the default immutability of Arc allows only trees. To make a cycle you need the recursive part of the type to also be in a wrapper type allowing interior mutability, so you can modify it later to form a cycle (or use `Arc::new_cycle` helper, which is an obvious red flag, but you still need to upgrade the reference to a strong one after construction).

It's common to have Arc-wrapped Mutex. It's possible to have recursive types, but having both together at the same time are less common, and then still you need to make a cycle yourself, and dodge all the ownership and borrow checking issues required to poll a future in such type.

Re: Pitfalls of Safe Rust

#102

Earlier 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…

Yeah, anything can (and should) be marked unsafe if it could lead to memory safety problems. And so if it potentially breaks an invariant which is relied on for memory safety, it should be marked unsafe (conversely, code should not rely on an unchecked, safe condition for memory safety). That's basically how it works, Rust has the concept of unsafe functions so that libraries can communicate to users about what can and can't be relied on to keep memory safety without manual checking. This requires a common definition of 'safe', but it then means there isn't any argument about where the bug is: if the invariant isn't enforced by the compiler in safe code, then other code should not rely on it. If it is, then the bug is in the unsafe code that broke the invariant.

Re: Pitfalls of Safe Rust

#103
post #94
post #33

Earlier quoted context omitted.

That subculture is called “people who haven’t read the docs”, and I don’t see why anyone would give a whole lot of weight to their opinion on what technical terms mean

Because of cult like belief structures growing up around rust, it's clear as day for us on the outside, I see it from the evangelists in the company I work for "rust is faster and safer to develop with when compared to c++", I'm no c++ fan but it's obviously nonsense. I feel people took the comparison of rust to c and extrapolated to c++ which is blatantly disingenuous.

Care to explain the obvious, then? Rust is quite a lot nicer to write than C++ in my experience (and in fact, it seems like rust is most attractive to people who were already writing C++: people who still prefer C are a lot less likely to like Rust).

Re: Pitfalls of Safe Rust

#104
post #94

Earlier quoted context omitted.

Because of cult like belief structures growing up around rust, it's clear as day for us on the outside, I see it from the evangelists in the company I work for "rust is faster and safer to develop with when compared to c++", I'm no c++ fan but it's obviously nonsense. I feel people took the comparison of rust to c and extrapolated to c++ which is blatantly disingenuous.

Care to explain the obvious, then? Rust is quite a lot nicer to write than C++ in my experience (and in fact, it seems like rust is most attractive to people who were already writing C++: people who still prefer C are a lot less likely to like Rust).

There is nothing attractive about c++ or rust, I really don't understand how anyone can think so, it has to be some sort of Stockholm syndrome. Think about it, before you started programming what about your experiences would make you appreciate the syntax soup of rust and c++?

Re: Pitfalls of Safe Rust

#105

Earlier 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++

To add on another pitfall: iterator invalidation. In C++ you generally aren't allowed to modify a container while you're iterating through it, because it may re-allocate the memory and leave dangling pointers in the iterator, but the compiler doesn't check this. Rust's lifetime analysis closes this particular issue.

(Basically, the 'newer' C++ features do help a little with memory safety, but it's still fairly easy to trip up even if you restrict your own code from 'dangerous' operations. It's not at all obvious that a useful memory-safe subset of C++ exists. Even if you were to re-write the standard library to correct previous mistakes, it seems likely you would still need something like the borrow checker once you step beyond the surface level).

Re: Pitfalls of Safe Rust

#106

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…

True, I should add the wrapping types. They are actually quite useful if you know that you have a fixed range of values and you can't go above the min/max. Like a volume dial that just would stay at "max" if you turn up the volume; it wouldn't wrap around.

Re: Pitfalls of Safe Rust

#107
post #104

Earlier quoted context omitted.

Care to explain the obvious, then? Rust is quite a lot nicer to write than C++ in my experience (and in fact, it seems like rust is most attractive to people who were already writing C++: people who still prefer C are a lot less likely to like Rust).

There is nothing attractive about c++ or rust, I really don't understand how anyone can think so, it has to be some sort of Stockholm syndrome. Think about it, before you started programming what about your experiences would make you appreciate the syntax soup of rust and c++?

I dunno, there's not much about my previous experience that would indicate much one way or the other. I have found, though, that I tend to prefer slightly denser, heterogeneous code and syntax than average. Low-syntax languages like Haskell and Lisps make my head hurt because the code is so formless it becomes hard for me to parse, while languages with more syntax and symbols are easier (though, there is a limit, APL,k, etc, are a little far I find)

Re: Pitfalls of Safe Rust

#108
post #74

Earlier quoted context omitted.

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?

> 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, lik…

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.

Re: Pitfalls of Safe Rust

#109
post #26
post #15

Earlier quoted context omitted.

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 w…

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, replac…

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.

Re: Pitfalls of Safe Rust

#110
post #77

> Overflow errors can happen pretty easily No they can’t. Overflows aren’t a real problem. Do not add checked_mul to all your maths. Thankfully Rust changed overflow behavior from “undefined” to “well defined twos-complement”.

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 check in a way that is safe against code modifications— i.e. if someone edits code that breaks the assumption we needed to know it won't overflow it will err.

But at the same time in real code in the real world you just do the maths, throw caution to the wind, and if it overflows and produces a bug you just fix it there. It's not worth the performance hit and your fellow developers will call you mad if you try to have a whole codebase with only checked maths.

Post reply on HN