rust-visibilidad-rutas-pub-use

Visibilidad y rutas en Rust: pub, use, super y más

  • 4 min

La visibilidad y las rutas son las reglas de acceso y nombrado entre módulos.

En el artículo anterior creamos un árbol de módulos. Si intentaste llamar a una función de un módulo hijo desde el main.rs, probablemente Rust te gritó un error: module xxx is private.

En Rust, los elementos son privados por defecto, con excepciones concretas como las variantes de un enum público.

A diferencia de Java o C++ donde a veces las clases son públicas por defecto, Rust adopta la postura de seguridad máxima: nadie fuera del módulo actual puede ver ni usar nada, a menos que tú le des permiso explícito.

La palabra clave pub

Para hacer que una función, struct, enum o módulo sea accesible desde su módulo padre, debemos añadir la palabra clave pub (public) al principio.

mod cocina {
    // Esta función es privada. Solo visible dentro de 'cocina'.
    fn cocinar_secreto() { ... }

    // Esta función es pública. Visible desde 'main' (el padre).
    pub fn preparar_plato() {
        cocinar_secreto(); // 'cocina' sí puede ver sus propias funciones privadas
    }
}

fn main() {
    cocina::preparar_plato(); // ✅ Funciona
    // cocina::cocinar_secreto(); // ❌ Error: función privada
}
Copied!

Privacidad en los struct

Aquí hay una trampa común. Si haces un struct público, sus campos siguen siendo privados por defecto. Tienes que decidir campo por campo qué quieres exponer.

mod restaurante {
    pub struct Desayuno {
        pub tostada: String, // El cliente puede elegir la tostada
        fruta: String,       // La fruta la elige el chef (privada)
    }

    impl Desayuno {
        // Necesitamos un constructor público, porque desde fuera
        // no pueden instanciar 'fruta' directamente.
        pub fn verano(tostada: &str) -> Desayuno {
            Desayuno {
                tostada: String::from(tostada),
                fruta: String::from("melocotón"),
            }
        }
    }
}

fn main() {
    let mut comida = restaurante::Desayuno::verano("Integral");

    comida.tostada = String::from("Blanca"); // ✅ Podemos cambiar lo público
    // comida.fruta = String::from("piña");  // ❌ Error: campo privado
}
Copied!

Esto es genial para la encapsulación. Te permite cambiar la implementación interna (fruta) sin romper el código de quien usa tu librería.

Rutas y use: crear atajos

Escribir crate::modulo::submodulo::funcion() cada vez es tedioso. La palabra clave use nos permite crear un “enlace simbólico” o atajo en el ámbito actual.

pub mod cientifico {
    pub mod calculos {
        pub fn sumar(a: i32, b: i32) -> i32 { a + b }
    }
}

// Traemos el camino al ámbito actual
use cientifico::calculos;

fn main() {
    // Ahora podemos llamar a 'calculos' directamente
    let x = calculos::sumar(1, 2);
}
Copied!

Por convención:

  • Para funciones: Trae con use el módulo padre (use cientifico::calculos) y llama a calculos::sumar(). Así sabes que la función no es local.
  • Para structs/enums: Trae la ruta completa (use std::collections::HashMap) y usa HashMap::new().

Rutas absolutas y relativas

Cuando usamos use o llamamos a funciones, podemos especificar la ruta de dos formas:

  1. Ruta absoluta (crate::...): Empieza desde la raíz del crate (main.rs o lib.rs). Deja claro el origen, aunque también puede necesitar cambios si reorganizamos el árbol de módulos.
  2. Ruta Relativa (self::... o super::...): Empieza desde el módulo actual.

Subir al padre con super

La palabra clave super es equivalente a .. en la terminal. Te permite subir un nivel en la jerarquía de módulos para acceder a cosas del módulo padre.

Es extremadamente útil en los Tests Unitarios, que suelen escribirse en un submódulo dentro del mismo archivo.

fn funcion_importante() {
    println!("¡Soy importante!");
}

mod tests {
    use super::funcion_importante; // "Sube arriba y tráeme esto"

    #[test]
    fn prueba_algo() {
        funcion_importante();
    }
}
Copied!

Re-exportación (pub use)

A veces, la estructura interna de archivos de tu proyecto es compleja y ordenada para ti (el desarrollador), pero incómoda para el usuario de tu librería.

Imagina esto: mi_libreria::motor::combustion::cilindros::Piston.

Para un usuario, escribir eso es horrible. Preferiría escribir mi_libreria::Piston. Podemos usar pub use para re-exportar un elemento desde una ubicación diferente.

// src/lib.rs

mod motor; // Declaramos el módulo interno

// Re-exportamos Piston para que parezca que está en la raíz
pub use crate::motor::combustion::cilindros::Piston;

pub fn arrancar() {
    let p = Piston::new(); // Ahora es accesible fácilmente
}
Copied!

Esto se llama el patrón Fachada (Facade). Te permite tener una organización interna compleja pero ofrecer una API pública plana y sencilla.