En esta página

Módulos, crates y el sistema de paquetes

12 min lectura TextoCap. 5 — Rust en práctica

El sistema de módulos de Rust

Rust organiza el código con un sistema de módulos explícito y jerárquico. Los módulos controlan la privacidad de los items (funciones, structs, enums, traits) y organizan el espacio de nombres.

La jerarquía: paquetes, crates y módulos

Paquete (package)
├── Cargo.toml
└── Crates
    ├── Crate binario (src/main.rs) — produce un ejecutable
    └── Crate biblioteca (src/lib.rs) — produce una librería reutilizable
        └── Módulos
            ├── módulo::submódulo::item
            └── ...

Un paquete puede tener uno o más crates. Un crate tiene un "crate root" (main.rs o lib.rs). Los módulos organizan el código dentro de un crate.

Módulos inline

// src/main.rs

mod frutas {
    // pub: visible fuera del módulo
    pub struct Fruta {
        pub nombre: String,
        calorias: u32, // privado
    }
    
    impl Fruta {
        pub fn nueva(nombre: &str, calorias: u32) -> Self {
            Fruta {
                nombre: nombre.to_string(),
                calorias,
            }
        }
        
        pub fn calorias(&self) -> u32 {
            self.calorias
        }
    }
    
    pub mod tropicales {
        use super::Fruta; // super: módulo padre
        
        pub fn mango() -> Fruta {
            Fruta::nueva("mango", 60)
        }
        
        pub fn papaya() -> Fruta {
            Fruta::nueva("papaya", 43)
        }
    }
}

fn main() {
    // Ruta absoluta desde el crate root
    let f = frutas::Fruta::nueva("manzana", 52);
    println!("{}: {} cal", f.nombre, f.calorias());
    
    // Acceso a submódulo
    let m = frutas::tropicales::mango();
    println!("{}: {} cal", m.nombre, m.calorias());
}

Módulos en archivos separados

Para proyectos grandes, cada módulo vive en su propio archivo:

src/
├── main.rs
├── matematicas.rs        ← mod matematicas;
└── matematicas/
    ├── mod.rs            (alternativa: o usar matematicas.rs)
    └── avanzado.rs       ← pub mod avanzado;
// src/main.rs
mod matematicas; // Busca src/matematicas.rs o src/matematicas/mod.rs

use matematicas::sumar;
use matematicas::avanzado::factorial;

fn main() {
    println!("3 + 4 = {}", sumar(3.0, 4.0));
    println!("5! = {}", factorial(5));
}
// src/matematicas.rs
pub mod avanzado; // Busca src/matematicas/avanzado.rs

pub fn sumar(a: f64, b: f64) -> f64 { a + b }
pub fn restar(a: f64, b: f64) -> f64 { a - b }
pub fn multiplicar(a: f64, b: f64) -> f64 { a * b }
// src/matematicas/avanzado.rs
pub fn factorial(n: u64) -> u64 {
    (1..=n).product()
}

pub fn combinatorio(n: u64, r: u64) -> u64 {
    if r > n { return 0; }
    factorial(n) / (factorial(r) * factorial(n - r))
}

La keyword `use`: importar paths

mod geometria {
    pub mod formas {
        pub struct Circulo(pub f64);
        pub struct Cuadrado(pub f64);
    }
}

// Importar un item específico
use geometria::formas::Circulo;

// Importar múltiples items con llaves
use geometria::formas::{Circulo as C, Cuadrado};

// Importar todo (glob import) — úsalo con cuidado
use geometria::formas::*;

fn main() {
    let c = Circulo(5.0);
    let s = Cuadrado(4.0);
    println!("Radio: {}, Lado: {}", c.0, s.0);
}

Re-exportar con `pub use`

// src/lib.rs

mod implementacion {
    pub struct Conexion {
        pub url: String,
    }
    
    impl Conexion {
        pub fn nueva(url: &str) -> Self {
            Conexion { url: url.to_string() }
        }
    }
}

// Re-exportar: los usuarios pueden usar mi_crate::Conexion
// en lugar de mi_crate::implementacion::Conexion
pub use implementacion::Conexion;

Cargo.toml: dependencias

El archivo Cargo.toml es el manifiesto del proyecto:

[package]
name = "mi-app"
version = "0.1.0"
edition = "2021"
authors = ["David Morales <[email protected]>"]
description = "Mi aplicación Rust"

[dependencies]
# Versión semántica
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# Caret: ^1.0 = >=1.0.0, <2.0.0
tokio = { version = "^1.0", features = ["full"] }

# Tilde: ~1.2 = >=1.2.0, <1.3.0
log = "~0.4"

# Versión exacta
chrono = "=0.4.38"

# Desde git
# mi-crate = { git = "https://github.com/usuario/mi-crate" }

# Desde ruta local (para workspaces)
# mi-util = { path = "../mi-util" }

[dev-dependencies]
# Solo para tests
pretty_assertions = "1"

[build-dependencies]
# Para scripts de build (build.rs)
cc = "1"

[profile.release]
opt-level = 3
lto = true

Workspaces: múltiples crates en un repositorio

Para proyectos grandes con múltiples crates relacionados:

# Cargo.toml raíz (workspace)
[workspace]
members = [
    "core",
    "api-server",
    "cli",
    "utils",
]
resolver = "2"

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# core/Cargo.toml
[package]
name = "mi-core"
version = "0.1.0"
edition = "2021"

[dependencies]
serde.workspace = true  # Usa la versión del workspace

Crates esenciales del ecosistema

Crate Propósito
serde + serde_json Serialización/deserialización
tokio Runtime async
reqwest HTTP client
clap CLI parsing
thiserror Tipos de error con derive
anyhow Manejo de errores en aplicaciones
log + env_logger Logging
chrono Fechas y horas
regex Expresiones regulares
rand Números aleatorios

Agregar y usar una dependencia real

cargo add serde --features derive
cargo add serde_json
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Producto {
    id: u32,
    nombre: String,
    precio: f64,
    disponible: bool,
}

fn main() {
    let producto = Producto {
        id: 1,
        nombre: String::from("Teclado Mecánico"),
        precio: 89.99,
        disponible: true,
    };
    
    // Serializar a JSON
    let json = serde_json::to_string_pretty(&producto).unwrap();
    println!("{json}");
    
    // Deserializar desde JSON
    let json_entrada = r#"{"id":2,"nombre":"Mouse","precio":29.99,"disponible":false}"#;
    let producto2: Producto = serde_json::from_str(json_entrada).unwrap();
    println!("{:?}", producto2);
}

Con el sistema de módulos dominado, pasamos a una de las características más elegantes de Rust: closures e iteradores — la forma funcional de transformar datos.

pub(crate) vs pub vs pub(super)
Rust tiene visibilidad granular: pub hace el item público para todos, pub(crate) lo limita al crate actual, pub(super) lo limita al módulo padre, y sin pub es privado al módulo actual. Prefiere pub(crate) sobre pub cuando el item no forma parte de la API pública de la librería.
crates.io y Cargo.lock
Cargo.lock garantiza builds reproducibles — guarda las versiones exactas de todas las dependencias. Para proyectos binarios debes commitear Cargo.lock al repositorio. Para librerías, generalmente NO se commitea (los consumidores de la librería resolverán las versiones).
// src/lib.rs — raíz del crate biblioteca

// Módulos inline
pub mod matematicas {
    pub fn sumar(a: f64, b: f64) -> f64 { a + b }
    pub fn restar(a: f64, b: f64) -> f64 { a - b }

    // Submódulo
    pub mod avanzado {
        pub fn factorial(n: u64) -> u64 {
            (1..=n).product()
        }

        pub fn fibonacci(n: u32) -> u64 {
            match n {
                0 => 0,
                1 => 1,
                _ => fibonacci(n - 1) + fibonacci(n - 2),
            }
        }
    }
}

// Módulo privado (sin pub)
mod interno {
    pub(super) fn ayudante() -> &'static str { "ayuda interna" }
}

pub fn usar_interno() -> &'static str {
    interno::ayudante()
}

// Re-exportar con use pub
pub use matematicas::avanzado::factorial;