Mover la semántica en Rust

Nilesh Katuwal 7 junio 2022
Mover la semántica en Rust

En este artículo, aprenderemos sobre la semántica de movimiento en Rust.

Mover la semántica en Rust

En Rust, todos los tipos son mutables y todas las operaciones de movimiento equivalen a una copia de bits de los datos originales en la nueva ubicación. Mover convierte cualquier variable capturada por referencia o referencia mutable en variables capturadas por valor.

Cuando hay subprocesos involucrados, el movimiento se usa comúnmente.

Los procesos de eliminación de Rust también son destructivos. Por ejemplo, después de dejar una variable, la variable ya no se puede utilizar en el código.

En Rust, un movimiento se implementa consistentemente copiando los datos en el objeto mismo y no destruyendo el objeto original. Nunca se implementa copiando los recursos administrados del objeto o ejecutando código personalizado.

Rust hace una excepción para los tipos que no requieren semántica de movimiento. En su lugar, el valor contiene toda la información necesaria para representarlo, y el valor no administra asignaciones de montón ni recursos, como i32.

Estos tipos implementan el rasgo especial Copiar porque copiar es económico y el método predeterminado para pasar a funciones o administrar asignaciones:

fn foo(bar: i32) {
    // Implementation
}

let var: i32 = 9;
foo(var); // copy
foo(var); // copy
foo(var); // copy

La llamada de función predeterminada utiliza semántica de movimiento para tipos distintos de Copy, como Cadena. En Rust, cuando se mueve una variable, su vida útil finaliza prematuramente.

En tiempo de compilación, el movimiento reemplaza la llamada al destructor al final del bloque, por lo que es un error escribir el siguiente código para String:

fn foo(bar: String) {
    // Implementation
}

let var: String = "Hello".to_string();
foo(var); // Move
foo(var); // Compile-Time Error
foo(var); // Compile-Time Error

Clone rust requiere implementaciones, pero es idéntico para todos los movimientos: copie la memoria en el valor y no llame al destructor del valor original.

Y en Rust, con esta implementación exacta, todos los tipos son móviles; los tipos inmóviles no existen (aunque sí los valores inmóviles). Los bytes deben codificar información sobre el recurso que administra el valor, como un puntero, en la nueva ubicación con la misma eficacia que en la ubicación anterior.

Rust tiene un mecanismo conocido como pinning que indica en el sistema de tipos que un valor específico nunca volverá a moverse, que se puede usar para implementar valores autorreferenciales y se utiliza de forma asíncrona.

Ejemplo:

fn destroy_box(z: Box<i32>) {
    println!("The box that contains {} is destroyed", z);
}

fn main() {
    let a = 5u32;
    let b = a;
    println!("a is {}, and y is {}", a, b);

    let m = Box::new(5i32);

    println!("a contains: {}", m);
    let n = m;
    destroy_box(n);
}

La función destroy_box toma posesión de la memoria asignada en montón en el código fuente anterior. Primero, se destruye la z y se libera la memoria.

m es un puntero a un número entero asignado al montón.

Producción :

a is 5, and y is 5
a contains: 5
The box that contains 5 is destroyed