Search code examples
pattern-matchingrustmove

Why is this struct not moved after pattern matching?


Based on Preventing move semantics during pattern matching, my understanding is when I do a match on a struct, if not using a reference to do the match, the struct will be moved since it is not a primitive type. To test this, I implemented the following:

struct Point {
    x: i32,
    y: i32,
}

let origin = Point { x: 0, y: 0 };

match origin {
    Point { x: x1, y: y1 } => println!("({},{})", x1, y1),
}

// to check if the origin has been moved, and it seems not, why ?? 
match origin {
    Point { x: x1, y: y1 } => println!("({},{})", x1, y1),
}

The output is (0,0) (0,0), which means the original struct is still there. Shouldn't it have been moved after the first match?


Solution

  • What matters is not the type of the value being matched, but rather the type of each value being bound on each match arm.

    In your struct Point, the fields x and y are of type i32. This type implements Copy, so Rust will copy values of this type instead of moving them – this means that the original value is still considered valid. Since all the values being bound on the match arm implement Copy, it's not necessary to invalidate origin. Field access works similarly: origin.x doesn't invalidate origin when origin.x implements Copy!

    Now, if the fields were of a type that doesn't implement Copy (say, String), then that's a different story. Rust is forced to move each String from the field to the binding in the match arm. As a result, the fields in origin are invalidated. Since we can't use a value with invalidated fields, then the whole struct is invalidated as well.

    Let's spice things up a little. Consider the following code:

    struct Point {
        x: i32,
        y: String,
    }
    
    let origin = Point { x: 0, y: "zero".to_string() };
    
    match origin {
        Point { x: x1, y: _ } => println!("({},...)", x1),
    }
    
    match origin {
        Point { x: x1, y: _ } => println!("({},...)", x1),
    }
    
    match origin {
        Point { x: _, y: y1 } => println!("(...,{})", y1),
    }
    
    match origin {
        Point { x: _, y: y1 } => println!("(...,{})", y1),
    }
    

    Here, we're using the placeholder pattern (_) to say we're not interested in the value of a certain field. We could also use a wildcard pattern (.., as in Point { x: x1, .. }) to ignore all the fields that are not named in the struct pattern. In either case, it has the effect of not moving the ignored field(s).

    In the first two matches, we only bind the x field, which is of type i32. Since i32 implements Copy, origin is not invalidated, even though neither origin nor origin.y are copyable (origin.y just stays where it is).

    In the third match, we only bind the y field, which is of type String. Since String doesn't implement Copy, origin.y is moved into y1, so origin is invalidated. This causes compiler errors on the fourth match, as it tries to use origin after it has been invalidated:

    error[E0382]: use of partially moved value: `origin`
      --> <anon>:21:11
       |
    18 |         Point { x: _, y: y1 } => println!("(...,{})", y1),
       |                          -- value moved here
    ...
    21 |     match origin {
       |           ^^^^^^ value used here after move
       |
       = note: move occurs because `origin.y` has type `std::string::String`, which does not implement the `Copy` trait
    
    error[E0382]: use of moved value: `origin.y`
      --> <anon>:22:26
       |
    18 |         Point { x: _, y: y1 } => println!("(...,{})", y1),
       |                          -- value moved here
    ...
    22 |         Point { x: _, y: y1 } => println!("(...,{})", y1),
       |                          ^^ value used here after move
       |
       = note: move occurs because `origin.y` has type `std::string::String`, which does not implement the `Copy` trait