| |

Rust 27 🦀 Modules System, Privacy Rules (pub), and use Declarations

A Rust program is organized into modules. A module is a namespace that contains items—functions, structs, enums, traits, constants, and other modules. The module system determines what is visible where, what is private by default, and how paths are resolved. Without it, every item would be in a single global namespace, and name collisions would be inevitable. With it, a library can expose a small public API and keep the rest private, and a large application can be divided into logical units that do not interfere with each other.

This chapter covers Rust’s module system in full. You will learn the mod declaration, the file and directory structure that modules map to, the privacy rules that make items private by default, the pub keyword and its variants, and the use declarations that bring items into scope. You will also see the path syntax—crate, self, super, and absolute paths—and the patterns for organizing a crate into a clean public API.

Key point: Items in Rust are private by default. A function, struct, or field that is not marked pub is visible only within its own module and its descendants. The pub keyword makes an item visible to the parent module and, with the right path, to the rest of the crate or to external code. The module system is the mechanism that enforces encapsulation.


Why the module system exists

The namespace problem. A large program has thousands of items. Without namespaces, two functions named new or two structs named Config would collide. Modules provide separate namespaces. user::Config and server::Config can coexist. The path syntax disambiguates them.

The encapsulation problem. Not every item should be public. A helper function, an internal cache, a field that should not be modified directly—these are implementation details. Rust makes items private by default. The author decides what to expose, and the compiler enforces the boundary. A change to a private item cannot break external code because external code cannot see it.

The organization problem. A file with ten thousand lines is hard to navigate. Modules let a program be divided into files and directories, each with a coherent purpose. A models module contains the data structures. A handlers module contains the request handlers. A db module contains the database access. The structure of the code mirrors the structure of the problem.

The path problem. When you use an item from another module, you need a path to it. Rust has several path forms: crate:: for the current crate, self:: for the current module, super:: for the parent module, and the name of an external crate. The use declaration brings an item into scope so it can be referred to by a short name.

The visibility problem. The pub keyword has variants. pub makes an item visible to the parent module and, transitively, to the rest of the crate. pub(crate) makes it visible only within the crate. pub(super) makes it visible to the parent module. pub(in path) makes it visible to a specific module. The variants allow fine-grained control over where an item can be seen.

The file-mapping problem. A module can be declared inline with mod name { }, or in a separate file with mod name;. The file is named name.rs or name/mod.rs. The file structure mirrors the module structure. This is a convention, not a requirement, but following it keeps the code navigable.


a. Declaring modules and files

A module is declared with the mod keyword. It can be inline or in a separate file.

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

    fn helper() -> i32 {
        42
    }
}

fn main() {
    println!("{}", math::add(2, 3));
    // math::helper(); // error: private
}

The math module contains two functions. add is public, so it can be called from main with the path math::add. helper is private, so it can only be called within the math module.

The file-based form:

// src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn helper() -> i32 {
    42
}
// src/main.rs
mod math;

fn main() {
    println!("{}", math::add(2, 3));
}

The mod math; declaration tells the compiler to look for src/math.rs and include its contents as the math module. The file’s items are the module’s items. The privacy rules apply the same way.

A module can contain submodules:

// src/math.rs
pub mod geometry {
    pub fn area(width: f64, height: f64) -> f64 {
        width * height
    }
}

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

The geometry submodule is declared inline. Its area function is called with the path math::geometry::area.

A submodule can also be in a file:

// src/math/mod.rs
pub mod geometry;

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
// src/math/geometry.rs
pub fn area(width: f64, height: f64) -> f64 {
    width * height
}

The src/math/mod.rs file is the parent module. The src/math/geometry.rs file is the submodule. The path math::geometry::area resolves to the function in the submodule.

In Rust 2018 and later, the mod.rs convention is optional. A file can have submodules in a directory with the same name:

src/
  math.rs          -> mod math
  math/
    geometry.rs    -> mod geometry (submodule of math)

The math.rs file declares pub mod geometry;, and the compiler looks for src/math/geometry.rs. Both the mod.rs form and the math.rs + math/ form work. The second is preferred in modern Rust because it keeps the parent module’s name in the filename.


b. Privacy rules and pub variants

Items are private by default. A private item is visible to the module that declares it and to all descendant modules.

mod outer {
    fn private_fn() {}

    mod inner {
        fn call_private() {
            super::private_fn(); // OK: inner is a descendant of outer
        }
    }
}

The inner module can call outer::private_fn because inner is a descendant of outer. Private items are visible up and down the module tree within the declaring module’s subtree.

A parent module cannot access a child’s private items:

mod outer {
    mod inner {
        fn secret() {}
    }

    fn try_access() {
        // inner::secret(); // error: private
    }
}

The secret function is private to inner. The parent outer cannot see it. Privacy flows down, not up.

The pub keyword makes an item visible to the parent module and, transitively, to the rest of the crate.

mod outer {
    pub fn public_fn() {}

    mod inner {
        pub fn call_outer() {
            super::public_fn(); // OK
        }
    }
}

fn main() {
    outer::public_fn(); // OK
}

The variants of pub restrict visibility more precisely:

VariantVisible To
pubParent module and the rest of the crate (and external code if the module is public)
pub(crate)The entire crate
pub(super)The parent module
pub(self)The current module (equivalent to private)
pub(in path)The specified module
mod outer {
    pub(crate) fn crate_visible() {}
    pub(super) fn parent_visible() {}
    pub(in crate::outer) fn module_visible() {}
}

The crate_visible function is visible anywhere in the crate. The parent_visible function is visible to the parent of outer, which is the crate root. The module_visible function is visible only within outer and its descendants.

Struct fields have their own privacy. A struct can be public while its fields are private.

mod models {
    pub struct User {
        pub name: String,
        email: String, // private
    }

    impl User {
        pub fn new(name: String, email: String) -> Self {
            User { name, email }
        }

        pub fn email(&self) -> &str {
            &self.email
        }
    }
}

fn main() {
    let user = models::User::new(
        String::from("Alice"),
        String::from("alice@example.com"),
    );
    println!("{}", user.name);   // OK: public field
    // println!("{}", user.email); // error: private field
    println!("{}", user.email()); // OK: public method
}

The name field is public and can be accessed directly. The email field is private and can only be accessed through the public email() method. This is the pattern for encapsulation: the struct is public, the data is private, and access is through methods.

Enum variants are always public if the enum is public. There is no way to make one variant private and another public.

pub enum Status {
    Active,
    Inactive,
}

Both variants are public. A private enum has private variants. The visibility is all-or-nothing.


c. Paths and use declarations

A path identifies an item. The forms are:

PathMeaning
crate::From the crate root
self::From the current module
super::From the parent module
name::From a module in scope or an external crate
mod a {
    pub mod b {
        pub fn c() {}
    }
}

fn main() {
    crate::a::b::c();     // absolute from crate root
    a::b::c();            // relative from crate root (in 2018+)
    self::a::b::c();      // explicit self
}

In Rust 2018 and later, paths in use declarations can start with a crate name or crate::. The self:: prefix is used for clarity when a name might be ambiguous.

The use declaration brings an item into scope so it can be referred to by a short name.

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

use math::add;

fn main() {
    println!("{}", add(2, 3)); // short name
}

The use math::add; declaration makes add available in the current scope. Without it, the call would be math::add(2, 3).

The use declaration can rename an item with as:

use math::add as sum;

fn main() {
    println!("{}", sum(2, 3));
}

The use declaration can bring multiple items with a group:

use math::{add, subtract, multiply};

// or with self
use math::{self, add};

The self in the group refers to the module itself. use math::{self, add} brings math and math::add into scope.

The glob import brings all public items:

use math::*;

fn main() {
    add(2, 3);
    subtract(5, 3);
}

Glob imports are convenient but make it unclear where names come from. They are acceptable for preludes and test modules, and discouraged elsewhere.

The pub use re-exports an item, making it part of the current module’s public API.

mod internal {
    pub fn helper() {}
}

pub use internal::helper;

// External code can use crate::helper
// even though the function is defined in internal

The re-export is the mechanism for building a public API from private modules. The internal structure is hidden, and the public surface is the re-exported items.

use declarations can be nested for deep paths:

use std::collections::HashMap;
use std::collections::HashSet;

// or
use std::collections::{HashMap, HashSet};

The grouped form is preferred when importing multiple items from the same module.


Complete Example Session

// ============================================
// PART 1: INLINE MODULE
// ============================================
mod math {
    pub fn add(a: i32, b: i32) -> i32 { a + b }
    fn helper() -> i32 { 42 }
}

fn main() {
    println!("{}", math::add(2, 3));
    // math::helper(); // error
}
// ============================================
// PART 2: FILE-BASED MODULE
// ============================================
// src/math.rs
pub fn add(a: i32, b: i32) -> i32 { a + b }

// src/main.rs
mod math;

fn main() {
    println!("{}", math::add(2, 3));
}
// ============================================
// PART 3: SUBMODULE
// ============================================
// src/math.rs
pub mod geometry {
    pub fn area(w: f64, h: f64) -> f64 { w * h }
}

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

// src/main.rs
mod math;

fn main() {
    math::geometry::area(3.0, 4.0);
}
// ============================================
// PART 4: PRIVACY
// ============================================
mod outer {
    fn private_fn() {}

    mod inner {
        fn call_private() {
            super::private_fn(); // OK
        }
    }
}
// ============================================
// PART 5: PUB VARIANTS
// ============================================
mod outer {
    pub fn public_fn() {}
    pub(crate) fn crate_fn() {}
    pub(super) fn parent_fn() {}
}
// ============================================
// PART 6: PRIVATE STRUCT FIELDS
// ============================================
mod models {
    pub struct User {
        pub name: String,
        email: String,
    }

    impl User {
        pub fn new(name: String, email: String) -> Self {
            User { name, email }
        }

        pub fn email(&self) -> &str { &self.email }
    }
}
// ============================================
// PART 7: PATHS
// ============================================
mod a {
    pub mod b {
        pub fn c() {}
    }
}

fn main() {
    crate::a::b::c();
    a::b::c();
    self::a::b::c();
}
// ============================================
// PART 8: USE DECLARATION
// ============================================
mod math {
    pub fn add(a: i32, b: i32) -> i32 { a + b }
}

use math::add;

fn main() {
    println!("{}", add(2, 3));
}
// ============================================
// PART 9: RENAME AND GROUP
// ============================================
use math::add as sum;
use std::collections::{HashMap, HashSet};
// ============================================
// PART 10: PUB USE RE-EXPORT
// ============================================
mod internal {
    pub fn helper() {}
}

pub use internal::helper;

// External code uses crate::helper

The ten parts covered inline modules, file-based modules, submodules, privacy, pub variants, private struct fields, paths, use declarations, renaming and grouping, and pub use re-exports.


Quick Reference

Module Declarations

FormSyntax
Inlinemod name { }
Filemod name;
Submodule inlinemod parent { mod child { } }
Submodule filemod parent; + src/parent/child.rs

Visibility

KeywordVisible To
(none)Current module and descendants
pubParent and the rest of the crate
pub(crate)Entire crate
pub(super)Parent module
pub(self)Current module
pub(in path)Specified module

Path Forms

PathMeaning
crate::From crate root
self::Current module
super::Parent module
name::Module in scope
external_crate::External crate

Use Declarations

FormPurpose
use path::item;Bring into scope
use path::item as alias;Rename
use path::{a, b};Group
use path::{self, a};Module and item
use path::*;Glob
pub use path::item;Re-export

Best Practices

✅ Do This:

// Use pub for the public API
pub fn public_function() {}                                    // ✅

// Use pub(crate) for internal sharing
pub(crate) fn internal_function() {}                           // ✅

// Use pub(super) for parent-only access
pub(super) fn parent_function() {}                             // ✅

// Keep struct fields private by default
pub struct User {
    pub name: String,
    email: String,
}                                                              // ✅

// Use use to shorten paths
use std::collections::HashMap;                                 // ✅

// Use pub use to re-export
pub use internal::PublicType;                                  // ✅

// Group related imports
use std::collections::{HashMap, HashSet};                      // ✅

❌ Don’t Do This:

// Don't make everything public
pub fn helper() {}  // if it should be private                // ⚠️

// Don't use glob imports everywhere
use module::*;                                                 // ⚠️

// Don't expose fields that should be encapsulated
pub struct User {
    pub email: String,  // if it should be private             // ⚠️
}                                                              // ⚠️

// Don't use deep relative paths
super::super::super::something();                              // ⚠️

// Don't forget mod declaration
// fn in math.rs without mod math; in main.rs                  // ❌

// Don't use pub(self), it is equivalent to private
pub(self) fn f() {}                                            // ⚠️

Common Pitfalls

PitfallWhy It HappensFix
Item not foundModule not declaredAdd mod name;
Private function inaccessibleMissing pubAdd pub
Field inaccessiblePrivate fieldAdd pub or an accessor
mod.rs deprecatedOld conventionUse name.rs + name/
Glob import ambiguityTwo items with same nameUse explicit imports
Re-export not workingpub use missingAdd pub use
Path not resolvingWrong prefixUse crate::, super::, or self::

Real-World Examples

1. Inline Module

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

2. File-Based Module

// src/math.rs
pub fn add(a: i32, b: i32) -> i32 { a + b }

// src/main.rs
mod math;

3. Submodule

pub mod geometry {
    pub fn area(w: f64, h: f64) -> f64 { w * h }
}

4. Private Function

fn helper() {} // visible only in this module

5. Crate-Visible Function

pub(crate) fn internal() {} // visible in the crate

6. Parent-Visible Function

pub(super) fn parent_only() {} // visible to parent

7. Private Struct Field

pub struct User {
    pub name: String,
    email: String, // private
}

8. Accessor Method

pub fn email(&self) -> &str { &self.email }

9. Use Declaration

use std::collections::HashMap;

10. Re-Export

pub use internal::PublicType;

Visual

Module Tree

┌─────────────────────────────────────────────────────────────┐
│  crate                                                      │
│  ├── mod math                                               │
│  │   ├── fn add (pub)                                       │
│  │   ├── fn helper (private)                                │
│  │   └── mod geometry                                       │
│  │       └── fn area (pub)                                  │
│  ├── mod models                                             │
│  │   └── struct User (pub)                                  │
│  │       ├── field name (pub)                               │
│  │       └── field email (private)                          │
│  └── fn main                                                │
│                                                             │
│  Paths:                                                     │
│  crate::math::add                                           │
│  crate::math::geometry::area                                │
│  crate::models::User                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Privacy Flow

┌─────────────────────────────────────────────────────────────┐
│  Privacy flows DOWN the module tree.                        │
│                                                             │
│  mod outer {                                                │
│    fn private_fn() {}    ← visible in outer and descendants │
│                                                             │
│    mod inner {                                              │
│      fn call() {                                            │
│        super::private_fn();  ← OK                           │
│      }                                                      │
│    }                                                        │
│  }                                                          │
│                                                             │
│  mod other {                                                │
│    fn try() {                                               │
│      // outer::private_fn();  ← error: not a descendant     │
│    }                                                        │
│  }                                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

pub Variants

┌─────────────────────────────────────────────────────────────┐
│  pub              → parent + rest of crate                  │
│  pub(crate)       → entire crate                            │
│  pub(super)       → parent module                           │
│  pub(self)        → current module (same as private)        │
│  pub(in path)     → specified module                        │
│                                                             │
│  crate                                                      │
│  ├── mod a                                                  │
│  │   ├── pub(crate) fn x()  ← visible in crate              │
│  │   └── pub(super) fn y()  ← visible in crate root         │
│  └── mod b                                                  │
│      fn z() {                                               │
│        crate::a::x();  ← OK                                 │
│      }                                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Use Declaration

┌─────────────────────────────────────────────────────────────┐
│  WITHOUT use                                                │
│                                                             │
│  std::collections::HashMap::new();                          │
│                                                             │
│  WITH use                                                   │
│                                                             │
│  use std::collections::HashMap;                             │
│  HashMap::new();                                            │
│                                                             │
│  The use declaration brings the item into scope.            │
│  The path is still the same; only the call site is shorter. │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
Inline modulemod name { }
File modulemod name;
Submodulemod parent { mod child { } }
Privacy defaultPrivate
Public itempub
Crate-visiblepub(crate)
Parent-visiblepub(super)
Module-visiblepub(in path)
Path from rootcrate::
Path from parentsuper::
Path from currentself::
Bring into scopeuse path::item;
Re-exportpub use path::item;

Key takeaways:

  • Items are private by default. A function, struct, or field that is not marked pub is visible only within its own module and its descendants. The pub keyword makes an item visible more broadly.
  • The pub variants control visibility precisely. pub(crate) for the entire crate, pub(super) for the parent module, pub(in path) for a specific module. The default pub makes the item visible to the parent and, transitively, to the rest of the crate.
  • Modules map to files and directories. A mod name; declaration in the parent file includes name.rs or name/mod.rs. Submodules follow the same pattern in a directory named after the parent module.
  • Paths use crate, self, and super. crate:: refers to the crate root, self:: to the current module, and super:: to the parent module. External crates are referred to by name.
  • use brings items into scope. It does not change the item’s visibility; it only shortens the path at the call site. The as keyword renames, the group syntax imports multiple items, and the glob imports all public items.
  • pub use re-exports. The re-export makes an item from a private module part of the current module’s public API. This is the mechanism for building a clean public surface from internal modules.
  • Struct fields have their own visibility. A struct can be public while its fields are private. The pattern is to make the struct public, keep the fields private, and provide public methods for access. This is encapsulation.
  • Enum variants are always public if the enum is public. There is no way to make one variant private and another public. The visibility is all-or-nothing.

Remember: The module system is how Rust organizes code and enforces encapsulation. It determines what is visible where, what is private, and how paths are resolved. The mod keyword declares a module. The pub keyword makes an item visible. The use declaration brings an item into scope. The privacy rules are the foundation of a stable public API: private items can change without breaking external code, and public items are the contract. Organize your crate into modules that reflect the domain, keep the implementation private, and expose only what the rest of the world needs. The compiler enforces the boundary, and the boundary is what makes the API maintainable.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!