4 ms·
While I am only a Rust novice it seems to me like the "2.2 Item 11: Implement the Drop trait for RAII patterns" could use some kind of mention of Drop-leaks. I
by musicnarcoman 2y ago
While I am only a Rust novice it seems to me like the "2.2 Item 11: Implement the Drop trait for RAII patterns" could use some kind of mention of Drop-leaks. I learned about it at https://doc.rust-lang.org/nightly/nomicon/leaking.html https://doc.rust-lang.org/nightly/nomicon/leaking.html
- loeg 2y agoThe big foot-gun here is mem::forget rather than Drop itself. Although yeah it is pretty surprising that is considered safe.
- vlovich123 2y agoIt’s not that surprising when you consider that “unsafe” only concerns itself with memory safety. mem::forget is not unsafe from that perspective. > In the past mem::forget was marked as unsafe as a sort of lint against using it, since failing to call a destructor is generally not a well-behaved thing to do (though useful for some special unsafe code). However this was generally determined to be an untenable stance to take: there are many ways to fail to call a destructor in safe code. The most famous example is creating a cycle of reference-counted pointers using interior mutability.
- loeg 2y agoYes, thanks, I read the article. Nevertheless, it's still a surprising footgun.
- PartiallyTyped 2y agoUnsafe is concerned with unsafe blocks. NonZero::new_unchecked requires unsafe even though it’s not concerned with mem safety.
- vlovich123 2y agoI believe the optimizer will do optimizations in response to the NonZero which can trigger UB if it does contain a 0, which is a traditional safety issue for Rust which can have no UB in safe code. But even the value being corrupt (ie NonZero returning 0) can cause memory safety issues. But yes, Rust also uses unsafe to bypass enforcing invariants, which std::mem::forget isn’t.
- milesrout 2y agoLeaking memory is unsafe. It was considered unsafe for decades: a prime example of the sort of problem you get in C or C++ that you avoid with automatic memory management. Lots of real crashes, stability issues and performance issues have been caused by memory leaks over the years. Rust initially advertised itself as preventing leaks, which makes sense as it is supposed to have the power of automatic memory management but without the runtime overhead. Unfortunately, shortly before Rust's release it was discovered that there were some APIs that could cause memory corruption in the presence of memory leaks. The decision was made that memory leaks would be too complicated to fix before 1.0: it would have had to have been delayed. So the API in question was taken out and Rust people quietly memory-holed the idea that leak freedom has ever been considered part of memory safety.
- chlorion 2y agoThe difference is that leaking is not UB, the worst case is an OOM situation, which at worst causes a crash, not a security exploit. Crashing is also considered to be safe in rust, panicking is common for example when something bad happens.
- milesrout 2y agoUndefined behaviour is behaviour not defined by the language. So obviously Rust can define or undefine whatever it likes. It is not a sensible argument to say that something is safe because its behaviour is defined, or unsafe because it is undefined, when the whole point is that Rust's chosen definition of safety is just marketing. I admit a better example is race conditions.
- kobebrookskC3 2y agono, undefined behavior not just behavior that is not covered by the language definition. undefined behavior is term of art largely taken from C/C++, basically meaning that correct programs are assumed not to have these behaviors. for example, see https://en.cppreference.com/w/c/language/behavior https://en.cppreference.com/w/c/language/behavior. the definition of ub is not "just marketing". many major security vulnerabilities stem from having ub (out of bounds access, use after free). the point of rust is pretty much that you have to try hard to have ub, whereas in c/c++, it's basically impossible to not have ub.
- 0x457 2y agoWhat's unsafe about implicitly "leaking" memory?
- loeg 2y agoDestructors do more than just free memory.
- 0x457 2y agoI've specifically said leaking memory? Then again, destructor that didn't run would be an application level error not "safety in rust" error.
- loeg 2y agoYou were responding to my comment, which had scope broader than just leaking memory. So, to suggest it is only about leaking memory is not really responsive.
- FpUser 2y agoRunning out of memory and killing the OS I would guess unless the OS kills misbehaving process first.
- 0x457 2y agoSo pretty much completely safe, then?
- charlotte-fyi 2y agoThe important note here is that you can't rely on Drop running in order to satisfy the SAFETY comment of an unsafe block. In practice, in safe Rust, this knowledge shouldn't really change how you write your code.
- Animats 2y agoRust destructors are interesting. - You can't export a reference to the thing you are dropping. You can do that in C++. This prevents "re-animation", where something destroyed comes back to life or is accessed beyond death. Microsoft Managed C++ (early 2000s), supported re-animation and gave it workable semantics. Bad idea, now dead. - This is part of why Rust destructors cannot run more than once. Less than once is possible, as mentioned above. - There's an obscure situation with Arc and destructors. When an Arc counts down to 0, the destructor is run. Exactly once. However, Arc countdown and destructor running are not an atomic operation. It is possible for two threads to see an Arc in a strong_count == 1 state just before the Arc counts down. Never check strong_count to see if you are "the last owner". That creates a race condition.[1] I've seen that twice now. I found race conditions that took a day of running to hit. Use strong_count only for debug print. - A pattern that comes up in GUI libraries and game programming involves objects that are both in some kind of index and owned by Arcs. On drop, the object should be removed from the index. This is a touchy operation. The index should use weak refs, and you have to be prepared to get an un-upgradable Weak from the index. - Even worse is the case where dropping an object starts a deletion of something else. If the second deletion can't be completed from within the destructor, perhaps because it requires a network transaction, it's very easy to introduce race conditions. [1] https://github.com/rust-lang/rust/issues/117485 https://github.com/rust-lang/rust/issues/117485
- Rusky 2y ago> - You can't export a reference to the thing you are dropping. You can do that in C++. This prevents "re-animation", where something destroyed comes back to life or is accessed beyond death. Microsoft Managed C++ (early 2000s), supported re-animation and gave it workable semantics. Bad idea, now dead. > > - This is part of why Rust destructors cannot run more than once. ... This is a very backwards way to describe this, I think. Managed C++ only supported re-animation for garbage collected objects, where it is still today a fairly normal thing for a language to support. This is why these "destructors" typically go by a different name, "finalizers." Some languages allow finalizers to run more than once, even concurrently, but this is again due to their GC design and not a natural thing to expect of a "destructor." The design of Drop and unmanaged C++ destructors is that they are (by default) deterministically executed before the object is deallocated. Often this deallocation is not by `delete` or `free`, which could perhaps in principle be cancelled, but by a function return popping a stack frame, or some larger object being freed, which it simply does not make sense to cancel.