20.7 Encapsulation via Modules and Visibility

In C++, encapsulation relies on public, protected, and private specifiers within class definitions. Rust achieves encapsulation primarily at the module level using its visibility rules:

  • Private by Default: Items (structs, enums, functions, methods, constants, modules, fields) are private to the module they are defined in. They cannot be accessed from outside the module, including parent or child modules, unless explicitly made public.
  • Public Interface (pub): The pub keyword makes an item visible outside its defining module. Visibility can be restricted further (e.g., pub(crate), pub(super)), but pub typically means public to any code that can access the module.
  • Struct Field Privacy: Even if a struct is declared pub, its fields remain private by default. Each field must be individually marked pub to be accessible from outside the module. This allows structs to maintain internal invariants by controlling access through public methods defined in an impl block.

This module-based system provides strong encapsulation boundaries, allowing library authors to clearly define a public API while hiding implementation details.

Example: Encapsulated Averaging Collection

mod math_utils {
    // The struct is public.
    pub struct AverageCollection {
        // Fields are private, enforcing use of methods.
        elements: Vec<i32>,
        sum: i64, // Use i64 to avoid overflow on sum
    }

    impl AverageCollection {
        // Public constructor-like associated function.
        pub fn new() -> Self {
            AverageCollection {
                elements: Vec::new(),
                sum: 0,
            }
        }

        // Public method to add an element.
        pub fn add(&mut self, value: i32) {
            self.elements.push(value);
            self.sum += value as i64;
        }

        // Public method to calculate the average.
        // Returns None if the collection is empty.
        pub fn average(&self) -> Option<f64> {
            if self.elements.is_empty() {
                None
            } else {
                Some(self.sum as f64 / self.elements.len() as f64)
            }
        }

        // An internal helper method (private by default).
        #[allow(dead_code)] // Prevent warning for unused private method
        fn clear_cache(&mut self) {
            // Potential internal logic irrelevant to the public API
        }
    }
}

fn main() {
    let mut collection = math_utils::AverageCollection::new();
    collection.add(10);
    collection.add(20);
    collection.add(30);

    println!("Average: {:?}", collection.average()); // Output: Average: Some(20.0)

    // These would fail to compile because fields are private:
    // let _ = collection.elements;
    // collection.sum = 0;
    // This would fail as the method is private:
    // collection.clear_cache();
}

Users of AverageCollection interact solely through new, add, and average. The internal storage (elements, sum) and any private helper methods (clear_cache) are implementation details hidden within the math_utils module, ensuring the collection’s integrity.