Copy and move constructors

In both C++ and Rust, one rarely has to write copy or move constructors (or their Rust equivalents) by hand. In C++ this is because the implicit definitions are good enough for most purposes, especially when using smart pointers (i.e., following the rule of zero). In Rust this is because move semantics are the default, and the automatically derived implementations of the Clone and Copy traits are good enough for most purposes.

For the following C++ classes, the implicitly defined copy and move constructors are sufficient. The equivalent in Rust uses a derive macro provided by the standard library to implement the corresponding traits.

#include <memory>
#include <string>

struct Age {
  unsigned int years;

  Age(unsigned int years) : years(years) {}

  // copy and move constructors and destructor
  // implicitly declared and defined
};

struct Person {
  Age age;
  std::string name;
  std::shared_ptr<Person> best_friend;

  Person(Age age,
         std::string name,
         std::shared_ptr<Person> best_friend)
      : age(age), name(name),
        best_friend(best_friend) {}

  // copy and move constructors and destructor
  // implicitly declared and defined
};
#![allow(unused)]
fn main() {
use std::rc::Rc;

#[derive(Clone, Copy)]
struct Age {
    years: u32,
}

#[derive(Clone)]
struct Person {
    age: Age,
    name: String,
    best_friend: Rc<Person>,
}
}

User-defined constructors

On the other hand, the following example requires a user-defined copy and move constructor because it manages a resource (a pointer acquired from a C library). The equivalent in Rust requires a custom implementation of the Clone trait.

#include <cstdlib>
#include <cstring>

// widget.h
struct widget_t;
widget_t *alloc_widget();
void free_widget(widget_t *);
void copy_widget(widget_t *dst, widget_t *src);

// widget.cc
class Widget {
  widget_t *widget;

public:
  Widget() : widget(alloc_widget()) {}

  Widget(const Widget &other) : widget(alloc_widget()) {
    copy_widget(widget, other.widget);
  }

  Widget(Widget &&other) : widget(other.widget) {
    other.widget = nullptr;
  }

  ~Widget() {
    free_widget(widget);
  }
};
#![allow(unused)]
fn main() {
mod example {
mod widget_ffi {
    // Models an opaque type.
    // See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs
    #[repr(C)]
    pub struct CWidget {
        _data: [u8; 0],
        _marker: core::marker::PhantomData<(
            *mut u8,
            core::marker::PhantomPinned,
        )>,
    }

    extern "C" {
        pub fn make_widget() -> *mut CWidget;
        pub fn copy_widget(
            dst: *mut CWidget,
            src: *mut CWidget,
        );
        pub fn free_widget(ptr: *mut CWidget);
    }
}

use self::widget_ffi::*;

struct Widget {
    widget: *mut CWidget,
}

impl Widget {
    fn new() -> Self {
        Widget {
            widget: unsafe { make_widget() },
        }
    }
}

impl Clone for Widget {
    fn clone(&self) -> Self {
        let widget = unsafe { make_widget() };
        unsafe {
            copy_widget(widget, self.widget);
        }
        Widget { widget }
    }
}

impl Drop for Widget {
    fn drop(&mut self) {
        unsafe { free_widget(self.widget) };
    }
}
}
}

Just as with how in C++ it is uncommon to need user-defined implementations for copy and move constructors or user-defined implementations for destructors, in Rust it is rare to need to implement the Clone and Drop traits by hand for types that do not represent resources.

There is one exception to this. If the type has type parameters, it might be desirable to implement Clone (and Copy) manually even if the clone should be done field-by-field. See the standard library documentation of Clone and of Copy for details.

Trivially copyable types

In C++, a class type is trivially copyable when it has no non-trivial copy constructors, move constructors, copy assignment operators, move assignment operators and it has a trivial destructor. Values of a trivially copyable type are able to be copied by copying their bytes.

In the first C++ example above, Age is trivially copyable, but Person is not. This is because despite using a default copy constructor, the constructor is not trivial because std::string and std::shared_ptr are not trivially copyable.

Rust indicates whether types are trivially copyable with the Copy trait. Just as with trivially copyable types in C++, values of types that implement Copy in Rust can be copied by copying their bytes. Rust requires explicit calls to the clone method to make copies of values of types that do not implement Copy.

In the first Rust example above, Age implements the Copy trait but Person does not. This is because neither std::String nor Rc<Person> implement Copy. They do not implement Copy because they own data that lives on the heap, and so are not trivially copyable.

Rust prevents implementing Copy for a type if any of its fields are not Copy, but does not prevent implementing Copy for types that should not be copied bit-for-bit due to their intended meaning, which is usually indicated by a user-defined Clone implementation.

Rust does not permit the implementation of both Copy and Drop for the same type. This aligns with the C++ standard's requirement that trivially copyable types not implement a user-defined destructor.

Move constructors

In Rust, all types support move semantics by default, and custom move semantics cannot be (and do not need to be) defined. This is because what "move" means in Rust is not the same as it is in C++. In Rust, moving a value means changing what owns the value. In particular, there is no "old" object to be destructed after a move, because the compiler will prevent the use of a variable whose value has been moved.

Assignment operators

Rust does not have a copy or move assignment operator. Instead, assignment either moves (by transferring ownership), explicitly clones and then moves, or implicitly copies and then moves.

fn main() {
    let x = Box::<u32>::new(5);
    let y = x; // moves
    let z = y.clone(); // explicitly clones and then moves the clone
    let w = *y; // implicitly copies the content of the Box and then moves the copy
}

For situations where something like a user-defined copy assignment could avoid allocations, the Clone trait has an additional method called clone_from. The method is automatically defined, but can be overridden when implementing the Clone trait to provide an efficient implementation.

The method is not used for normal assignments, but can be explicitly used in situations where the performance of the assignment is significant and would be improved by using the more efficient implementation, if one is defined. The implementation can be made more efficient because clone_from takes ownership of the object to which the values are being assigned, and so can do things like reuse memory to avoid allocations.

#![allow(unused)]
fn main() {
fn go(x: &Vec<u32>) {
    let mut y = vec![0; x.len()];
    // ...
    y.clone_from(&x);
    // ...
}
}

Performance concerns and Copy

The decision to implement Copy should be based on the semantics of the type, not on performance. If the size of objects being copied is a concern, then one should instead use a reference (&T or &mut T) or put the value on the heap (Box<T> or Rc<T>). These approaches correspond to passing by reference, or using a std::unique_ptr or std::shared_ptr in C++.