8.6 Handling Optional and Named Parameters
Unlike languages such as Python or C++, Rust does not have built-in support for:
- Default parameter values: Providing a default value if an argument isn’t supplied.
- Named arguments: Passing arguments using
parameter_name = value
syntax, allowing arbitrary order.
All function arguments in Rust must be explicitly provided by the caller in the exact order specified in the function signature.
However, Rust offers idiomatic patterns to achieve similar flexibility:
8.6.1 Using Option<T>
for Optional Parameters
The standard library type Option<T>
(Chapter 14) can represent a value that might be present (Some(value)
) or absent (None
). This is commonly used to simulate optional parameters.
// 'level' is an optional parameter. fn log_message(message: &str, level: Option<&str>) { // Use unwrap_or to provide a default value if 'level' is None. let log_level = level.unwrap_or("INFO"); println!("[{}] {}", log_level, message); } fn main() { log_message("User logged in.", None); // Use default level "INFO". log_message("Disk space low!", Some("WARN")); // Provide a specific level. }
8.6.2 The Builder Pattern for Complex Configuration
For functions with multiple configurable parameters, especially optional ones, the Builder Pattern is often used. This involves creating a separate Builder
struct that accumulates configuration settings via method calls before finally constructing the desired object or performing the action.
struct WindowConfig { title: String, width: u32, height: u32, resizable: bool, } // Builder struct struct WindowBuilder { title: String, width: Option<u32>, height: Option<u32>, resizable: Option<bool>, } impl WindowBuilder { // Start building with a mandatory parameter (title) fn new(title: String) -> Self { WindowBuilder { title, width: None, height: None, resizable: None, } } // Methods to set optional parameters fn width(mut self, width: u32) -> Self { self.width = Some(width); self // Return self to allow chaining } fn height(mut self, height: u32) -> Self { self.height = Some(height); self } fn resizable(mut self, resizable: bool) -> Self { self.resizable = Some(resizable); self } // Final build method using defaults for unspecified options fn build(self) -> WindowConfig { WindowConfig { title: self.title, width: self.width.unwrap_or(800), // Default width height: self.height.unwrap_or(600), // Default height resizable: self.resizable.unwrap_or(true), // Default resizable } } } fn main() { let window1 = WindowBuilder::new("My App".to_string()).build(); // Use all defaults let window2 = WindowBuilder::new("Editor".to_string()) .width(1024) .height(768) .resizable(false) .build(); // Specify some options println!("Window 1: width={}, height={}, resizable={}", window1.width, window1.height, window1.resizable); println!("Window 2: width={}, height={}, resizable={}", window2.width, window2.height, window2.resizable); }
The Builder pattern provides clear, readable configuration and handles defaults gracefully, making it a robust alternative to named/default parameters for complex function calls or object construction.