rust-visibilidad-rutas-pub-use

Visibility and paths in Rust: pub, use, super and more

  • 4 min

The visibility and paths are the access and naming rules between modules.

In the previous article we created a tree of modules. If you tried to call a function from a child module in main.rs, Rust probably yelled an error: module xxx is private.

In Rust, elements are private by default, with specific exceptions like the variants of a public enum.

Unlike Java or C++ where sometimes classes are public by default, Rust adopts a maximum security stance: no one outside the current module can see or use anything, unless you give them explicit permission.

The pub keyword

To make a function, struct, enum or module accessible from its parent module, we must add the pub (public) keyword at the beginning.

mod kitchen {
    // This function is private. Only visible within 'kitchen'.
    fn cook_secret() { ... }

    // This function is public. Visible from 'main' (the parent).
    pub fn prepare_dish() {
        cook_secret(); // 'kitchen' can see its own private functions
    }
}

fn main() {
    kitchen::prepare_dish(); // ✅ Works
    // kitchen::cook_secret(); // ❌ Error: private function
}
Copied!

Privacy in structs

Here’s a common trap. If you make a struct public, its fields are still private by default. You have to decide field by field what you want to expose.

mod restaurant {
    pub struct Breakfast {
        pub toast: String, // The customer can choose the toast
        fruit: String,       // The fruit is chosen by the chef (private)
    }

    impl Breakfast {
        // We need a public constructor, because from outside
        // they cannot instantiate 'fruit' directly.
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                fruit: String::from("peach"),
            }
        }
    }
}

fn main() {
    let mut meal = restaurant::Breakfast::summer("Whole wheat");

    meal.toast = String::from("White"); // ✅ We can change public fields
    // meal.fruit = String::from("pineapple");  // ❌ Error: private field
}
Copied!

This is great for encapsulation. It allows you to change the internal implementation (fruit) without breaking the code of whoever uses your library.

Paths and use: creating shortcuts

Writing crate::module::submodule::function() every time is tedious. The use keyword allows us to create a “symbolic link” or shortcut in the current scope.

pub mod scientist {
    pub mod calculations {
        pub fn add(a: i32, b: i32) -> i32 { a + b }
    }
}

// Bring the path into the current scope
use scientist::calculations;

fn main() {
    // Now we can call 'calculations' directly
    let x = calculations::add(1, 2);
}
Copied!

By convention:

  • For functions: Bring the parent module with use (use scientist::calculations) and call calculations::add(). This way you know the function is not local.
  • For structs/enums: Bring the full path (use std::collections::HashMap) and use HashMap::new().

Absolute and relative paths

When we use use or call functions, we can specify the path in two ways:

  1. Absolute path (crate::...): Starts from the crate root (main.rs or lib.rs). It makes the origin clear, although it may also require changes if we reorganize the module tree.
  2. Relative path (self::... or super::...): Starts from the current module.

Going up to the parent with super

The super keyword is equivalent to .. in the terminal. It allows you to go up one level in the module hierarchy to access things from the parent module.

It is extremely useful in Unit Tests, which are often written in a submodule within the same file.

fn important_function() {
    println!("I am important!");
}

mod tests {
    use super::important_function; // "Go up and bring me this"

    #[test]
    fn test_something() {
        important_function();
    }
}
Copied!

Re-exporting (pub use)

Sometimes, the internal file structure of your project is complex and organized for you (the developer), but inconvenient for the user of your library.

Imagine this: my_library::engine::combustion::cylinders::Piston.

For a user, writing that is horrible. They would prefer to write my_library::Piston. We can use pub use to re-export an element from a different location.

// src/lib.rs

mod engine; // We declare the internal module

// Re-export Piston so it appears to be at the root
pub use crate::engine::combustion::cylinders::Piston;

pub fn start() {
    let p = Piston::new(); // Now it is easily accessible
}
Copied!

This is called the Facade pattern. It allows you to have a complex internal organization but offer a flat and simple public API.