wandres.dev
STRUCTS Y MÉTODOS · modelar datos

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.

⏱ 18 min

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.

🎯 Al terminar esta lección sabrás
  • Agrupar código en módulos con mod y navegar el árbol con rutas.
  • Controlar la visibilidad con pub (privado por defecto).
  • Usar use, y las rutas crate, self y super.
  • 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.

La frontera de privacidad es tu arquitectura, comprobada en compilación

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.

⚔️ Encapsula un invariante
  1. Crea un módulo temperatura con un struct Celsius cuyo campo interno sea privado.
  2. Expón una función asociada pub fn nueva(valor: f64) -> Celsius y un método pub fn valor(&self) -> f64.
  3. Desde fuera del módulo, comprueba que puedes construir y leer, pero no asignar el campo directamente.
  4. Añade un submódulo y usa super:: para llamar a una función del módulo padre.
  5. Dibuja (en papel o en un comentario) el árbol de módulos de tu programa desde crate.