Organización: módulos, visibilidad y el proyecto
Módulos con mod, visibilidad con pub (privado por defecto), rutas con use, crate, self y super, y el camino hacia estructurar un proyecto en crates y workspaces.
Un archivo con veinte structs sueltos no escala. Rust organiza el código en un árbol de módulos (mod) donde, por defecto, todo es privado, y tú decides qué cruza la frontera con pub. Ese sistema de visibilidad no es burocracia: es la herramienta con la que impones encapsulación e invariantes, y el primer paso hacia estructurar un proyecto en crates y workspaces.
- Agrupar código en módulos con
mody navegar el árbol con rutas. - Controlar la visibilidad con
pub(privado por defecto). - Usar
use, y las rutascrate,selfysuper. - Situar módulos, crates y workspaces en la jerarquía de un proyecto.
mod: el árbol de módulos
mod define un módulo, un espacio de nombres que agrupa tipos y funciones relacionados:
mod geometria {
pub struct Punto {
pub x: f64,
pub y: f64,
}
pub fn origen() -> Punto {
Punto { x: 0.0, y: 0.0 }
}
}
fn main() {
let p = geometria::origen(); // ruta: modulo::elemento
println!("{} {}", p.x, p.y);
}
El operador :: recorre el árbol. Los módulos pueden anidarse, y todo tu programa cuelga de una raíz implícita llamada crate.
pub: privado por defecto
Aquí Rust se separa de casi todos los lenguajes: todo es privado por defecto. Un módulo, un struct, un campo o una función solo son visibles fuera de su módulo si los marcas pub:
mod banco {
pub struct Cuenta {
pub titular: String,
saldo: f64, // privado: nadie fuera de 'banco' lo toca
}
impl Cuenta {
pub fn nueva(titular: String) -> Self {
Self { titular, saldo: 0.0 }
}
pub fn saldo(&self) -> f64 { // acceso controlado, solo lectura
self.saldo
}
}
}
let c = banco::Cuenta::nueva(String::from("Ada"));
// c.saldo = 1_000_000.0; // ERROR: 'saldo' es privado
println!("{}", c.saldo()); // OK: vía método público
Fíjate: saldo es privado, así que nadie desde fuera puede ponerlo a un valor inválido. El módulo custodia su propio invariante.
Rutas: use, crate, self y super
Escribir la ruta completa cada vez cansa. use trae un nombre al ámbito actual:
use geometria::Punto;
let p = Punto { x: 1.0, y: 2.0 }; // sin el prefijo 'geometria::'
Las rutas pueden ser absolutas (desde crate::) o relativas (self:: para el módulo actual, super:: para el padre):
mod app {
pub fn version() -> u32 { 2 }
mod interno {
pub fn describe() -> u32 {
super::version() // sube al módulo padre 'app'
}
}
}
flowchart TD A[crate raiz] --> B[mod geometria] A --> C[mod banco] A --> D[fn main] B --> E[struct Punto pub] B --> F[fn origen pub] C --> G[struct Cuenta pub] G --> H[campo saldo privado] G --> I[fn nueva pub]
De módulos a crates y workspaces
El árbol de módulos vive dentro de una unidad mayor. La jerarquía completa:
Módulo (mod)
La unidad de organización y visibilidad dentro de un crate. Un archivo o un bloque mod.
Crate
La unidad de compilación de Rust: un binario o una biblioteca. Tiene una raíz (main.rs o lib.rs).
Workspace
Un conjunto de crates que se compilan juntos y comparten dependencias. Así se estructuran los proyectos grandes.
Convencionalmente, cada mod puede vivir en su propio archivo (geometria.rs o geometria/mod.rs), y cargo orquesta la compilación. El árbol lógico de módulos y el árbol físico de archivos se corresponden, pero es mod —no el sistema de archivos— quien define la estructura real del programa.
El “privado por defecto” de Rust invierte la carga de la prueba: no decides qué ocultar, decides qué exponer, y todo lo demás queda protegido sin esfuerzo. Esto convierte la visibilidad en la herramienta central de encapsulación. Recuerda el constructor validado del nivel 5.3: si haces privados los campos de un struct y solo publicas constructores que validan, entonces la única forma de fabricar ese tipo pasa por tu validación —el compilador te garantiza que ningún código, en ningún lugar del programa, puede saltarse tus invariantes tocando un campo directamente—. La frontera pub es, literalmente, la superficie de tu API: lo que es pub es un contrato que te comprometes a mantener; lo privado es libertad para refactorizar sin romper a nadie. Por eso, en Rust, diseñar los módulos y decidir qué es pub no es una tarea de limpieza que se hace al final: es diseñar la arquitectura del sistema, y el compilador la impone en cada compilación. Un experto piensa la frontera de visibilidad con el mismo cuidado con que piensa los tipos, porque ambas cosas son lo mismo: hacer que el mal uso sea imposible, no solo desaconsejado.
- Crea un módulo
temperaturacon un structCelsiuscuyo campo interno sea privado. - Expón una función asociada
pub fn nueva(valor: f64) -> Celsiusy un métodopub fn valor(&self) -> f64. - Desde fuera del módulo, comprueba que puedes construir y leer, pero no asignar el campo directamente.
- Añade un submódulo y usa
super::para llamar a una función del módulo padre. - Dibuja (en papel o en un comentario) el árbol de módulos de tu programa desde
crate.