4 ms·
It’s important that a for loop takes ownership of the vec, because it’s the only way you can call things inside the loop body that require the element itself to
by ninkendo 1y ago
It’s important that a for loop takes ownership of the vec, because it’s the only way you can call things inside the loop body that require the element itself to be moved.
If you don’t want the loop to take ownership of the vec, there’s literally a one character change: put a & before the thing you’re iterating (ie. for x in &y). That borrows the vec instead of moving it.
You seem to want rust to decide for itself whether to borrow or own the contents, but that way lies madness… it will be really hard to reason about what more complicated code is doing, and changes would have very non-local effects on how the compiler decides to use your code.
For me, move-semantics-by-default is the key idea that rust got right, and it’s a very simple concept. It’s not intuitive, but it’s the key idea behind all of rust’s benefits for memory management and preventing concurrency bugs. “Learn one simple but non-intuitive thing and you get these huge benefits” is a tradeoff I’m very much willing to make, personally.
- 59nadir 1y ago> You seem to want rust to decide for itself whether to borrow or own the contents, but that way lies madness… Most of what Rust does already feels like madness, like the concept of implicit moves, etc., but I understand your point. I don't think the reasoning really makes sense in terms of actual logic, but as I wrote in another comment: It's possible that I've misunderstood the sales pitch of Rust trying to be GC-less GCd language. > For me, move-semantics-by-default is the key idea that rust got right, and it’s a very simple concept. It’s not intuitive, but it’s the key idea behind all of rust’s benefits for memory management and preventing concurrency bugs. “Learn one simple but non-intuitive thing and you get these huge benefits” is a tradeoff I’m very much willing to make, personally. I can respect that and seen this way (where we accept that we're simply going to have unintuitive and incorrect rejections of programs) it does make a lot more sense.
- bombela 1y agoI 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.