Propiedad, alcance y movimientos

RustBeginner
Practicar Ahora

Introducción

Rust utiliza la propiedad para determinar qué parte de un programa es responsable de un valor y cuándo se puede liberar ese valor. Considere la propiedad como responsabilidad y permiso, no como una etiqueta permanente asociada a un único nombre de variable: esa responsabilidad puede trasladarse a una nueva vinculación. Esto evita varios errores de memoria sin necesidad de un recolector de basura.

En este laboratorio, primero comprobará que un entero pequeño se puede copiar. Después, pedirá intencionadamente a Rust que utilice un String que ya se ha movido, leerá el diagnóstico resultante del compilador y corregirá el flujo de datos utilizando la propiedad que devuelve una función. El código base ya está preparado, por lo que cada modificación será pequeña y concreta.

Observe el alcance y un entero copiado

En este paso, copiará un entero pequeño dentro de un alcance interno y confirmará que la vinculación original sigue disponible.

Entre en el proyecto preparado y abra el archivo de código fuente:

cd /home/labex/project/ownership-demo
nano src/main.rs

Un alcance es la región en la que una vinculación es válida. Las llaves crean un nuevo alcance interno. La vinculación local_count solo existe entre el par interior de llaves, mientras que warehouse_count pertenece al alcance circundante de main.

En la línea del paso 1, sustituya únicamente 0 por warehouse_count:

        let local_count = warehouse_count; // Step 1: replace the placeholder.

El tipo de entero i32 implementa el comportamiento Copy de Rust. Al asignarlo a local_count, se copia el valor pequeño en lugar de invalidar warehouse_count.

Guarde el archivo con Ctrl+O, pulse Enter y salga con Ctrl+X. Ejecute el programa:

cargo run --quiet

Las dos primeras líneas demuestran que ambas vinculaciones se pueden utilizar:

Local count: 3
Warehouse count: 3

Cuando termina el alcance interno, local_count deja de ser válido. warehouse_count sigue siendo válido hasta el final de main.

Lea un error de movimiento intencional

En este paso, utilizará intencionadamente un String después de que se haya movido y, a continuación, interpretará el diagnóstico del compilador de Rust. La comprobación fallida es esperada y constituye el resultado de aprendizaje de este paso.

String::from("PX-104") crea texto cuya propiedad pertenece al programa. A diferencia de un i32 pequeño, un String administra un búfer de texto asignado por separado cuyo tamaño puede cambiar. Rust asigna la responsabilidad de ese búfer a un único propietario para poder liberarlo exactamente una vez.

La llamada record_parcel(parcel_id) pasa el String por valor. La propiedad se mueve desde la vinculación parcel_id a la función. La función devuelve el valor y la llamada guarda ese propietario devuelto en _returned_id. El guion bajo inicial indica a Rust que esta vinculación temporal no se utilizará por ahora de forma intencionada.

El valor no se destruye al moverse. Solo la vinculación anterior pierde el permiso para utilizarlo. La ruta de propiedad en este programa es:

parcel_id in main → parcel_id parameter in record_parcel → _returned_id in main

En cada punto, un único propietario actual sigue siendo responsable del mismo texto.

Abra el archivo:

nano src/main.rs

Busque la línea situada debajo del comentario del paso 2 y elimine únicamente los // iniciales para que quede así:

    println!("Original after move: {parcel_id}");

Guarde el archivo y salga. Antes de ejecutar el siguiente comando, prediga el resultado: parcel_id ya no es el propietario, por lo que el compilador debería rechazar este uso.

Ejecute la comprobación. Es normal que falle:

cargo check

Busque estas partes estables del diagnóstico:

error[E0382]: borrow of moved value: `parcel_id`
...
value moved here
...
value borrowed here after move

Aparece la palabra borrow porque println! solo necesita acceso temporal para mostrar el valor. Ese acceso sigue siendo imposible a través de parcel_id, porque la propiedad ya se ha movido. En el siguiente paso, utilizará la vinculación que recibió de nuevo la propiedad desde la función.

Continúe con el propietario devuelto

En este paso, corregirá el error de movimiento asignando un nombre y utilizando el valor devuelto por record_parcel.

La firma de la función fn record_parcel(parcel_id: String) -> String indica que la función toma la propiedad de un String y devuelve un String. La expresión final parcel_id transfiere la propiedad de nuevo a quien realiza la llamada.

Abra el archivo:

nano src/main.rs

Realice dos modificaciones pequeñas. Primero, cambie _returned_id por returned_id en la línea de llamada a la función. Esto elimina el guion bajo temporal porque está a punto de utilizar el valor.

Después, sustituya la línea de impresión que provoca el error por la siguiente:

    println!("Returned owner: {returned_id}");

La vinculación anterior parcel_id sigue siendo inválida después del movimiento. Cambiar _returned_id por returned_id no recupera la vinculación anterior; simplemente asigna un nombre normal a la vinculación que ya recibió de nuevo la propiedad. Ahora returned_id es el propietario actual y, por tanto, es el nombre correcto que debe utilizar.

Guarde el archivo y salga. Después, compruebe el programa corregido:

cargo check

Un resultado Finished sin el error E0382 confirma que el flujo de propiedad es válido. Ejecute el programa:

cargo run --quiet

La salida completa debe ser:

Local count: 3
Warehouse count: 3
Recorded parcel: PX-104
Returned owner: PX-104

Al final de main, returned_id sale de su alcance y Rust libera su String del que es propietario. Sigue habiendo un único propietario responsable de esos datos.

Resumen

Relacionó el alcance léxico con la vida útil de los valores, observó cómo se copia un i32 sin invalidar su vinculación original, siguió el movimiento de un String hacia una función, interpretó E0382 y continuó de forma segura utilizando la propiedad que se devuelve a quien realiza la llamada.