1
votes

If you make a struct derive the Copy trait then Rust is going to make y as a copy of x in the code below, as opposed to moving from x to y otherwise:

#[derive(Debug, Copy, Clone)]
struct Foo;

let x = Foo;

let y = x;

If I were in C++ I'd say that Copy somehow makes Foo implement the = operator in a way that it copies the entire object on the right side.

In Rust, is it simply implemented as a rule in the compiler? When the compiler finds let y=x it simply checks if the Copy trait is derived or not and decides if copy or moves?

I'm intersted in Rust internals so I can understand the language better. This information can't be found on tutorials.

3

3 Answers

1
votes

Yes, this is directly implemented in the compiler.

It affects any situation that would otherwise move the item, so it also affects passing parameters to functions or matching in a match expression – basically any situation that involves pattern matching. In that way, it's not really comparable to implementing the = operator in C++.

The definition of the Copy trait is marked as a "lang" item in the source code of the standard library. The compiler knows that the item marked with #[lang = "copy"] is the trait that decides whether a type is moved or copied. The compiler also knows some ruls about types that are implicitly Copy, like closures or tuples that only contain items that are Copy.

1
votes

In Rust, is it simply implemented as a rule in the compiler? When the compiler finds let y=x it simply checks if the Copy trait is derived or not and decides if copy or moves?

At runtime, there is no semantic difference (though the applicable optimisations might vary), both move and copy are just a memcopy, and in either case the copy can be optimised away.

At compile-time, the compile is indeed aware of the Copy/!Copy distinction: in the case where x would be a !Copy type the assignment "consumes" the variable, meaning you can't use it afterwards.

If the item is Copy then it doesn't and you can.

That's about it.

1
votes

If you want to dig into how this code is compiled, you could take a look at the MIR representation in the playground. In this slightly simplified version:

#[derive(Copy, Clone)]
struct Foo;

fn main() {
    let x = Foo;

    let y = x;
}

the slightly trimmed MIR output is:

    bb0: {
        StorageLive(_1);
        _1 = const Scalar(<ZST>): Foo;

        StorageLive(_2);
        _2 = const Scalar(<ZST>): Foo;

        StorageDead(_2);
        StorageDead(_1);
        return;
    }

So in this specific case, the compiler has determined that Foo is a Zero Sized Type (ZST) for x and y (here, _1 and _2), so both are assigned a constant empty value, so there isn't any copying as-such.

To see it in the playground, click here then select "MIR" from the drop down triple dots button just to the right of the "Run" button. For more in depth information about MIR, take a look at the rustc dev guide.