2 ms·
I find move by default refreshingly simple. I don't even understand what is so hard to understand about move. A move is a simple memcopy + the certainty that t
by bombela 1y ago
I find move by default refreshingly simple. I don't even understand what is so hard to understand about move.
A move is a simple memcopy + the certainty that the source is unreachable.
This is important if the memcopied object holds resources.
Any new type is move only by default (no copy or clone). This is so that you can opt in willingly.
Making a move type also copy is not an API/ABI breaking change compared to removing copy (or clone).
And it's the same for clone not being the default. With clone you get to run custom code for cloning instead of a memcopy.
Remind me again how move, copy and clone works in C++ /s
- ninkendo 1y ago> Remind me again how move, copy and clone works in C++ /s Sarcasm, but it’s worth outlining… C++ “move semantics” are (1) precisely the opposite of rust, and (2) not move semantics at all. - Rust doesn’t let you override what happens during a move, it’s just a memcpy - C++ has an rvalue reference (&&) constructor, which lets you override how a thing is moved - Rust doesn’t let you use the moved-from value - C++ absolutely has no problem letting you used a value after wrapping it in std::move (which is really just a cast to an rvalue reference) - Rust uses moves to allow simple memcpy’ing of values that track resources (heap space, etc) by simply making sure nobody can access the source, and not calling Drop on it. - C++ requires you to write logic in your move constructor that “pillages” the moved-from value (for instance in std::string it has to set the source string’s pointer to nullptr and its length to 0.) This has the consequence of making the moved-from value still “valid” For copies: - Rust’s Copy is just “memcpy, but you can still use the original value”. Basically any type that doesn’t track some resource that gets freed on Drop. Rust simply doesn’t let you implement Copy for things that track other resources, like heap pointers. - C++’s copy happens implicitly when you pass something by value, and you get to write arbitrary code to make it work (like copying a string will malloc a new place on the heap and copy the buffer over) - Rust has an entirely different concept, Clone, which lets you write arbitrarily code to duplicate managed resources (analogous to how you’d use a C++ copy constructor) - C++ has nothing to help you distinguish “deep copy that makes new copies of resources” from “dumb copy that is just a memcpy”… if your type has an expensive concept of deep copying, callers will (perhaps inadvertently) use it every time they pass your type by value. IMO C++’s “move” still letting you touch the moved-from value is what made me realize how much C++ had lost the plot when C++11 came out. Rust’s semantics here are basically what happens when you look at what C++ was trying to do, and learn from its mistakes.
- bombela 1y agoI wouldn't have said it better or more thoroughly.
- jandrewrogers 1y agoC++ “move” semantics are quite complicated. That said, those C++ semantics are much better at handling some edge cases in systems software that Rust largely pretends don’t exist. It is a tradeoff. C++ is much uglier but also much better at handling cases where ownership and lifetimes are intrinsically ambiguous in a moved-from context because hardware has implicit ownership exogenous to the code.
- bombela 1y agoThe equivalent of the C++ move in Rust are the function take/replace (like mem::replace ajd option::take). And it is fully memory safe. You can build all the ownership you want by using raw pointers in Rust. And there is nothing wrong with a specific problem requiring unsafe because the problem cannot be taught to the borrow checker. But there is a point in your stack of abstractions where you can expose a safe and ergonomic API. If you have a concrete example I would love to get a crack at it.