Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Literally nothing in your comment is correct. In the Rust snippet vector is moved into the loop, and thus freed.

There are situations where you hit the limits of the borrow checker, plenty of them. This is not one of them. Again, the borrow checker is not even involved in the original snippet.



> In the Rust snippet vector is moved into the loop, and thus freed.

Do you see any code in the snippet that requires it to be? This could be a simple read-only borrow because the actual logic of the program requires only that, and the value could live on happily after the loop. Literally nothing in this snippet requires anything else, you've just sort of assumed that the way Rust does it is the only thing that makes sense.

It's not about what Rust currently does, really, it's about what it ought to do.


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.


> 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.


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


C++ “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.


The 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.


> 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.


I wouldn't have said it better or more thoroughly.


Are you suggesting Rust should automatically insert the borrow annotation because it is able to see that a borrow is sufficient? That would be quite unintuitive and make it ambiguous whether a for loop is borrowing or consuming the iterator without reviewing the body. I'd strongly argue that it should unambiguously do either one or the other and not try and read the author's mind.


Yes, I'm suggesting it should do the right thing for the code the loop is actually trying to execute. I personally think this is exactly what Rust and its users have signed up for. I might be mistaken about that, but I think it's in line with the more general view that Rust is attempting to be as close as it can get to a language that reads like it has a garbage collector without having one.


> the more general view that Rust is attempting to be as close as it can get to a language that reads like it has a garbage collector without having one.

I've used Rust a fair amount, and I've never seen that expressed as a goal.

A couple of general principles followed by Rust are to prefer explicit code over implicit conversions and to support local reasoning. Those are both present here: the borrow needs to be made explicitly, rather than implicitly based on code later on.


This is not at all what we signed up for. The explicit-ness is the point.


The code says to call into_iter and consume the iteratee, so rust does that. If you want a reference, use the &, just like in zig/c/c++/etc. You are saying an even more extreme version of "If there's a way what I wrote could possibly be interpreted that could compile, it should do that" ignoring the fact that there's almost assuredly _many_ ways that your code can be interpreted that could compile.

Slowing down type resolution/compilation (by making every unannotated loop a generic T|&T) and adding more syntax (since rust would need new annotations to explicitly specify borrow/take in for loops), in order to save a single character that matches the behavior of most other related languages and is perfectly clearly explained by the compiler, is maybe a bad move. Considering compile time and complicated syntax are two of the biggest things people who actually write rust complain about.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: