Rutas en el Árbol de Módulos 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 Rutas para Referirse a un Elemento en el Árbol de Módulos. Esta práctica es parte del Rust Book. Puedes practicar tus habilidades de Rust en LabEx.

En esta práctica, aprendemos que las rutas se utilizan en Rust para referirse a elementos en el árbol de módulos y pueden tener la forma de rutas absolutas o rutas relativas.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL 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(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") 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/lifetime_specifiers("Lifetime Specifiers") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/traits("Traits") subgraph Lab Skills rust/variable_declarations -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/mutable_variables -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/string_type -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/function_syntax -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/expressions_statements -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/lifetime_specifiers -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/method_syntax -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} rust/traits -.-> lab-100403{{"Rutas en el Árbol de Módulos de Rust"}} end

Rutas para Referirse a un Elemento en el Árbol de Módulos

Para indicar a Rust dónde encontrar un elemento en el árbol de módulos, usamos una ruta de la misma manera en que usamos una ruta al navegar por un sistema de archivos. Para llamar a una función, necesitamos conocer su ruta.

Una ruta puede tomar dos formas:

  • Una ruta absoluta es la ruta completa que comienza en la raíz del crat; para el código de un crat externo, la ruta absoluta comienza con el nombre del crat, y para el código del crat actual, comienza con el literal crate.
  • Una ruta relativa comienza desde el módulo actual y utiliza self, super o un identificador en el módulo actual.

Tanto las rutas absolutas como las relativas se siguen con uno o más identificadores separados por dos puntos dobles (::).

Volviendo a la Lista 7-1, digamos que queremos llamar a la función add_to_waitlist. Esto es lo mismo que preguntar: ¿cuál es la ruta de la función add_to_waitlist? La Lista 7-3 contiene la Lista 7-1 con algunos de los módulos y funciones eliminados.

Mostraremos dos maneras de llamar a la función add_to_waitlist desde una nueva función, eat_at_restaurant, definida en la raíz del crat. Estas rutas son correctas, pero queda otro problema que evitará que este ejemplo se compile tal como está. Lo explicaré un poco más adelante.

La función eat_at_restaurant es parte de la API pública de nuestro crat de biblioteca, por lo que la marcamos con la palabra clave pub. En "Exponiendo Rutas con la Palabra Clave pub", entraremos en más detalle sobre pub.

Nombre del archivo: src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Ruta absoluta
    crate::front_of_house::hosting::add_to_waitlist();

    // Ruta relativa
    front_of_house::hosting::add_to_waitlist();
}

Lista 7-3: Llamando a la función add_to_waitlist usando rutas absolutas y relativas

La primera vez que llamamos a la función add_to_waitlist en eat_at_restaurant, usamos una ruta absoluta. La función add_to_waitlist está definida en el mismo crat que eat_at_restaurant, lo que significa que podemos usar la palabra clave crate para comenzar una ruta absoluta. Luego incluimos cada uno de los módulos sucesivos hasta llegar a add_to_waitlist. Puedes imaginar un sistema de archivos con la misma estructura: especificaríamos la ruta /front_of_house/hosting/add_to_waitlist para ejecutar el programa add_to_waitlist; usar el nombre del crate para comenzar desde la raíz del crat es como usar / para comenzar desde la raíz del sistema de archivos en tu shell.

La segunda vez que llamamos a add_to_waitlist en eat_at_restaurant, usamos una ruta relativa. La ruta comienza con front_of_house, el nombre del módulo definido en el mismo nivel del árbol de módulos que eat_at_restaurant. Aquí, el equivalente en el sistema de archivos sería usar la ruta front_of_house/hosting/add_to_waitlist. Comenzar con el nombre de un módulo significa que la ruta es relativa.

Decidir si usar una ruta relativa o absoluta es una decisión que tomarás en función de tu proyecto y depende de si es más probable que muevas el código de definición de elementos por separado o junto con el código que usa el elemento. Por ejemplo, si movemos el módulo front_of_house y la función eat_at_restaurant a un módulo llamado customer_experience, tendremos que actualizar la ruta absoluta a add_to_waitlist, pero la ruta relativa todavía sería válida. Sin embargo, si movemos la función eat_at_restaurant por separado a un módulo llamado dining, la ruta absoluta a la llamada add_to_waitlist permanecería igual, pero la ruta relativa tendría que actualizarse. En general, nuestra preferencia es especificar rutas absolutas porque es más probable que queramos mover las definiciones de código y las llamadas a elementos independientemente entre sí.

Intentemos compilar la Lista 7-3 y averigüemos por qué todavía no se compilará. Los errores que obtenemos se muestran en la Lista 7-4.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
 --> src/lib.rs:9:28
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                            ^^^^^^^ private module
  |
note: the module `hosting` is defined here
 --> src/lib.rs:2:5
  |
2 |     mod hosting {
  |     ^^^^^^^^^^^

error[E0603]: module `hosting` is private
  --> src/lib.rs:12:21
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                     ^^^^^^^ private module
   |
note: the module `hosting` is defined here
  --> src/lib.rs:2:5
   |
2  |     mod hosting {
   |     ^^^^^^^^^^^

Lista 7-4: Errores del compilador al compilar el código de la Lista 7-3

Los mensajes de error dicen que el módulo hosting es privado. En otras palabras, tenemos las rutas correctas para el módulo hosting y la función add_to_waitlist, pero Rust no nos permite usarlas porque no tiene acceso a las secciones privadas. En Rust, todos los elementos (funciones, métodos, structs, enums, módulos y constantes) son privados para los módulos padre por defecto. Si quieres hacer que un elemento como una función o un struct sea privado, lo pones en un módulo.

Los elementos en un módulo padre no pueden usar los elementos privados dentro de los módulos hijos, pero los elementos en los módulos hijos pueden usar los elementos en sus módulos ancestros. Esto se debe a que los módulos hijos envuelven y ocultan sus detalles de implementación, pero los módulos hijos pueden ver el contexto en el que están definidos. Para continuar con nuestra metáfora, piensa en las reglas de privacidad como si fueran la oficina trasera de un restaurante: lo que pasa allí es privado para los clientes del restaurante, pero los gerentes de oficina pueden ver y hacer todo en el restaurante que administran.

Rust decidió que el sistema de módulos funcione de esta manera para que ocultar los detalles de implementación internos sea el comportamiento predeterminado. De esta manera, sabes qué partes del código interno puedes cambiar sin romper el código externo. Sin embargo, Rust te da la opción de exponer las partes internas del código de los módulos hijos a los módulos ancestros externos usando la palabra clave pub para hacer un elemento público.

Exponiendo Rutas con la Palabra Clave pub

Volvamos al error de la Lista 7-4 que nos dijo que el módulo hosting es privado. Queremos que la función eat_at_restaurant en el módulo padre tenga acceso a la función add_to_waitlist en el módulo hijo, por lo que marcamos el módulo hosting con la palabra clave pub, como se muestra en la Lista 7-5.

Nombre del archivo: src/lib.rs

mod front_of_house {
    pub mod hosting {
        fn add_to_waitlist() {}
    }
}

--snip--

Lista 7-5: Declarando el módulo hosting como pub para usarlo desde eat_at_restaurant

Lamentablemente, el código de la Lista 7-5 todavía genera errores del compilador, como se muestra en la Lista 7-6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
 --> src/lib.rs:9:37
  |
9 |     crate::front_of_house::hosting::add_to_waitlist();
  |                                     ^^^^^^^^^^^^^^^ private function
  |
note: the function `add_to_waitlist` is defined here
 --> src/lib.rs:3:9
  |
3 |         fn add_to_waitlist() {}
  |         ^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `add_to_waitlist` is private
  --> src/lib.rs:12:30
   |
12 |     front_of_house::hosting::add_to_waitlist();
   |                              ^^^^^^^^^^^^^^^ private function
   |
note: the function `add_to_waitlist` is defined here
  --> src/lib.rs:3:9
   |
3  |         fn add_to_waitlist() {}
   |         ^^^^^^^^^^^^^^^^^^^^

Lista 7-6: Errores del compilador al compilar el código de la Lista 7-5

¿Qué pasó? Agregar la palabra clave pub delante de mod hosting hace que el módulo sea público. Con este cambio, si podemos acceder a front_of_house, podemos acceder a hosting. Pero los contenidos de hosting siguen siendo privados; hacer el módulo público no hace que sus contenidos sean públicos. La palabra clave pub en un módulo solo permite que el código en sus módulos ancestros se refiera a él, no acceda a su código interno. Debido a que los módulos son contenedores, no hay mucho que podamos hacer solo haciendo el módulo público; necesitamos ir más allá y elegir hacer público uno o más de los elementos dentro del módulo también.

Los errores de la Lista 7-6 dicen que la función add_to_waitlist es privada. Las reglas de privacidad se aplican a structs, enums, funciones, métodos y módulos.

Vamos a hacer también pública la función add_to_waitlist agregando la palabra clave pub antes de su definición, como en la Lista 7-7.

Nombre del archivo: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

--snip--

Lista 7-7: Agregando la palabra clave pub a mod hosting y fn add_to_waitlist nos permite llamar a la función desde eat_at_restaurant.

Ahora el código se compilará! Para ver por qué agregar la palabra clave pub nos permite usar estas rutas en add_to_waitlist con respecto a las reglas de privacidad, veamos las rutas absolutas y relativas.

En la ruta absoluta, comenzamos con crate, la raíz del árbol de módulos de nuestro crat. El módulo front_of_house está definido en la raíz del crat. Si bien front_of_house no es público, dado que la función eat_at_restaurant está definida en el mismo módulo que front_of_house (es decir, eat_at_restaurant y front_of_house son hermanos), podemos referirnos a front_of_house desde eat_at_restaurant. A continuación está el módulo hosting marcado con pub. Podemos acceder al módulo padre de hosting, por lo que podemos acceder a hosting. Finalmente, la función add_to_waitlist está marcada con pub y podemos acceder a su módulo padre, por lo que esta llamada a función funciona!

En la ruta relativa, la lógica es la misma que la ruta absoluta excepto en el primer paso: en lugar de comenzar desde la raíz del crat, la ruta comienza desde front_of_house. El módulo front_of_house está definido dentro del mismo módulo que eat_at_restaurant, por lo que la ruta relativa que comienza desde el módulo en el que está definida eat_at_restaurant funciona. Luego, dado que hosting y add_to_waitlist están marcados con pub, el resto de la ruta funciona, y esta llamada a función es válida!

Si planeas compartir tu crat de biblioteca para que otros proyectos puedan usar tu código, tu API pública es tu contrato con los usuarios de tu crat que determina cómo pueden interactuar con tu código. Hay muchas consideraciones al gestionar los cambios en tu API pública para que sea más fácil para las personas depender de tu crat. Estas consideraciones están fuera del alcance de este libro; si estás interesado en este tema, consulta las Guías de API de Rust en https://rust-lang.github.io/api-guidelines.

Mejores Prácticas para Paquetes con un Binario y una Biblioteca

Mencionamos que un paquete puede contener tanto una raíz de crat binario src/main.rs como una raíz de crat de biblioteca src/lib.rs, y ambos crates tendrán el nombre del paquete por defecto. Típicamente, los paquetes con este patrón de contener tanto una biblioteca como un crat binario tendrán solo suficiente código en el crat binario para iniciar un ejecutable que llame a código con el crat de biblioteca. Esto permite que otros proyectos beneficien de la mayor funcionalidad que el paquete ofrece porque el código del crat de biblioteca se puede compartir.

El árbol de módulos debe definirse en src/lib.rs. Luego, cualquier elemento público se puede usar en el crat binario comenzando las rutas con el nombre del paquete. El crat binario se convierte en un usuario del crat de biblioteca al igual que un crat completamente externo usaría el crat de biblioteca: solo puede usar la API pública. Esto te ayuda a diseñar una buena API; no solo eres el autor, también eres un cliente!

En el Capítulo 12, demostraremos esta práctica de organización con un programa de línea de comandos que contendrá tanto un crat binario como un crat de biblioteca.

Comenzando Rutas Relativas con super

Podemos construir rutas relativas que empiecen en el módulo padre, en lugar del módulo actual o la raíz del crat, usando super al principio de la ruta. Esto es como comenzar una ruta de sistema de archivos con la sintaxis ... Usar super nos permite referirnos a un elemento que sabemos que está en el módulo padre, lo que puede facilitar la reorganización del árbol de módulos cuando el módulo está estrechamente relacionado con el padre pero el padre podría ser movido a otro lugar en el árbol de módulos algún día.

Considere el código de la Lista 7-8 que modela la situación en la que un chef corrige una orden incorrecta y la lleva personalmente al cliente. La función fix_incorrect_order definida en el módulo back_of_house llama a la función deliver_order definida en el módulo padre especificando la ruta a deliver_order, comenzando con super.

Nombre del archivo: src/lib.rs

fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}

Lista 7-8: Llamando a una función usando una ruta relativa que comienza con super

La función fix_incorrect_order está en el módulo back_of_house, por lo que podemos usar super para ir al módulo padre de back_of_house, que en este caso es crate, la raíz. A partir de ahí, buscamos deliver_order y lo encontramos. ¡Éxito! Pensamos que el módulo back_of_house y la función deliver_order probablemente permanecerán en la misma relación y se moverán juntos si decidimos reorganizar el árbol de módulos del crat. Por lo tanto, usamos super para tener menos lugares donde actualizar el código en el futuro si este código se mueve a un módulo diferente.

Haciendo Públicos Structs y Enums

También podemos usar pub para designar structs y enums como públicos, pero hay algunos detalles adicionales en el uso de pub con structs y enums. Si usamos pub antes de una definición de struct, hacemos el struct público, pero los campos del struct todavía serán privados. Podemos hacer que cada campo sea público o no caso por caso. En la Lista 7-9, hemos definido un struct público back_of_house::Breakfast con un campo público toast pero un campo privado seasonal_fruit. Esto modela el caso en un restaurante donde el cliente puede elegir el tipo de pan que viene con una comida, pero el chef decide qué fruta acompaña la comida según lo que esté de temporada y en stock. La fruta disponible cambia rápidamente, por lo que los clientes no pueden elegir la fruta ni siquiera ver qué fruta recibirán.

Nombre del archivo: src/lib.rs

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    // Order a breakfast in the summer with Rye toast
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Change our mind about what bread we'd like
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // The next line won't compile if we uncomment it; we're not
    // allowed to see or modify the seasonal fruit that comes
    // with the meal
    // meal.seasonal_fruit = String::from("blueberries");
}

Lista 7-9: Un struct con algunos campos públicos y algunos campos privados

Debido a que el campo toast en el struct back_of_house::Breakfast es público, en eat_at_restaurant podemos escribir y leer en el campo toast usando notación de punto. Observe que no podemos usar el campo seasonal_fruit en eat_at_restaurant, porque seasonal_fruit es privado. Intente descomentar la línea que modifica el valor del campo seasonal_fruit para ver qué error obtiene.

También, observe que debido a que back_of_house::Breakfast tiene un campo privado, el struct necesita proporcionar una función asociada pública que construya una instancia de Breakfast (la hemos nombrado summer aquí). Si Breakfast no tuviera tal función, no podríamos crear una instancia de Breakfast en eat_at_restaurant porque no podríamos establecer el valor del campo privado seasonal_fruit en eat_at_restaurant.

En contraste, si hacemos un enum público, todas sus variantes entonces son públicas. Solo necesitamos el pub antes de la palabra clave enum, como se muestra en la Lista 7-10.

Nombre del archivo: src/lib.rs

mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

Lista 7-10: Designar un enum como público hace que todas sus variantes sean públicas.

Debido a que hicimos público el enum Appetizer, podemos usar las variantes Soup y Salad en eat_at_restaurant.

Los enums no son muy útiles a menos que sus variantes sean públicas; sería molesto tener que anotar todas las variantes de enum con pub en cada caso, por lo que el valor predeterminado para las variantes de enum es ser públicas. Los structs a menudo son útiles sin que sus campos sean públicos, por lo que los campos de struct siguen la regla general de que todo es privado por defecto a menos que se anote con pub.

Hay una más situación que implica pub que no hemos cubierto, y esa es nuestra última característica del sistema de módulos: la palabra clave use. Cubriremos use por sí sola primero, y luego mostraremos cómo combinar pub y use.

Resumen

¡Felicidades! Has completado el laboratorio de Rutas para Referirse a un Elemento en el Árbol de Módulos. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.