Crates (cajones) y módulos
Todo el código que se escribe en Rust se encuentra en un crate (N.T.: es el término que utiliza Rust para describir lo que en otros lenguajes de programación son las librerías o bibliotecas). Un crate
es un fichero o conjunto de ficheros que debe ir unido. Dentro de un fichero, se pueden crear mod
(módulos). Un módulo es un espacio de nombres único para funciones, estructuras, etc. y tiene sentido para:
- Contruir el código: facilita estructurar el código en piezas lógicas según crece.
- Leer el código: facilita la comprensión del código. Por ejemplo, el nombre
std::collections::Hashmap
expresa que elHashmap
se encuentra enstd
, dentro del módulocollections
. Así se dispone de una pista sobre la existencia de un conjunto de tipos que son colecciones dentro decollections
. - Privacidad: todo lo que se escribe en un módulo es privado por defecto. Se impide el uso de las funciones incluidas por parte de otros usuarios.
Para crear un módulo, solo se necesita escribir mod
e iniciar un bloque de código con {}
. A continuación, se crea un módulo denominado imprimir_cosas
para incluir diversas funciones relacionadas con la impresión.
mod imprimir_cosas { use std::fmt::Display; fn imprimir_una_cosa<T: Display>(input: T) { // Imprime cualquier cosa que tenga definido Display println!("{}", input) } } fn main() {}
Cabe destacar que se ha escrito use std::fmt::Display
dentro del módulo imprimir_cosas
. Es un espacio separado. Si estuviera definido en main()
no estaría disponible en el módulo. De esta forma, se puede usar en el módulo, pero no está accesible en main()
. Además, la función imprimir_una_cosa()
no puede usarse en este momento fuera del módulo, ya que no se ha precedido de la palabra reservada pub
. Es decir, imprimir_una_cosa()
es de uso privado del módulo mientras no se declare pública. Si se intenta el siguiente código:
mod imprimir_cosas { use std::fmt::Display; fn imprimir_una_cosa<T: Display>(input: T) { // Imprime cualquier cosa que tenga definido Display println!("{}", input) } } fn main() { crate::imprimir_cosas::imprimir_una_cosa(6); }
Falla con el siguiente error:
error[E0603]: function `imprimir_una_cosa` is private
--> src/main.rs:10:28
|
10 | crate::imprimir_cosas::imprimir_una_cosa(6);
| ^^^^^^^^^^^^^^^^^ private function
|
Aparte del error, en el código anterior, para llamar a la función se ha usado una sintaxis crate
que significa desde la raíz de este proyecto. Esto se puede escribir cada vez que sea necesario usar la función o se puede usar use
para importar la función:
mod imprimir_cosas { use std::fmt::Display; fn imprimir_una_cosa<T: Display>(input: T) { // Imprime cualquier cosa que tenga definido Display println!("{}", input) } } fn main() { use crate::imprimir_cosas::imprimir_una_cosa; imprimir_una_cosa(6); imprimir_una_cosa("Intento imprimir una cadena de caracteres".to_string()); }
Este código falla de la misma forma que el anterior:
error[E0603]: function `imprimir_una_cosa` is private
--> src/main.rs:10:32
|
10 | use crate::imprimir_cosas::imprimir_una_cosa;
| ^^^^^^^^^^^^^^^^^ private function
note: the function `imprimir_una_cosa` is defined here
--> src/main.rs:4:5
|
4 | fn imprimir_una_cosa<T: Display>(input: T) { // Imprime cualquier cosa que tenga definido Display
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
En este ejemplo, se ha añadido el resto del mensaje de error, que indica donde encontrar la función que es privada en este momento. Esto puede llegar a ser muy útil cuando se tiene mucho código en diferentes ficheros.
Solo se necesita escribir pub fn
en lugar de fn
para que la función sea pública al módulo. Con este cambio, el código funciona:
mod imprimir_cosas { use std::fmt::Display; pub fn imprimir_una_cosa<T: Display>(input: T) { // Imprime cualquier cosa que tenga definido Display println!("{}", input) } } fn main() { use crate::imprimir_cosas::imprimir_una_cosa; imprimir_una_cosa(6); imprimir_una_cosa("Intento imprimir una cadena de caracteres".to_string()); }
E imprime lo siguiente:
6
Intento imprimir una cadena de caracteres
En el caso de estructuras, enumerados, rasgos o módulos, pub
funciona de la siguiente forma:
- Para un
struct
: hace que la estructura sea pública, per sus elementos no. Para hacer público un elemento de unstruct
hay que usarpub
con él. - Para un
enum
otrait
: todo su contenido se vuelve público. Tiene sentido ya que los rasgos tienen que ver con dar un comportamiento a algo y los enumerados sirven para elegir entre varios tipos, por lo que deben estar accesibles. - Para un módulo: un módulo de nivel superior siempre es público dado que es la única forma de poder usarlo, pero los módulos interiores, solo serán accesibles desde fuera si se utiliza
pub
ante ellos.
A continuación se incorpora una estructura de nominada Billy
dentro del módulo imprimir_cosas
. La estructura será pública, dentro tendrá un nombre
y veces_a_imprimir
. El primero no será público. El usuario podrá seleccionar el número de veces a imprimir, así que este último si será público. Queda así:
mod imprimir_cosas { use std::fmt::{Display, Debug}; #[derive(Debug)] pub struct Billy { // Billy es público nombre: String, // pero nombre es privado. pub veces_a_imprimir: u32, // y veces_a_imprimir es público } impl Billy { pub fn new(veces_a_imprimir: u32) -> Self { // El usuario debe utilizar new para crear a Billy. Y solo decide el número de veces a imprimir Self { nombre: "Billy".to_string(), // El nombre se pone internamente veces_a_imprimir, } } pub fn imprimir_a_billy(&self) { // Esta función imprime a Billy for _ in 0..self.veces_a_imprimir { println!("{:?}", self.nombre); } } } pub fn imprimir_una_cosa<T: Display>(input: T) { println!("{}", input) } } fn main() { use crate::imprimir_cosas::*; // Ahora se usa *. Esto hace accesible todos los elementos públicos del módulo let my_billy = Billy::new(3); my_billy.imprimir_a_billy(); }
Esto imprimirá:
"Billy"
"Billy"
"Billy"
Por cierto, el *
para importar todo se denomina operador glob. Glob es por global, que significa aquí importar todo.
Dentro de un módulo se pueden crear otros módulos. Un módulo hijo de otro (el módulo que está dentro de otro se llama hijo del que lo contiene) siempre puede usar cualquier cosa del módulo padre. Esto se observa con el siguiente ejemplo en el que existe un mod ciudad
dentro de un mod provincia
dentro de un mod pais
.
Se puede pensar en esta estructura del modo en el que cuando estás ejecutando código de un país, puede que no estés dentro de código de un provinica, pero si estás ejecutando código de una provincia es que has pasado por un país. Si estás ejecutando código de ciudad, es que estás dentro de provincia y dentro de país, por lo que tienes acceso a todo su código.
mod pais { // El módulo principal no necesita pub fn imprimir_pais(pais: &str) { // Observa que esta función no es pública println!("Estamos en el pais de {}", pais); } pub mod provincia { // Este módulo es público fn imprimir_provincia(provincia: &str) { // Observa que esta función no es pública println!("en la provincia de {}", provincia); } pub mod ciudad { // Este módulo es público pub fn imprimir_ciudad(pais: &str, provincia: &str, ciudad: &str) { // Esta función sí es pública crate::pais::imprimir_pais(pais); crate::pais::provincia::imprimir_provincia(provincia); println!("en la ciudad de {}", ciudad); } } } } fn main() { crate::pais::provincia::ciudad::imprimir_ciudad("Canadá", "New Brunswick", "Moncton"); }
Esto imprime:
Estamos en el pais de Canadá
en la provincia de New Brunswick
en la ciudad de Moncton
Lo reseñable es que imprimir_ciudad
puede acceder a imprimir_provincia
e imprimir_pais
aunque no sean públicas para todo el código que esté fuera de estos módulos. Esto se debe a que el módulo ciudad
sí está dentro de los otros dos, por lo que tiene acceso a ellos de forma completa, incluido todo aquello que no sea público.
Se habrá observado que crate::pais::provincia::imprimir_provincia(provincia);
es un código muy largo. Dentro de un módulo hijo, es posible utilizar la palabra super
para acceder al módulo padre de este. Por lo tanto, se puede reescribir el código de otra forma, incluso importando los módulos para simplificar usos variados:
mod pais { fn imprimir_pais(pais: &str) { println!("Estamos en el pais de {}", pais); } pub mod provincia { fn imprimir_provincia(provincia: &str) { println!("en la provincia de {}", provincia); } pub mod ciudad { use super::super::*; // usa todo lo del módulo "abuelo": pais use super::*; // usa todo lo del módulo "padre": provincia pub fn imprimir_ciudad(pais: &str, provincia: &str, ciudad: &str) { imprimir_pais(pais); imprimir_provincia(provincia); println!("en la ciudad de {}", ciudad); } } } } fn main() { use crate::pais::provincia::ciudad::imprimir_ciudad; // importa solo esta función pública imprimir_ciudad("Canadá", "New Brunswick", "Moncton"); imprimir_ciudad("Korea", "Gyeonggi-do", "Gwangju"); // Ahora es menos trabajo utilizarlo de nuevo }