Hacker News new | ask | show | jobs
by tialaramex 1182 days ago
This is idiomatic Rust, it works very nicely there, however most languages aren't Rust

Rust's Into::into() is consuming the object in the old (now shadowed) rect variable. So conveniently the old rect variable which we can't access also no longer has a value†. In many languages a method can't consume the object like that, so the old object still exists but we can't access it because it is shadowed.

For example in C++ they have move semantics, but their move isn't destructive, so the object is typically hollowed out, but still exists until the end of the scope at least.

Rust's type strictness matters here too. It means if you later modify some code using rect meaning whatever it was before that statement morphing it into a Rect<f32> chances are it doesn't type check and is rejected. For example in many languages if (rect) { ... } would be legal code and might change meaning as a result of the transformation, but in Rust only booleans are true or false.

† Unless this previous variable's type implemented the Copy trait and therefore it has Copy semantics and consuming it doesn't do anything.

2 comments

This is all true in this instance, but it doesn't have to be at all. You could write something like the following:

    let name = "Arthur".to_owned();
    //... Do something with &Arthur

    let name = "Bethan".to_owned();
    //... Do something with &Bethan
In this situation, ownership of the first string was never passed on, and the value will be dropped (deallocated, destructed, etc) at the end of the scope, meaning that also in Rust, the first string value is shadowed and becomes inaccessible, much like in your description of C++. In addition, because in this case both variables have the same type, you can use second variable thinking that you're using the first one, and the compiler will not help you, you'll just end up using the wrong name somewhere.

Fwiw, I find this feature very useful, and it's helpful more often than it is a nuisance. But there are no guarantees that you're consuming or transforming the object you're shadowing, and the compiler won't necessarily help you out if you simply accidentally use the same name twice.

This is interesting to me that C++ allows you to access a value after move is called. Presumably it wouldn’t be hard for the compiler to yell at you.

I assume accessing it is undefined behavior?

I would assume you could change this without affecting backwards compatibility.

As another commenter says the moved-from object should have "Valid but unspecified state" (types provided by the standard library will do that, custom types merely should do that)

Since you don't know what valid state it has, calls with pre-requisites are nonsense (e.g. if you have a Bird and the method land requires that the Bird should be flying, you can't call land() on a moved from Bird, because you don't know if it's flying) but all calls without pre-requisites are fine e.g. asking how long a string you moved is would work - it's probably zero length now, but maybe not.

> Presumably it wouldn’t be hard for the compiler to yell at you.

In the general case this is Undecidable, so, the opposite of not hard.

> I would assume you could change this without affecting backwards compatibility.

C++ which relies on this exists today, the most likely path to actually landing destructive move in C++ would be to add a whole new set of construction and assignment operators for destructive move, forcing people to opt in and adding to the many sets C++ already has, and likely angering C++ developers a great deal in the process.

Howard Hinnant, whose design today's non-destructive move is, did argue that in principle it's possible to add destructive move to the language later if desired, but his description rather undersells the benefits of this design, presumably because he couldn't deliver it. Maybe he'd watched enough Mad Men (yup, Mad Men's early seasons pre-date C++ having move semantics) to know you shouldn't tell the customer what they can't have or they'll want it.

Common things to actually do with a C++ variable after moving from it are:

* Nothing, but in the knowledge it won't be cleaned up until the scope ends * Re-assign it, destroying the hollowed out object immediately * Re-use the hollowed out object, e.g. call a clear() method on it and then use as normal

The only requirement placed on the “moved out” variable is that you should be able to call its destructor. Which means that it has to be in a valid but unspecified state. So it's fine to access such a variable, so long as you don't read its exact state. You can still assign to it, for instance.