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): Thepubkeyword makes an item visible outside its defining module. Visibility can be restricted further (e.g.,pub(crate),pub(super)), butpubtypically means public to any code that can access the module. - Struct Field Privacy: Even if a
structis declaredpub, its fields remain private by default. Each field must be individually markedpubto be accessible from outside the module. This allows structs to maintain internal invariants by controlling access through public methods defined in animplblock.
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.