26

A custom type by default is moved through default assignment. By implementing the Copy trait, I get "shallow copy semantics" through default assignment. I may also get "deep copy semantics" by implementing the Clone trait.

Is there a way to force a move on a Copy type?

I tried using the move keyword and a closure (let new_id = move || id;) but I get an error message. I'm not into closures yet, but, from seeing them here and there, I thought that that would have worked.

nbro
  • 15,395
  • 32
  • 113
  • 196
Noein
  • 522
  • 1
  • 6
  • 16
  • Precisely making/marking the moved-from variable as 'uninitialized', as it were. As in, if I force a move from a `Copy` type, I make the source variable 'empty' and unusable with the value that the target got from it. Hope I'm getting this across correctly ^^' – Noein Jul 01 '15 at 18:22
  • 1
    There's no benefit to leaving something as uninitialized, other than trying to open up holes in your program. I think you need to tell us *why* you want to do the things you want to do, as it doesn't make any sense. :-) – Shepmaster Jul 01 '15 at 18:24
  • But uninit'ed vars are already guarded against in Rust. I just want to sometimes say "yeah, this type is `Copy`, but I really don't need this value in this variable anymore. This function takes an arg by val, just take it.", then, after calling the function or whatever, attempting to reuse the identifier without giving it another value errors at compile-time. That's it. – Noein Jul 01 '15 at 18:27
  • yeah I'm kinda with @Narfanar here. If there was no copy/clone implemented, rust would move by default. It should be possible to force that exact semantic even if there is an implementation of copy/clone. Seems very non-visible to not know what the compiler's going to do by looking at a line of code, because you have to magically know if the clone is implemented as to whether it will leave the original usable or not. – stu Dec 31 '19 at 15:12

6 Answers6

48

I don't really understand your question, but you certainly seem confused. So I'll address what seems to be the root of this confusion:

The C++ notions of copy/move I think I get correctly, but this 'everything is a memcpy anyway' is, well, it hasn't been very intuitive any time I read it

When thinking about Rust's move semantics, ignore C++. The C++ story is way more complicated than Rust's, which is remarkably simple. However, explaining Rust's semantics in terms of C++ is a mess.

TL;DR: Copies are moves. Moves are copies. Only the type checker knows the difference. So when you want to "force a move" for a Copy type, you are asking for something you already have.

So we have three semantics:

  • let a = b where b is not Copy
  • let a = b where b is Copy
  • let a = b.clone() where b is Clone

Note: There is no meaningful difference between assignment and initialization (like in C++) - assignment just first drops the old value.

Note: Function call arguments work just like assignment. f(b) assigns b to the argument of f.


First things first.

The a = b always performs a memcpy.

This is true in all three cases.

  • When you do let a = b, b is memcpy'd into a.
  • When you do let a = b.clone(), the result of b.clone() is memcpy'd into a.

Moves

Imagine b was a Vec. A Vec looks like this:

{ &mut data, length, capacity }

When you write let a = b you thus end up with:

b = { &mut data, length, capacity }
a = { &mut data, length, capacity }

This means that a and b both reference &mut data, which means we have aliased mutable data.

The type-system doesn't like this so says we can't use b again. Any access to b will fail at compile-time.

Note: a and b don't have to alias heap data to make using both a bad idea. For example, they could both be file handles - a copy would result in the file being closed twice.

Note: Moves do have extra semantics when destructors are involved, but the compiler won't let you write Copy on types with destructors anyway.


Copies

Imagine b was an Option<i32>. An Option<i32> looks like this:

{ is_valid, data }

When you write let a = b you thus end up with:

b = { is_valid, data }
a = { is_valid, data }

These are both usable simultaneously. To tell the type system that this is the case, one marks Option<i32> as Copy.

Note: Marking something copy doesn't change what the code does. It only allows more code. If you remove a Copy implementation, your code will either error or do exactly the same thing. In the same vein, marking a non-Copy type as Copy will not change any compiled code.


Clones

Imagine you want to copy a Vec, then. You implement Clone, which produces a new Vec, and do

let a = b.clone()

This performs two steps. We start with:

b = { &mut data, length, capacity }

Running b.clone() gives us an additional rvalue temporary

b = { &mut data, length, capacity }
    { &mut copy, length, capacity } // temporary

Running let a = b.clone() memcpys this into a:

b = { &mut data, length, capacity }
    { &mut copy, length, capacity } // temporary
a = { &mut copy, length, capacity }

Further access of the temporary is thus prevented by the type system, since Vec is not Copy.


But what about efficiency?

One thing I skipped over so far is that moves and copies can be elided. Rust guarantees certain trivial moves and copies to be elided.

Because the compiler (after lifetime checking) sees the same result in both cases, these are elided in exactly the same way.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • Huh, great answer! I think I get it now. Though `clone`, after a bit of playing around, now seems like the magical bit in all of this. I tried to impl it for a local type the same way it's impl'd [here](http://doc.rust-lang.org/src/core/clone.rs.html#63-67) but it errors because I'm trying to pass by value a non-copy type that I'm only borrowing. So, where *could* the cloning be happening for any complex type?! (note: not a bug in my setup; `#[derive(...)]` works.) – Noein Jul 01 '15 at 21:10
  • That `Clone` implementation just redirects to `Copy`. If your type is not copyable, you're going to have to be a bit cleverer about it. `#[derive(clone)]` will `Clone` each struct member, for example. – Veedrac Jul 01 '15 at 21:14
  • But... Huh... http://is.gd/r0OCcl ?! It looks like `Copy` depends on `Clone`, if I'm reading this correctly. – Noein Jul 01 '15 at 21:17
  • 1
    @Noein `Copy` requires `Clone` because there's no reason to implement just `Copy`. If someone wants to accept `T: Clone`, it'd be silly if they couldn't accept `Copy` types too. The compiler error is just to stop people forgetting. – Veedrac Jul 01 '15 at 21:21
11

Wrap the copyable type in another type that doesn't implement Copy.

struct Noncopyable<T>(T);

fn main() {
    let v0 = Noncopyable(1);
    let v1 = v0;
    println!("{}", v0.0); // error: use of moved value: `v0.0`
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
6

New Answer

Sometimes I just want it to scream at me "put a new value in here!".

Then the answer is "no". When moving a type that implements Copy, both the source and destination will always be valid. When moving a type that does not implement Copy, the source will never be valid and the destination will always be valid. There is no syntax or trait that means "let me pick if this type that implements Copy acts as Copy at this time".

Original Answer

I just want to sometimes say "yeah, this type is Copy, but I really don't need this value in this variable anymore. This function takes an arg by val, just take it."

It sounds like you are trying to do the job of the optimizer by hand. Don't worry about that, the optimizer will do that for you. This has the benefit of not needing to worry about it.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    No, no. Sometimes I just want it to scream at me "put a *new* value in here!". Certainly not trying to optimize; esp. given that I still don't get the technical difference between `Copy` and `!Copy` under-the-hood (i.e. "everything is a memcpy anyway; just sometimes shallow and sometimes deep, but there's little intuition about it."). – Noein Jul 01 '15 at 18:37
  • 3
    Right. An answer's an answer even if it's not to the asker's heart :/ – Noein Jul 01 '15 at 18:44
  • 1
    @Noein true, but chances are that you have a real question that prompted this question. I'd like to avoid the [XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Shepmaster Jul 01 '15 at 18:46
  • 1
    Maybe the original question was just a reaction to "Copy" seeming like it would be a slow thing. If I copy a file it takes awhile, but if I move a file it's fast. So by trying to prevent copying I might be trying to go for a speed increase (though the answers and comments make it clear there is none to be had) – jocull Jul 01 '15 at 19:50
  • so perhaps you can explain how one is supposed to divine what's going on? I'm a rust newbie and I thought the point was to be as clear and as exact as possible, to avoid pitfalls at runtime. From what you're saying, I can look at a line of code that does an assignment and have no idea if it's doing a copy and leaving the original intact or doing a move and making the original unusable. By having some syntactic marker, the author would be able to make it clear in the code that it was specifically being moved. – stu Dec 31 '19 at 15:16
  • @stu One of the reasons that the `Copy` trait exists is because leaving something unusable doesn't make sense for certain types, such as plain integers or booleans. While it's true that you might not be able to look at a line and tell, the compiler will quickly provide an error stating that you are trying to use a moved non-`Copy` value, so you don't have to do any thinking yourself. – Shepmaster Jan 01 '20 at 15:39
6

Moves and copies are basically just the same runtime operation under the covers. The compiler inserts code to make a bitwise copy from the first variable's address into the second variable's address. In the case of a move, the compiler also invalidates the first variable so that if it subsequently used it will be a compile error.

Even so, I think there would be still be validity if Rust language allowed a program to say the assignment was an explicit move instead of a copy. It could catch bugs by preventing inadvertant references to the wrong instance. It might also generate more efficient code in some instances if the compiler knows you don't need two copies and could jiggle the bindings around to avoid the bitwise copy.

e.g. if you could state a = move assignment or similar.

let coord = (99.9, 73.45);
let mut coord2 = move coord;
coord2.0 += 100.0;
println!("coord2 = {:?}", coord2);
println!("coord = {:?}", coord); // Error
locka
  • 5,809
  • 3
  • 33
  • 38
1

At runtime, copies and moves, in Rust, have the same effect. However, at compile-time, in the case of a move, the variable which an object is moved from is marked as unusable, but not in the case of a copy.

When you're using Copy types, you always want value semantics, and object semantics when not using Copy types.

Objects, in Rust, don't have a consistent address: the addresses often change between moves because of the runtime behavior, i.e. they are owned by exactly one binding. This is very different from other languages!

nbro
  • 15,395
  • 32
  • 113
  • 196
  • 2
    It would seem to a Rust novice such as myself that when you say "object's don't have a consistent address" because of the runtime, this would be a lot less efficient by having extra allocations and copying happening to ensure only one binding owns the memory. – johnbakers May 07 '18 at 02:55
1

In Rust when you use (or move, in Rust's terms) a value that is Copy, the original value is still valid. If you want to simulate the case that like other non-copyable values, to invalidate after a specific use, you can do:

let v = 42i32;
// ...
let m = v; 
// redefine v such that v is no longer a valid (initialized) variable afterwards
// Unfortunately you have to write a type here. () is the easiest,
// but can be used unintentionally.
let v: (); 
// If the ! type was stabilized, you can write
let v: !;
// otherwise, you can define your own:
enum NeverType {};
let v: NeverType;
// ...

If you later change v to something that is not Copy, you don't have to change the code above to avoid using the moved value.

Correction on some misunderstanding on the question

  • The difference between Clone and Copy is NOT "shallow copy" and "deep copy" semantics. Copy is "memcpy" semantics and Clone is whatever the implementors like, that is the only difference. Although, by definition, things which require a "deep copy" are not able to implement Copy.

  • When a type implements both Copy and Clone, it is expected that both have the same semantics except that Clone can have side effects. For a type that implements Copy, its Clone should not have "deep copy" semantics and the cloned result is expected to be the same as a copied result.

  • As an attempt, if you want to use the closure to help, you probably wanted to run the closure, like let new_id = (move || id)();. If id is copy then id is still valid after the move, so this does not help, at all.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Earth Engine
  • 10,048
  • 5
  • 48
  • 78
  • *both have the same semantics except* — I disagree: if something implements `Copy`, then `Clone` should **always** delegate to the `Copy` implementation. There is no good reason to have `Clone` do something different from `Copy`. – Shepmaster Jun 27 '18 at 02:12
  • Emm... What about a `println!` invocation? This is what I said "side-effects". – Earth Engine Jun 27 '18 at 02:16