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 el Hashmap se encuentra en std, dentro del módulo collections. Así se dispone de una pista sobre la existencia de un conjunto de tipos que son colecciones dentro de collections.
  • 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 un struct hay que usar pub con él.
  • Para un enum o trait: 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
}