The perils of pinning
The perils of pinning
Posted Sep 15, 2022 19:33 UTC (Thu) by fw (subscriber, #26023)In reply to: The perils of pinning by atnot
Parent article: The perils of pinning
If you take a struct in C and pass it somewhere by value, that struct is now in a new location. If you do that with a self referential struct, it is now invalid in the new location.C only has copies, not moves, so the result is completely valid. It's just that the new object is not self-referential anymore: its pointers refer to the old object instead. But these pointers remain valid while the old object is still around. That's completely different with Rust move semantics.
Posted Sep 15, 2022 20:23 UTC (Thu)
by mb (subscriber, #50428)
[Link] (7 responses)
No. Not really.
The only difference is that in Rust the "the old object is still around" is zero time.
The difference between a copy and a move is that the lifetime of the old object is terminated immediately and not at some later point. And based on that the compiler might be able to apply some optimizations to omit the copy altogether.
And if you copy a self referential object, it's by definition not self referential after copy. It references another object (the original one!). Not self.
Posted Sep 16, 2022 1:59 UTC (Fri)
by JoeBuck (subscriber, #2330)
[Link] (3 responses)
Posted Sep 16, 2022 18:10 UTC (Fri)
by tialaramex (subscriber, #21167)
[Link] (2 responses)
Rust's Clone trait, which is what happens if you clone() something that admits it can be cloned, is a function you can implement. For example my Multiplicity https://docs.rs/misfortunate/1.0.0/misfortunate/struct.Mu... wrapper type claims to be Clone, despite the fact all it asks of your wrapped type T is that it should be Default. Multiplicity's clone() implementation just makes a Default::default() of your type, which is type correct but presumably not what you wanted. (The name is a reference to the 1996 Andie MacDowell comedy in which Michael Keaton clones himself repeatedly and gradually the clones deteriorate in quality)
But you deliberately can't intervene in the simple memcpy() behaviour when the compiler decides to move your object in memory. The Rust compiler reserves the rights to do this more often than you expect, or less often, and in different places, as it sees fit. Pinning stops it from doing that, at some potential cost in performance.
Posted Sep 22, 2022 6:44 UTC (Thu)
by Homer512 (subscriber, #85295)
[Link] (1 responses)
If the compiler already has facilities to detect when an object should not be moved, why is it so hard to give it facilities to invoke a custom function for the move instead?
Posted Sep 27, 2022 9:27 UTC (Tue)
by farnz (subscriber, #17727)
[Link]
It's hard because the consequence of adding a "move fix-up" function is to break the mental model people have of Rust code.
In today's world, the maximum cost of a move in Rust (or a copy for things that impl Copy) is a memory copy of the object. The cost of a move is thus predictable.
If Rust adds a "move fix-up" function of some form, that predictability goes away - you now depend on the implementation of the move function being high-quality. C++ goes this route, of having hidden fix-up functions (operator=, move and copy constructors), and thus provides an illustration of the benefits of that approach (where all developers are skilled) and of the costs (where some developers are unskilled and don't realise that they're invoking a lot of code doing a move).
Actually implementing the changed semantics in the compiler would be trivial - it's the human cost of learning how the new semantics work that's non-trivial.
Posted Sep 17, 2022 14:01 UTC (Sat)
by jezuch (subscriber, #52988)
[Link] (1 responses)
Posted Sep 18, 2022 14:57 UTC (Sun)
by matthias (subscriber, #94967)
[Link]
And for correctness of self referential objects, you have to assume that the object might be physically moved if it is logically moved. Also, for unpinned and unaliased objects, the compiler is free to move it around for code optimization, even without a logical move.
Posted Sep 18, 2022 17:40 UTC (Sun)
by fw (subscriber, #26023)
[Link]
Posted Sep 15, 2022 23:06 UTC (Thu)
by atnot (subscriber, #124910)
[Link] (1 responses)
Of course, this is a completely moot point in C because it's always possible to get any memory into an arbitrary state anyway. But Rust doesn't have the luxury of being able to tell people just not to do things. Hence unlike C, it does not allow you to access the old object after a copy (unless it's marked with the special Copy trait). Which is after all the only difference between a copy and a move, it's not like the old memory disappears.
Posted Sep 16, 2022 2:15 UTC (Fri)
by wahern (subscriber, #37304)
[Link]
Rather, Rust has the luxury of telling people they *can't* do things. Which is critical to and in some cases necessary for Rust being able to provide its safety guarantees. Complex, memory-aware data structures (e.g. cyclical, self-referential, etc) have *always* been a sore point for Rust. Most Rust developers come from the world of scripting languages or similar environments (e.g. Java) where hash tables are the primary, even sometimes exclusive primitive (beyond arrays) for building higher-order data structures, so they don't feel any loss. For this group, more complex data structures always come by way of magic libraries with generic interfaces that you glue together; building thin, bespoke data structures for your specific functional problems as a matter of course is a foreign concept.
Many solutions Linux has adopted for higher performance, lower latency, multi-core scalability, etc simply wouldn't have even been considered at all when writing everything in scratch from Rust. It's probably the case that equally (or at least sufficiently) performant Rust-consonant alternatives exist for most individual cases. The real question is whether at scale, when you're stitching all these solutions together into what is essentially a single, complex data structure, you can end up in the same place in terms of performance; or if the solutions Rust demands intrinsically impose greater costs when composed together.
The perils of pinning
Everything else is the same.
Rust can't rip out memory from your machine. Therefore it also technically copies.
The perils of pinning
The perils of pinning
The perils of pinning
The perils of pinning
The perils of pinning
The perils of pinning
- the object is moved from the stack to the heap
- the object is moved between different allocations on the heap
- the object is moved to a function parameter (if the function is not inlined, then the object has to be moved to the place where the function expect its input, for inlined funtions, the move will usually be avoided)
The perils of pinning
Rust can't rip out memory from your machine. Therefore it also technically copies.
Aren't Rust semantics such that the moved-from object becomes invalid as part of the move? What happens in a concrete computer implementation does not really matter for correctness if the abstract machine behaves differently. If the object is gone in the abstract machine, compilers can and will eventually optimize based on the assumption that accesses to it will not happen during the execution of the program.
The perils of pinning
The perils of pinning