Live data from Hacker News

Move, simply

herbsutter.com

71–80 of 96 posts

Re: Move, simply

#71
post #69
post #58

Earlier quoted context omitted.

Destructive move would be incompatible with c++ as it works today. If you construct an object it’s valid. Since move doesn’t do delete() (how could it?) it leaves behind a block of memory that the program considers an X and so must be valid.

The compiler can just say "this value is dead, you can't use it". That's what rust does - rust isn't literally freeing/clearing memory when a struct is moved.

Rust can do this for an arbitrary element of a dynamically allocated array?

Re: Move, simply

#72
post #53

Earlier quoted context omitted.

Is the intention of std::move that the "moved-from" pointer no longer has its destructor run? What if you move a unique_ptr from a std::vector? You don't know which elements of the vector need to have their destructors run. I think Rust unconditionally doesn't run the destructor of a moved-from Box, but uses drop flags for "maybe-moved-from" local variables, and doesn't allow maybe-moved-from Vec elements.

My point was on handling the moving of trivially copyable types, like int, not those with side effects like unique_ptr. For trivially copyable types, copy and move are (almost) synonymous, and can be made such in Rust with #[derive(Copy)]. But that difference actually gets to your point here. The only difference between copy and move is that a move allows for the new data to overlap the previous. So if you think abou…

You’ve referred to two kinds of move there — memmove (overlapping) vs memcpy (non-overlapping memory) and transfers of ownership. In C++ a move appears to always require moving memory with either (depending on aliasing) or via registers. Is that correct?

In Rust, the only thing that has to happen is a transfer of ownership, which does not always mean moving bytes. This greatly simplifies the whole “rvalue reference parameter” thing, because ownership abstracts over wherever you might want to move to. So if your new owner wants it in part of its new stack frame, the compiler is free to just put it there before calling or simply call a destructor on the parent stack frame’s memory in the child. (I don’t know exactly what it likes to do, but it’s all fine.) New owners like Box need to move memory, and that’s cool too, and the compiler is free to reorder the steps to avoid initialising and moving when “placement new” is better. This is also something I believe the compiler is getting better at slowly. The compiler won’t let you safely write moves out of a vector without mutating or taking ownership of the vector, each with moving bytes — otherwise it has access to something it no longer owns.

The easiest example is `let a = A::new(); let b = a;`, which only needs stack space for one A, and for which line 2 is a noop. Its only effect is in when drop is called, if b is in a smaller scope. Does C++ do this too? I would hope so, but it looks like no.

Edit: turns out neither do it.

- https://rust.godbolt.org/z/xjA9v4 - https://godbolt.org/z/7Wh_AZ

Re: Move, simply

#73
post #68
post #43

Earlier quoted context omitted.

> No different than when they added move the first time in C++11 and everybody had to learn the difference between copy and move. Yes, it's not the first time the cognitive load of C++ has increased. > No different than Rust programmers learning the difference between Rust's move and C++'s move. Learning concepts in a new language doesn't increase cognitive load in the same way. In C++ you will have to decide which k…

Happily the undefined behavior plan for C++ seems to be, in addition to putting all the UB definitions in one place , requiring all new proposals justify why something should be UB or UD. E.g. no more "it requires work to say what happens" UB cop outs - if something can be defined it should be. In this case I'd say that a value cannot be used after it is destructively moved. There is no reason for that to be UB, when…

In general the compiler will not be able to prove that the value is not used again, because of possible aliasing. E.g. if you called a method on the object, then the compiler would need to be able to prove that 'this' hasn't been stashed away somewhere so it can be used again (without otherwise triggering undefined behavior) after the object has been moved out of.

I.e. to have the compiler prove absence of use-after-move requires the same sort of lifetime analysis it would need to prove absence of use-after-free, and that is simply not realistic for C++.

Re: Move, simply

#74
post #71
post #69

Earlier quoted context omitted.

The compiler can just say "this value is dead, you can't use it". That's what rust does - rust isn't literally freeing/clearing memory when a struct is moved.

Rust can do this for an arbitrary element of a dynamically allocated array?

Rust can only do that in unsafe blocks.

The C++ version would need the existing (unchangeable) move semantics.

Re: Move, simply

#75
post #71
post #69

Earlier quoted context omitted.

The compiler can just say "this value is dead, you can't use it". That's what rust does - rust isn't literally freeing/clearing memory when a struct is moved.

Rust can do this for an arbitrary element of a dynamically allocated array?

No. You can't move an element out of an array in Rust.

What Ollie said is true for local variables, and collections generally support some kind of "move element(s) out of the collection" API (e.g. Vec::pop(), T::into_iter()).

Re: Move, simply

#76
post #73
post #68

Earlier quoted context omitted.

Happily the undefined behavior plan for C++ seems to be, in addition to putting all the UB definitions in one place , requiring all new proposals justify why something should be UB or UD. E.g. no more "it requires work to say what happens" UB cop outs - if something can be defined it should be. In this case I'd say that a value cannot be used after it is destructively moved. There is no reason for that to be UB, when…

In general the compiler will not be able to prove that the value is not used again, because of possible aliasing. E.g. if you called a method on the object, then the compiler would need to be able to prove that 'this' hasn't been stashed away somewhere so it can be used again (without otherwise triggering undefined behavior) after the object has been moved out of. I.e. to have the compiler prove absence of use-after-…

Use after free (through references) is a standard problem in C++ through anything. But you can say post-move a value is dead, and use of existing references would be UB (sigh, but that isn't new).

But to address your point: I'm a muppet and didn't think about conditional move in a loop.

I had been thinking that you require that any conditional block has to end with a moved value int he same state - e.g.

if (a) {move(x)} else {}

The compiler would say both blocks must end with x being dead, so the else block would be required to call x.~X();

But yeah, that fails completely if you have

for (...i...) { if (i) move(x) }

because my clever rule means that x would have to die at the end of the first loop.

But the general problem I have with std::move currently is that there's no guaranteed death - the fact that post std::move a destructor will still run, or the object can just continue to be used in general means that your code has to be made defensive against already being logically dead - even if just the destructor.

Re: Move, simply

#77
post #76
post #73

Earlier quoted context omitted.

In general the compiler will not be able to prove that the value is not used again, because of possible aliasing. E.g. if you called a method on the object, then the compiler would need to be able to prove that 'this' hasn't been stashed away somewhere so it can be used again (without otherwise triggering undefined behavior) after the object has been moved out of. I.e. to have the compiler prove absence of use-after-…

Use after free (through references) is a standard problem in C++ through anything. But you can say post-move a value is dead, and use of existing references would be UB (sigh, but that isn't new). But to address your point: I'm a muppet and didn't think about conditional move in a loop. I had been thinking that you require that any conditional block has to end with a moved value int he same state - e.g. if (a) {move(…

Rust takes a conservative approach here --- you can't use x if it might have been moved along some control-flow path leading up to the current point. C++ could do that too.

> Use after free (through references) is a standard problem in C++ through anything. But you can say post-move a value is dead, and use of existing references would be UB (sigh, but that isn't new).

OK, arguably aliased-use-after-move could be treated as an existing form of UB. Sanitizers and static checkers would still need to be updated, however.

Re: Move, simply

#78
post #67

It's 2020. We have planetary scale computers that do exaflop computations and AIs that can slaughter humans at almost any strategy game. The entire stock market is essentially driven by AI. Drugs are being developed by AI. Bitcoin is consuming more electricity than Ireland. And yet programmers are still fucking around with move constructors, copy semantics, and host of other horseshit foisted upon us by people who th…

> exaflop computations

Most certainly written in c++

> and AIs that can slaughter humans at almost any strategy game.

Guess in what language is trnsorflow written

> Bitcoin

Ditto

Re: Move, simply

#79
post #46

Earlier quoted context omitted.

So, what do you propose std::move should do? Initializing the ‘source’ int to zero seems the only reasonable option, but it would hurt performance. Also, what about std::move in general? If T has a destructor, should that call it on the original? Currently, deciding whether that (or doing its equivalent) is a good idea is up to the implementer of std::move . I don’t see how the compiler could make that decision witho…

std::move has been part of the language for 9 years. It is no longer reasonable to change std::move. To satisfy the programmer, there probably should be a std::dmove(x), which would be a compiler-enforced notation that would make use of x illegal after the use of std::dmove. std::dmove(x) would be implemented as std::move, followed by a destructor, and then the compiler removes variable x from the scope. Pointers and…

But the whole point of std::move is to avoid calling, possibly expensive, destructors.

The standard example is

  template
  void swap(T& x, T& y)
  {
    T z = std::move(x);
    x = std::move(y);
    y = std::move(z);
  }
  ...
  std::string a[1000];
  ...
  std::swap(&a[2], &a[7])
If std::move copy-constructs and destroys, that last line would do three allocations and three frees (recoverable by a significantly advanced compiler, but who has that?). The way std::move is specced, it can do zero allocations and zero frees.

Re: Move, simply

#80
This is what I like about Rust: there are no copy constructors. There are no "moved-from" object values. Moves are guaranteed to be nothing more than a shallow `memcpy`. Structs aren't even copyable unless the author explicitly says so, so if a move compiles, I know it's safe and efficient.
Post reply on HN