Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force a move of a type which implements the Copy trait?

Tags:

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.

like image 728
Noein Avatar asked Jul 01 '15 18:07

Noein


People also ask

Does Option implement copy trait?

Option<&mut T> can't implement Clone or Copy because &mut T doesn't, for lifetime reasons. If the compiler supported some special trait, say BorrowCopyOfPointer , then Option<&mut T> could implement that, as could other wrapper types ( &mut T would implicitly implement it).

Are references copy Rust?

Yes, this is correct. In Rust terms, &T is Copy , which means that it can be copied bitwise without transferring ownership.

What is move in Rust?

When doing assignments ( let x = y ) or passing function arguments by value ( foo(x) ), the ownership of the resources is transferred. In Rust-speak, this is known as a move. After moving resources, the previous owner can no longer be used.

How do you duplicate string in Rust?

The only correct way to copy a String is to allocate a new block of heap memory to copy all the characters into, which is what String 's Clone implementation does. https://doc.rust-lang.org/book/second-edition/ch04-01-what-is-ownership.html covers these topics in much more detail.


1 Answers

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.

like image 111
Veedrac Avatar answered Oct 19 '22 03:10

Veedrac