[Avg. reading time: 13 minutes]

User Defined Modules

Rust allows developers to create their own modules to organize code.

A module groups related functionality together.

Modules can contain:

  • Functions
  • Structs
  • Enums
  • Constants
  • Traits
  • Other modules

This helps large programs remain organized and maintainable.


Default Visibility

Items inside a module are private by default.

To make a function accessible outside the module, use the pub keyword.


Simple Module Example

mod sales {
    pub fn meet_customer() {
        println!("meet customer");
    }
}

fn main() {
    sales::meet_customer();
}
mod sales creates a module

pub fn makes the function visible outside the module

The function is accessed using the module path

sales::meet_customer()

Passing Parameters

Modules behave just like normal Rust functions.

They can receive parameters.

mod sales {
    pub fn meet_customer(num: i32) {
        println!("meet customer {num}");
    }
}

fn main() {
    sales::meet_customer(1);
}

Nested Modules

Large systems often require hierarchical structure.

Modules can contain other modules.

Example structure:

departments
 └── sales
 └── service
 └── hr
mod departments {
   pub mod sales {
       pub fn meet_customer(num: i32) {
           println!("meet customer {num}");
       }
   }
}

fn main() {
   departments::sales::meet_customer(1);
}
crate
 ├─ main()
 └─ departments
      └─ sales
  • main() and departments are in the same parent scope (crate root).
  • So main() can see departments without pub.
  • But sales is inside departments, so to expose it to the parent (crate root) it must be pub.

Exposing Limited Functionality

Modules allow internal helper functions to remain private.

Only selected functions are exposed publicly.

mod departments {
    pub mod sales {
        pub fn meet_customer(num: i32, requestedby: &str) {
            println!("meet customer {num}");

            let phone_number = get_number(num, requestedby);
            println!("calling {:?}", phone_number);
        }

        fn get_number(num: i32, requestedby: &str) -> String {
            let phonenumber = match num {
                1 => "123-456-7890".to_string(),
                2 => "987-654-3210".to_string(),
                _ => "000-000-0000".to_string(),
            };

            if requestedby == "Manager" {
                phonenumber
            } else if requestedby == "CustService" {
                phonenumber[8..].to_string()
            } else {
                "".to_string()
            }
        }
    }
}

fn main() {
    departments::sales::meet_customer(1, "Manager");
    departments::sales::meet_customer(1, "CustService");
}
  • meet_customer() is public
  • get_number is private
  • main() cannot call get_number()
  • This is called encapsulation.

Using super

The super keyword refers to the parent module.

This allows child modules to access functions from their parent module.

mod departments {

    fn get_number(num: i32) -> String {
        match num {
            1 => "123-456-7890".to_string(),
            2 => "987-654-3210".to_string(),
            _ => "000-000-0000".to_string(),
        }
    }

    pub mod sales {
        pub fn meet_customer(num: i32) {
            println!("Sales : meet customer {num}");

            let phone_number = super::get_number(num);

            println!("Sales calling {}", phone_number);
        }
    }

    pub mod service {
        pub fn meet_customer(num: i32) {
            println!("Service : meet customer {num}");

            let phone_number = super::get_number(num);

            println!("Service calling {}", phone_number);
        }
    }
}

fn main() {
    departments::sales::meet_customer(1);
    departments::service::meet_customer(3);
}

Both modules access the same helper function using super.

Using Self

Self usage is optional, added for better clarity. It is useful when calling functions within the same module.

mod departments {

    fn get_number(num: i32) -> String {
        match num {
            1 => "123-456-7890".to_string(),
            2 => "987-654-3210".to_string(),
            _ => "000-000-0000".to_string(),
        }
    }

    pub mod service {

        pub fn meet_customer(num: i32) {

            let phone_number = super::get_number(num);

            let ticket_number = self::get_service_ticket_number(num);

            println!(
                "Calling {phone_number} with ticket number {ticket_number}"
            );
        }

        fn get_service_ticket_number(num: i32) -> i32 {
            match num {
                1 => 2452423,
                2 => 2341332,
                _ => 6868765,
            }
        }
    }
}

fn main() {
    departments::service::meet_customer(3);
}
self::get_service_ticket_number()

means “call a function in the same module”.

Writing Tests Inside Modules

Rust allows unit tests to be written inside modules.

Test modules are compiled only when running tests.

cargo new demo_mod_test
mod departments {

    fn get_number(num: i32) -> String {
        match num {
            1 => "123-456-7890".to_string(),
            2 => "987-654-3210".to_string(),
            _ => "000-000-0000".to_string(),
        }
    }

    #[cfg(test)]
    mod tests {

        #[test]
        fn test_customer_phone() {
            assert_eq!(
                "000-000-0000",
                super::get_number(4)
            );
        }
    }
}
cargo test
  • #[cfg(test)] compiles the module only during testing
  • #[test] marks a test function
  • assert_eq! validates expected output

Summary

  • Modules organize Rust code into logical units
  • Items are private by default
  • pub exposes functions outside the module
  • Modules can be nested
  • super accesses the parent module
  • self refers to the current module
  • Tests can be embedded inside modules

#super #self #module #privateVer 2.0.19

Last change: 2026-03-04