Search code examples
rustmove-semantics

How does Rust provide move semantics?


The Rust language website claims move semantics as one of the features of the language. But I can't see how move semantics are implemented in Rust.

Rust boxes are the only place where move semantics are used.

let x = Box::new(5);
let y: Box<i32> = x; // x is 'moved'

The above Rust code can be written in C++ as

auto x = std::make_unique<int>(5);
auto y = std::move(x); // Note the explicit move

As far as I know (correct me if I'm wrong),

  • Rust doesn't have constructors at all, let alone move constructors.
  • No support for rvalue references.
  • No way to create functions overloads with rvalue parameters.

How does Rust provide move semantics?


Solution

  • I think it's a very common issue when coming from C++. In C++ you are doing everything explicitly when it comes to copying and moving. The language was designed around copying and references. With C++11 the ability to "move" stuff was glued onto that system. Rust on the other hand took a fresh start.


    Rust doesn't have constructors at all, let alone move constructors.

    You do not need move constructors. Rust moves everything that "does not have a copy constructor", a.k.a. "does not implement the Copy trait".

    struct A;
    
    fn test() {
        let a = A;
        let b = a;
        let c = a; // error, a is moved
    }
    

    Rust's default constructor is (by convention) simply an associated function called new:

    struct A(i32);
    impl A {
        fn new() -> A {
            A(5)
        }
    }
    

    More complex constructors should have more expressive names. This is the named constructor idiom in C++


    No support for rvalue references.

    It has always been a requested feature, see RFC issue 998, but most likely you are asking for a different feature: moving stuff to functions:

    struct A;
    
    fn move_to(a: A) {
        // a is moved into here, you own it now.
    }
    
    fn test() {
        let a = A;
        move_to(a);
        let c = a; // error, a is moved
    }
    

    No way to create functions overloads with rvalue parameters.

    You can do that with traits.

    trait Ref {
        fn test(&self);
    }
    
    trait Move {
        fn test(self);
    }
    
    struct A;
    impl Ref for A {
        fn test(&self) {
            println!("by ref");
        }
    }
    impl Move for A {
        fn test(self) {
            println!("by value");
        }
    }
    fn main() {
        let a = A;
        (&a).test(); // prints "by ref"
        a.test(); // prints "by value"
    }