3 ms·
In Rust, casting a *mut to a &mut (the standard method of allowing safe code to mutate an object) is unsafe if other &, &mut, or Pin<&mut> exists, regardless of
by nyanpasu64 4y ago
In Rust, casting a *mut to a &mut (the standard method of allowing safe code to mutate an object) is unsafe if other &, &mut, or Pin<&mut> exists, regardless of whether or not you commit a use-after-free. In C++, the equivalent code interchangeably using * and & is safe until you actually commit a use-after-free. This makes it harder to write correct Rust code mixing safe and unsafe code.
- hgomersall 4y agoBut the casting is specified as unsafe, so if you do it (in `unsafe` code) and don't check that its safe, then you're breaking the safety contract. The whole point of `unsafe` is to define a clear boundary across which invariants must be upheld. In this case, it's those given here: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_mut https://doc.rust-lang.org/std/primitive.pointer.html#method.... What's the use case for wanting to convert an invalid pointer to a `&mut` and then not use it? If I'm missing something, perhaps you could show me some code that highlights the problem (on, say, rust playground).
- nyanpasu64 4y agoIt's not just dereferencing invalid */& that's illegal in Rust, but constructing and dereferencing valid &/&mut in ways that don't respect tree-shaped mutability. I've put together a playground at https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3b5066c3f091b34b981d7d0ca8748c4d https://play.rust-lang.org/?version=stable&mode=debug&editio.... "Object graph" architectures are common in C++ and sometimes necessary in Rust when building GUI applications or emulators. Rust's pointer aliasing rules invalidate otherwise-correct code, placing roadblocks in the way of writing correct code. And there's so much creation of &mut (which invalidates aliasing pointers for the duration of the &mut, and invalidates sibling &mut and all pointers constructed from them), that's so implicit I don't know what's legal and what's not by auditing code. (Box<T> used to also invalidate aliasing pointers, but this may be changed. The current plan for enabling self-reference is Pin<&mut T>, but the exact semantics for how and when putting a &mut T in a wrapper struct makes it not invalidate self-reference and incoming pointers, is still not specified.) I've heard statements that addr_of_mut! is an interim API, and the situation may be improved with &raw and unsafe-deref syntax (https://faultlore.com/blah/fix-rust-pointers/ https://faultlore.com/blah/fix-rust-pointers/). But I expect a production systems language to be an improvement upon C/C++'s usability in their strongest domains out of the box (much like how Send/Sync as marker types not creating UB beyond C++ makes threading more tractable, and enums are superior to std::variant). Instead today's Rust pointer rules redefines swathes of C and C++'s use cases and design patterns as undefined behavior, and the alternative approaches are ugly to express in safe code (Cell/RefCell), and easy to get wrong and tricky and ugly to get right in unsafe code (see my playground), with the promise that they were trying to make programming easier and are trying to create a suitable replacement someday down the line (7 years and counting after Rust 1.0). It reminds me of C++'s long-running object lifetime saga (https://en.cppreference.com/w/cpp/language/lifetime https://en.cppreference.com/w/cpp/language/lifetime, https://www.reddit.com/r/cpp_questions/comments/dfglt2 https://www.reddit.com/r/cpp_questions/comments/dfglt2), but I've never had to deal with this mess directly, unlike Rust object graphs.
- hgomersall 4y agoThanks for the explanation. Self referential structures are indeed problematic. I'm actually pretty sanguine about the general case of invalidating C/C++ patterns. It doesn't surprise me that some new computer science is going to be necessary to help deal with the edge cases exposed by mainstreaming a new paradigm. As an example, ghost cells are an interesting solution to address some of these kinds of problems https://crates.io/crates/ghost-cell https://crates.io/crates/ghost-cell
- nyanpasu64 4y agoMy goal is not to invent new computer science, but to create a language where programming is not "puzzle solving" (https://news.ycombinator.com/item?id=33694124 https://news.ycombinator.com/item?id=33694124), like needing to work with ghost cells (didn't try firsthand yet) since pointer algorithms are laden with syntactic salt and pitfalls (note that pointers is not C/C++ patterns but fundamental systems programming), working with Rust's num traits that describe the properties of numbers rather than directly saying your FFT code is generic over f32 and f64, hacking in sealed traits using underspecified and disputed visibility rules (attempting to match the type of a sum type at compile time), chaining iterator combinators rather than writing code where program order matches execution order, not being able to index into strings when parsing unless I convert to &[u8] then spend extra O(n) runtime validating the remaining text is valid UTF-8.
- hgomersall 4y agoFrom experience, I find that "puzzle solving" tends to result in very good code. I recently had the experience of fundamentally changing the design of a decent chunk of code (think maybe 3 weeks work) for which I had integration tests that were comprehensive. Once I had the rust compiling, the tests passed first time. I've had this sort of experience in Rust several times, and never in other languages (primarily C and Python). It wasn't a trivial implementation in Rust - I had to think carefully how to craft it to make effective use of Rust idioms, but the result is great. It feels to me that the question comes down to whether changing paradigms is worth it. I have found overwhelmingly that it has been and with my hard won battle scars, I'll stay here.