Move Semantics

This section describes how values are moved and copied in Rue.

Value Categories

Types in Rue are categorized by how they behave when used:

  • Copy types can be implicitly duplicated when used. Using a Copy type does not consume the original value.
  • Move types (also called affine types) are consumed when used. After using a move type value, the original binding becomes invalid.

The following types are Copy types:

  • All integer types (i8, i16, i32, i64, u8, u16, u32, u64)
  • The boolean type (bool)
  • The unit type (())
  • Enum types (all variants of an enum)

User-defined struct types are move types by default. Using a struct value consumes it.

struct Point { x: i32, y: i32 }

fn main() -> i32 {
    let p = Point { x: 1, y: 2 };
    let q = p;      // p is moved to q
    // p is no longer valid here
    q.x + q.y
}

The @copy Directive

A struct type may be declared as a Copy type using the @copy directive before the struct definition.

copy_struct = "@copy" struct_def ;

A struct marked with @copy is a Copy type. Using a @copy struct value does not consume it; the value is implicitly duplicated.

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

fn main() -> i32 {
    let p = Point { x: 1, y: 2 };
    let q = p;      // p is copied, not moved
    let r = p;      // p can be used again
    p.x + q.x + r.x // all three are valid
}

A @copy struct must contain only fields that are themselves Copy types. It is a compile-time error to mark a struct as @copy if any of its fields are move types.

struct Inner { value: i32 }  // move type (no @copy)

@copy
struct Outer { inner: Inner }  // ERROR: field 'inner' has non-Copy type 'Inner'

A @copy struct may contain fields of primitive Copy types (integers, booleans, unit), enum types, or other @copy struct types.

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

@copy
struct Rect { top_left: Point, bottom_right: Point }  // OK: Point is @copy

fn main() -> i32 {
    let r = Rect {
        top_left: Point { x: 0, y: 0 },
        bottom_right: Point { x: 10, y: 10 }
    };
    let r2 = r;     // r is copied
    r.top_left.x    // r is still valid
}

Use After Move

It is a compile-time error to use a value that has been moved.

struct Point { x: i32, y: i32 }

fn main() -> i32 {
    let p = Point { x: 1, y: 2 };
    let q = p;      // p is moved
    let r = p;      // ERROR: use of moved value 'p'
    0
}

A value is considered moved when it is:

  • Assigned to another variable
  • Passed as an argument to a function
  • Returned from a function
struct Data { value: i32 }

fn consume(d: Data) -> i32 { d.value }

fn main() -> i32 {
    let d = Data { value: 42 };
    let result = consume(d);  // d is moved into the function
    // d is no longer valid here
    result
}

Copy Types and Multiple Uses

Copy types can be used multiple times without being consumed.

fn main() -> i32 {
    let x = 42;
    let a = x;  // x is copied
    let b = x;  // x is copied again
    a + b       // 84
}

Function parameters of Copy types receive a copy of the argument. Function parameters of move types receive ownership of the argument.

Shadowing and Moves

Shadowing a variable does not prevent it from being moved. A moved variable remains invalid even if a new variable with the same name is introduced in an inner scope.

struct Data { value: i32 }

fn main() -> i32 {
    let d = Data { value: 1 };
    let x = d;  // d is moved
    {
        let d = Data { value: 2 };  // New 'd' shadows, but doesn't restore old 'd'
        d.value
    }
    // Original 'd' is still invalid here
}