# Move Semantics Rust vs C++

**URL:** <https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274>\
**Category:** help\
**Created:** [June 18, 2021, 9:32pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274 "2021-06-18T21:32:25Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![SimonMaracine](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/simonmaracine/32/16943_2.png) [@SimonMaracine](https://users.rust-lang.org/u/SimonMaracine)\
**Post date:** [June 18, 2021, 9:32pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/1 "2021-06-18T21:32:25Z")

</div>

I have three questions regarding move semantics in Rust.

Not long ago I learned what move semantics are in C++ and I realized that move semantics happen almost all the time in Rust and it happens by default. For what I understand, move semantics in C++ have the purpose of reducing deep copying, or reducing memory allocations on the heap by transferring ownership from one variable to another. I understand that when you use the `=` operator in C++, there is always something copied. (I'm talking about **debug** builds only for now.)

**Let me give an example in C++ first:**

```rust
struct MyStruct {
    int a;
    float b;
    bool c;
    float* d;

    MyStruct() : a(0), b(0.0f), c(false) {
        d = new float[10];
    }

    ~MyStruct() { delete[] d; }

    MyStruct(MyStruct& other) {
        a = other.a;
        b = other.b;
        c = other.c;
        d = new float[10];
        memcpy(d, other.d, sizeof(float) * 10);
    }

    MyStruct(MyStruct&& other) {
        a = other.a;
        b = other.b;
        c = other.c;
        d = other.d;
        // For what I understand, this line is the big thing that makes move assignments possible
        other.d = nullptr;
    }
};

```

```rust
int main() {
    MyStruct ms;
    // Deep copy, so it's like clone() in Rust
    MyStruct copy = ms;

    // Both structs are valid
}

```

**Now I want to make a move, not a copy:**

```rust
int main() {
    MyStruct ms;
    MyStruct move = std::move(ms);

     // ms is invalid now
}

```

For what I undestand, moving `ms` into `copy` means **shallow copying** the `ms` struct including the pointer with the heap data and setting the `ms`'s pointer to `nullptr`. This means that when `ms` is dropped, `delete[]` does nothing but a check for null.

**Now, in Rust this is almost the same example:**

```rust
#[derive(Clone)]
struct MyStruct {
    a: i32,
    b: f32,
    c: bool,
    d: Vec<f32>,
}

```

```rust
fn main() {
    let ms = MyStruct { a: 19, b: 3.141, c: true, d: Vec::new() };
    let copy = ms.clone();

    // Both structs are valid
}

```

**With move:**

```rust
fn main() {
    let ms = MyStruct { a: 19, b: 3.141, c: true, d: Vec::new() };
    let move = ms;

    // ms is invalid now
}

```

My question is: **How do move semantics actually work in Rust? In the same way as in C++?**.

Since the compiler knows that `ms` is invalid after the move, what I believe is that the compiler can do some small optimizations by not calling the deallocator on `ms`'s heap data pointer in the `Vec`, because that heap data is moved. In C++, when the old variable is destroyed, the destructor is called and `delete` is called, which means a function call and an if statement inside that function. Rust should be free to not call any function. This means that, technically, Rust is more performant that C++. (C++ is slower than Rust by a function call and an if statement, not a big deal anyway.) My second question: **Is this assumption true? Does rustc make these optimizations?**

And one more thing. In C++, I think that doing a move is useless when you have a struct with no heap data, because it will do a shallow copy anyway, so it's the same as a deep copy:

```rust
struct MyStruct {
    long a1;
    long a2;
    long a3;
    // ... Lots of fields
};

```

```rust
struct MyStruct {
    a1: i64,
    a2: i64,
    a3: i64,
    // ... Lots of fields
}

```

And my third question, which is tied to the first one: **When you do a move in Rust, does it do a shallow copy? Even on large structs?**. I think not.

As I write this, I realize that maybe the second and third questions are irrelevant, because the code will anyway be optimized in release builds. I hope that I was clear enough... And I hope that this post helps a bit.

---

<div class="post-metadata">

**Author:** ![alice](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/alice/32/9777_2.png) [@alice](https://users.rust-lang.org/u/alice)\
**Post date:** [June 18, 2021, 9:37pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/2 "2021-06-18T21:37:20Z")

</div>

When a value is moved in Rust, that _always_ happens via a simple `memcpy` of the bytes containing the value. The old value is not cleared or zeroed — it just isn't read again, and no destructor will run on it. The copy that happens inside a move is always shallow since if there are any pointers in the moved value, the `memcpy` will just copy the pointer but not look inside it.

---

<div class="post-metadata">

**Author:** ![droundy](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/droundy/32/1129_2.png) [@droundy](https://users.rust-lang.org/u/droundy)\
**Post date:** [June 19, 2021, 2:21am UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/3 "2021-06-19T02:21:06Z")

</div>

> [@SimonMaracine](#):
>
> I understand that when you use the `=` operator in C++, there is always something copied.

This is incorrect. In C++ = is an operator that can be overloaded, so it can do literally anything (subject to the capabilities of the computer). In contrast, = in rust is a language feature that always does what @alice said.

If you want a deep copy in rust, there is the Clone trait. However, since that is a trait (like = in C++) it could do literally anything. But normally it just created a deep copy.

---

<div class="post-metadata">

**Author:** ![SimonMaracine](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/simonmaracine/32/16943_2.png) [@SimonMaracine](https://users.rust-lang.org/u/SimonMaracine)\
**Post date:** [June 19, 2021, 9:04pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/4 "2021-06-19T21:04:42Z")

</div>

> [@droundy](#):
>
> This is incorrect.

I know that `=` can be overloaded so that it doesn't do what it traditionally does. I wanted to say that `=` always does a copy by default, not when overloaded.

---

<div class="post-metadata">

**Author:** ![SimonMaracine](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/simonmaracine/32/16943_2.png) [@SimonMaracine](https://users.rust-lang.org/u/SimonMaracine)\
**Post date:** [June 19, 2021, 9:07pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/5 "2021-06-19T21:07:04Z")

</div>

Ok. So this means that Rust is indeed a little more efficient than C++ in this regard. Right?

---

<div class="post-metadata">

**Author:** ![SimonMaracine](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/simonmaracine/32/16943_2.png) [@SimonMaracine](https://users.rust-lang.org/u/SimonMaracine)\
**Post date:** [June 19, 2021, 9:09pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/6 "2021-06-19T21:09:13Z")

</div>

> [@alice](#):
>
> When a value is moved in Rust, that _always_ happens via a simple `memcpy` of the bytes containing the value.

Is this true also with release builds? What if I have a large struct that I want to pass to a function by moving it there? I read somewhere that Rust will pass it by reference in the background for performance reasons.

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/steffahn/32/47569_2.png) [@steffahn](https://users.rust-lang.org/u/steffahn)\
**Post date:** [June 19, 2021, 9:41pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/7 "2021-06-19T21:41:56Z")

</div>

> [@SimonMaracine](#):
>
> Rust is indeed a little more efficient than C++ in this regard. Right?

Yes. Not only with regards to moving. Since Rust’s move semantics _don’t_ require to leave the moved-out-of value in some sort of null-like state (I think they always talk about leaving it in an “undefined but valid” state for C++ which often is a null-like state though), you can use types that _don’t_ have any null-state. E.g. a smart pointer like `Box<T>` doesn’t have any null state, so you can do dereferencing on it without any null-checks. Admitted, not doing null checks is probably something you’ll sometimes do in C++ as well, but in those cases it’s a UB footgun; honestly I’m not too much into C++ in the first place, so correct me if I’m wrong but I’d guess that “proper” handling of pointer types or types containing pointers usually _does_ include some sort of null-check for most operations.

And then there’s also the benefit that you can use the 0 value that’s never used to make other data types containing `Box<T>` more compact, the canonical example being `Option<Box<T>>` which is kind-of Rust’s version of a nullable (owning) pointer, and still has `size_of::<Option<Box<T>>() == size_of::<usize>()`.

* * *

By the way, I’d like to reiterate this code comparison:

> [@SimonMaracine](#):
>
> ```rust
> // Deep copy, so it's like clone() in Rust
> MyStruct copy = ms;
> 
> ```

> [@SimonMaracine](#):
>
> ` let copy = ms.clone();`

In my opinion deep copying is _wayyy too easy and implicit_ in C++. I’m honestly questioning how you can ever be sure that your reasonably sized C++ codebases don’t contain lots of significantly expensive, unintentional deep copies. I’d be curious to hear on that from people that actually use C++ though, since I’m not one of them. Anyway, one general principle in Rust is that allocations, in particular from deep copies, are supposed to always be quite clearly visible / should not happen implicitly or on accident.

While moves being slightly more efficient in Rust may be a rather small advantage, the explicitness of `.clone()` seems like a possibly way more relevant performance benefit in Rust.

---

<div class="post-metadata">

**Author:** ![alice](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/alice/32/9777_2.png) [@alice](https://users.rust-lang.org/u/alice)\
**Post date:** [June 19, 2021, 10:05pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/8 "2021-06-19T22:05:44Z")

</div>

The compiler can of course optimize moves in various ways. However, it is guaranteed to behave as if it was moved with a memcpy.

---

<div class="post-metadata">

**Author:** ![trentj](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/trentj/32/5690_2.png) [@trentj](https://users.rust-lang.org/u/trentj)\
**Post date:** [June 19, 2021, 10:10pm UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/9 "2021-06-19T22:10:08Z")

</div>

> [@SimonMaracine](#):
>
> I know that `=` can be overloaded so that it doesn't do what it traditionally does. I wanted to say that `=` always does a copy by default, not when overloaded.

🤔 I think that's still not really correct. IIRC, C++'s "default" behavior for `operator=` on a struct is just to delegate to each field's `operator=`. The compiler is of course free to observe that all a struct's members are trivially copyable, and therefore optimize this default implementation of `operator=` to a single `memcpy`. But this is not guaranteed. In fact this is a way in which C++ can be more efficient than Rust; if you have a data structure with 100 fields of which only 1 has contents that matter, you can overload `operator=` to only copy the one field and skip the rest. If you do that, it'll also be the behavior used by everything that contains that struct (unless you overload `operator=` again).

In Rust, this is more or less reversed: `=` always does a `memcpy` (or equivalent), so it is guaranteed that no type will be _more_ work to move (or copy) than just that. But on the other hand, you can't make "extra cheap" overloads like C++ has. The compiler is, of course, still free to observe that some fields are not used later and omit those copies. But it's not guaranteed.

The important difference between Rust and C++ with regard to move semantics is **destruction**. This only comes into play with types that have destructors, which, in [most well-written code](https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)), will also have overloads of `operator=`. So the "default" behavior doesn't apply in the cases you usually care about with regard to move semantics. In the cases where it does apply, i.e. for trivially copyable types, I'd expect C++ and Rust to perform the same, because identifying types that are trivially copyable is an easy compile time analysis.

Since C++ destructors "always" run, C++ types that want to have move semantics have to have a valid _moved-from state_ that may be destroyed. Usually this is indicated by zeroing out something or setting a value to some sentinel, so the main body of the destructor can be skipped at runtime. If a particular variable is always moved out of, the compiler is (again) free to observe that and optimize away the check. But it's not guaranteed. In effect, types that have move semantics in C++ are implicitly `Option`s, and you never actually move them around, you just `take` them (leaving `None`).

But in Rust destructors (i.e. drop glue) _don't_ always run. So types with move semantics and types without are just the same: it's all a `memcpy`, and only the final resting place of an object needs to be dropped when it goes out of scope. In principle this means that copying/moving things with `=` can be more efficient, and objects may take up less space with niches, as steffahn also pointed out.

But wait -- how does the compiler keep track of when to run the destructors or not? Isn't it impossible in the general case to know whether a particular piece of code will be run or not at compile time? Well, yes - and I lied. Kind of. The compiler will do its level best to figure out at compile time which things need to be dropped and which don't. But if it can't, it will generate _drop flags_ which are really just doing the same runtime liveness tracking that happens with manually written destructors in C++! The difference is that drop flags are part of the stack frame where the variable lives, and not part of the object itself, and they're emitted by the compiler, not written by you.

---

<div class="post-metadata">

**Author:** ![ZiCog](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/zicog/32/12216_2.png) [@ZiCog](https://users.rust-lang.org/u/ZiCog)\
**Post date:** [June 20, 2021, 6:40am UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/10 "2021-06-20T06:40:02Z")

</div>

> [@trentj](#):
>
> In fact this is a way in which C++ can be more efficient than Rust; if you have a data structure with 100 fields of which only 1 has contents that matter, you can overload `operator=` to only copy the one field and skip the rest.

To my mind, and I believe most C++ programmers, that is a gross misuse of operator overloading. It is making the `=` operator do something other than what one would expect. It then looks like it is making an assignment but it it is also do a serious transformation/data loos in the process.

`move` semantics in C++ are indeed very hard to reason about. Which is why C++ has tests like:  
std::is\_move\_constructible  
std::is\_move\_assignable

And a slew of others that one can use in asserts to find out of ones code is `moving` as one expects.

---

<div class="post-metadata">

**Author:** ![Hyeonu](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/hyeonu/32/7970_2.png) [@Hyeonu](https://users.rust-lang.org/u/Hyeonu)\
**Post date:** [June 20, 2021, 7:30am UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/11 "2021-06-20T07:30:56Z")

</div>

The major difference is that the move semantics is the language feature in Rust while it's the code convention in C++. In C++ the language provides 2 kinds of references(which is not a real type but still related with the type system) and [5 kinds of values](https://en.cppreference.com/w/cpp/language/value_category). It's up to programmers to implement move semantics correctly with them. If you misuse these complex tools things may not work as expected but it's your fault.

In Rust everything, including references, are passed by value. A move is defined as single shallow bitwise copy and implies ownership transfer. Moved out values are statically marked as inaccessible, even destructors can't touch them. If the type is trivial like primitive integers you can implement the Copy trait to disable this compile time marker and use moved out values, but it doesn't affect the runtime semantics.

---

<div class="post-metadata">

**Author:** ![system](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/system/32/51372_2.png) [@system](https://users.rust-lang.org/u/system)\
**Post date:** [September 18, 2021, 7:31am UTC](https://users.rust-lang.org/t/move-semantics-rust-vs-c/61274/12 "2021-09-18T07:31:29Z")

</div>

Closed on September 18, 2021, 7:31am UTC

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.
