Limpieza con el trato Drop de Rust

RustRustBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Bienvenido a Ejecutar código en la limpieza con el trato Drop. Esta práctica es parte del Rust Book. Puedes practicar tus habilidades de Rust en LabEx.

En esta práctica, exploramos el trato Drop en Rust, que permite la personalización de las acciones de limpieza cuando un valor sale de ámbito, generalmente utilizado en punteros inteligentes, y se puede implementar proporcionando un método drop que es llamado automáticamente por Rust, evitando la necesidad de código de limpieza explícito.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/MemorySafetyandManagementGroup(["Memory Safety and Management"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/MemorySafetyandManagementGroup -.-> rust/drop_trait("Drop Trait") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/traits("Traits") subgraph Lab Skills rust/variable_declarations -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} rust/string_type -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} rust/function_syntax -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} rust/expressions_statements -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} rust/drop_trait -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} rust/method_syntax -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} rust/traits -.-> lab-100433{{"Limpieza con el trato Drop de Rust"}} end

Ejecutar código en la limpieza con el trato Drop

El segundo trato importante para el patrón de puntero inteligente es Drop, que te permite personalizar lo que sucede cuando un valor está a punto de salir de ámbito. Puedes proporcionar una implementación para el trato Drop en cualquier tipo, y ese código se puede utilizar para liberar recursos como archivos o conexiones de red.

Estamos presentando Drop en el contexto de los punteros inteligentes porque la funcionalidad del trato Drop casi siempre se utiliza al implementar un puntero inteligente. Por ejemplo, cuando se elimina un Box<T>, se desasignará el espacio en el montón al que apunta la caja.

En algunos lenguajes, para algunos tipos, el programador debe llamar a código para liberar memoria o recursos cada vez que termina de usar una instancia de esos tipos. Ejemplos incluyen manejadores de archivos, sockets y candados. Si se olvidan, el sistema puede sobrecargarse y se puede bloquear. En Rust, puedes especificar que se ejecute un determinado código cada vez que un valor sale de ámbito, y el compilador insertará este código automáticamente. Como resultado, no tienes que preocuparte por colocar código de limpieza en todos los lugares de un programa donde se termina con una instancia de un tipo particular: ¡todavía no se perderán recursos!

Especifiques el código a ejecutar cuando un valor sale de ámbito implementando el trato Drop. El trato Drop requiere que implementes un método llamado drop que tome una referencia mutable a self. Para ver cuándo Rust llama a drop, implementemos drop con declaraciones println! por ahora.

La Lista 15-14 muestra una estructura CustomSmartPointer cuya única funcionalidad personalizada es que imprimirá Dropping CustomSmartPointer! cuando la instancia sale de ámbito, para mostrar cuándo Rust ejecuta el método drop.

Nombre de archivo: src/main.rs

struct CustomSmartPointer {
    data: String,
}

1 impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
      2 println!(
            "Dropping CustomSmartPointer with data `{}`!",
            self.data
        );
    }
}

fn main() {
  3 let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
  4 let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
  5 println!("CustomSmartPointers created.");
6 }

Lista 15-14: Una estructura CustomSmartPointer que implementa el trato Drop donde pondríamos nuestro código de limpieza

El trato Drop está incluido en el preludio, por lo que no es necesario traerlo al ámbito. Implementamos el trato Drop en CustomSmartPointer [1] y proporcionamos una implementación para el método drop que llama a println! [2]. El cuerpo del método drop es donde colocarías cualquier lógica que quisieras ejecutar cuando una instancia de tu tipo sale de ámbito. Estamos imprimiendo algunos textos aquí para demostrar visualmente cuándo Rust llamará a drop.

En main, creamos dos instancias de CustomSmartPointer en [3] y [4] y luego imprimimos CustomSmartPointers created [5]. Al final de main [6], nuestras instancias de CustomSmartPointer saldrán de ámbito, y Rust llamará al código que pusimos en el método drop [2], imprimiendo nuestro mensaje final. Tenga en cuenta que no tuvimos que llamar explícitamente al método drop.

Cuando ejecutamos este programa, veremos la siguiente salida:

CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

Rust automáticamente llamó a drop por nosotros cuando nuestras instancias salieron de ámbito, llamando al código que especificamos. Las variables se eliminan en el orden inverso de su creación, por lo que d se eliminó antes que c. El propósito de este ejemplo es darte una guía visual de cómo funciona el método drop; por lo general, especificarías el código de limpieza que tu tipo necesita ejecutar en lugar de un mensaje de impresión.

Lamentablemente, no es sencillo deshabilitar la funcionalidad automática de drop. En general, no es necesario deshabilitar drop; el objetivo principal del trato Drop es que se cuide automáticamente. En ocasiones, sin embargo, es posible que desees limpiar un valor temprano. Un ejemplo es cuando se utilizan punteros inteligentes que administran candados: es posible que desees forzar el método drop que libera el candado para que otro código en el mismo ámbito pueda adquirir el candado. Rust no te permite llamar manualmente al método drop del trato Drop; en su lugar, debes llamar a la función std::mem::drop proporcionada por la biblioteca estándar si deseas forzar a que se elimine un valor antes del final de su ámbito.

Si intentamos llamar manualmente al método drop del trato Drop modificando la función main de la Lista 15-14, como se muestra en la Lista 15-15, obtendremos un error del compilador.

Nombre de archivo: src/main.rs

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    c.drop();
    println!(
        "CustomSmartPointer dropped before the end of main."
    );
}

Lista 15-15: Intentando llamar al método drop del trato Drop manualmente para limpiar temprano

Cuando intentamos compilar este código, obtendremos este error:

error[E0040]: explicit use of destructor method
  --> src/main.rs:16:7
   |
16 |     c.drop();
   |     --^^^^--
   |     | |
   |     | explicit destructor calls not allowed
   |     help: consider using `drop` function: `drop(c)`

Este mensaje de error indica que no se permite llamar explícitamente a drop. El mensaje de error utiliza el término destructor, que es el término general de programación para una función que limpia una instancia. Un destructor es análogo a un constructor, que crea una instancia. La función drop en Rust es un destructor en particular.

Rust no nos permite llamar a drop explícitamente porque Rust todavía llamará automáticamente a drop en el valor al final de main. Esto causaría un error de doble liberación porque Rust intentaría limpiar el mismo valor dos veces.

No podemos deshabilitar la inserción automática de drop cuando un valor sale de ámbito, y no podemos llamar al método drop explícitamente. Entonces, si necesitamos forzar a que se limpie un valor temprano, usamos la función std::mem::drop.

La función std::mem::drop es diferente del método drop en el trato Drop. La llamamos pasando como argumento el valor que queremos forzar a eliminar. La función está en el preludio, por lo que podemos modificar main en la Lista 15-15 para llamar a la función drop, como se muestra en la Lista 15-16.

Nombre de archivo: src/main.rs

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!(
        "CustomSmartPointer dropped before the end of main."
    );
}

Lista 15-16: Llamando a std::mem::drop para eliminar explícitamente un valor antes de que salga de ámbito

Ejecutar este código imprimirá lo siguiente:

CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.

El texto Dropping CustomSmartPointer with datasome data! se imprime entre el texto CustomSmartPointer created. y CustomSmartPointer dropped before the end of main., lo que muestra que se llama al código del método drop para eliminar c en ese momento.

Puedes usar el código especificado en una implementación del trato Drop de muchas maneras para hacer la limpieza conveniente y segura: por ejemplo, podrías usarla para crear tu propio asignador de memoria. Con el trato Drop y el sistema de propiedad de Rust, no tienes que recordar limpiar porque Rust lo hace automáticamente.

También no tienes que preocuparte por problemas que resulten de limpiar accidentalmente valores que todavía se están utilizando: el sistema de propiedad que asegura que las referencias siempre sean válidas también garantiza que drop se llame solo una vez cuando el valor ya no se está utilizando.

Ahora que hemos examinado Box<T> y algunas de las características de los punteros inteligentes, echemos un vistazo a algunos otros punteros inteligentes definidos en la biblioteca estándar.

Resumen

¡Felicidades! Has completado la práctica de Ejecutar código en la limpieza con el trato Drop. Puedes practicar más ejercicios en LabEx para mejorar tus habilidades.