Ir al contenido

Módulos en Rust

Hasta ahora, todos los ejemplos han vivido en un solo archivo main.rs. Para programas pequeños está bien, pero los proyectos reales dividen el código en partes lógicas. La herramienta de Rust para eso es el sistema de módulos: mod, pub y use.

Un módulo es un bloque con nombre que agrupa código relacionado:

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

Salida:

5
▶ Pruébalo tú mismo

Hay dos cosas en las que fijarse. La función se llama con math::add(...), usando el nombre del módulo como ruta. Y está marcada con pub: sin eso, no se podría usar desde fuera del módulo.

Vale la pena destacarlo porque sorprende a mucha gente: el contenido de un módulo es privado por defecto. Si quitas pub del ejemplo anterior, no compila:

mod math {
fn add(a: i32, b: i32) -> i32 { // sin `pub`
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ error: function `add` is private
println!("{sum}");
}

El mensaje del compilador (en inglés, como todos los de rustc) dice que la función add es privada. Tienes que marcar con pub, de forma explícita, todo lo que quieras que se use desde fuera del módulo. Es a propósito: así un módulo puede esconder sus funciones auxiliares internas y mostrar solo las partes pensadas como API pública.

Un módulo puede contener otros módulos. Así es como los proyectos grandes van formando una estructura en forma de árbol:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ Pruébalo tú mismo

Escribir shapes::circle::area(...) cada vez acaba cansando. use trae una ruta al ámbito actual para que puedas usarla directamente:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // más corto que shapes::circle::area
println!("{area}");
}
▶ Pruébalo tú mismo

En realidad ya has usado use: es la misma palabra clave que viste en la página de HashMap (use std::collections::HashMap;).

Los ejemplos anteriores ponen todo en un solo archivo, porque el Playground de Rust lo exige. En un proyecto real en tu propio equipo, cada módulo suele tener su propio archivo. Un proyecto con un módulo math normalmente se vería así:

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // le dice a Rust: busca el código de este módulo en math.rs
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math; (con punto y coma y sin cuerpo) le indica a Rust que cargue el contenido del módulo desde math.rs, en la misma carpeta. Es el patrón que usarás en cuanto tu proyecto crezca más allá de un solo archivo.

Crea un módulo llamado convert con dos funciones públicas: c_to_f (de Celsius a Fahrenheit) y f_to_c (al revés). Desde main, imprime 100°C son 212°F y 212°F son 100°C.

Pista: las fórmulas son c * 9.0 / 5.0 + 32.0 y (f - 32.0) * 5.0 / 9.0. No olvides pub, y llama a las funciones con el nombre del módulo, así: convert::c_to_f(100.0).

Ver solución
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C son {}°F", convert::c_to_f(100.0));
println!("212°F son {}°C", convert::f_to_c(212.0));
}

Salida:

100°C son 212°F
212°F son 100°C
▶ Pruébalo tú mismo

Las dos funciones son pub, así que main (que está fuera del módulo) puede llamarlas. Quita un pub y el compilador te dirá que esa función es privada.

Crea un módulo anidado school::grades con una función pública letter(score: u32) -> char que devuelva 'A' para 90 o más, 'B' para 80 o más y 'C' en cualquier otro caso. Dentro de grades, escribe también una función auxiliar privada is_at_least(score: u32, min: u32) -> bool que use letter. Trae school::grades al ámbito con use e imprime la letra de 95, 85 y 70.

Pista: tanto el mod grades interior de school como fn letter necesitan pub, pero is_at_least no debe llevarlo. El código de un mismo módulo siempre puede llamar a sus funciones privadas.

Ver solución
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// privada: solo el código dentro de `grades` puede llamarla
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

Salida:

95 -> A
85 -> B
70 -> C
▶ Pruébalo tú mismo

letter puede llamar a is_at_least porque están en el mismo módulo. Desde main, grades::is_at_least(95, 90) fallaría con “function is_at_least is private” (la función is_at_least es privada). De eso se trata: el módulo muestra una API pública pequeña y se guarda sus funciones auxiliares para sí.

  • mod nombre { ... } agrupa código relacionado en un módulo con nombre.
  • Todo lo que hay dentro de un módulo es privado por defecto: añade pub para que se pueda usar desde fuera.
  • use trae una ruta al ámbito para que no tengas que escribirla completa cada vez.
  • En un proyecto con varios archivos, mod nombre; (sin cuerpo) carga el código del módulo desde el archivo con ese nombre.
  • El código de un módulo siempre puede usar los elementos privados de ese módulo, así que deja privadas las funciones auxiliares y expón solo lo que necesitan quienes lo usan.

Comprueba lo aprendido

1. Por defecto, ¿los elementos dentro de un mod son públicos o privados?

2. ¿Cómo le dices a Rust que cargue el código de un módulo desde math.rs en lugar de escribirlo en el mismo archivo?

Puntuación: 0 / 2