Question
Is it possible to define a Rust function with default parameter values, similar to this?
fn add(a: int = 1, b: int = 2) {
a + b
}
If not, what is the Rust way to achieve similar behavior?
Short Answer
By the end of this page, you will understand that Rust does not support default function arguments directly. You will learn the common Rust alternatives, such as writing helper functions, using Option<T>, defining configuration structs with Default, and choosing clear APIs that match Rust's style.
Concept
Rust does not support default parameter values in function signatures.
In some languages, you can write a function once and let callers omit some arguments. Rust intentionally avoids this feature in favor of explicit APIs. This makes function calls easier to read and avoids ambiguity about which values are being passed.
For example, Rust does not allow this:
fn add(a: i32 = 1, b: i32 = 2) -> i32 {
a + b
}
Instead, Rust developers usually use one of these patterns:
- Create separate helper functions for common defaults
- Use
Option<T>when a parameter may or may not be provided - Use a struct with
Defaultwhen there are many configurable values - Provide constructor-like methods such as
new()andwith_x()
This matters in real programming because API design is a big part of writing maintainable Rust code. Rather than relying on hidden defaults in function signatures, Rust encourages you to make defaults visible and intentional.
Mental Model
Think of a Rust function call like filling out a paper form.
In some languages, you can leave fields blank and the system fills them in automatically. Rust usually prefers that you either:
- fill in every field clearly, or
- choose from a few well-defined forms, or
- submit a settings object that already contains sensible defaults.
So instead of saying, "This field is optional inside the function signature," Rust says, "Let's design the interface so defaults are obvious and explicit."
Syntax and Examples
1. Separate helper functions
This is the simplest Rust-style approach when there are only a few common combinations.
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn add_default() -> i32 {
add(1, 2)
}
fn add_with_a(a: i32) -> i32 {
add(a, 2)
}
fn main() {
println!("{}", add(5, 10));
println!("{}", add_default());
println!("{}", add_with_a(7));
}
Here:
addis the full functionadd_defaultuses both default valuesadd_with_alets you override one value
2. Use Option<T> for optional inputs
fn add(a: Option<i32>, b: Option<i32>) -> i32 {
let a = a.unwrap_or(1);
let b = b.unwrap_or(2);
a + b
}
fn main() {
println!("{}", add(None, None));
println!("{}", add(Some(10), None));
println!("{}", add(Some(10), Some(20)));
}
unwrap_or means:
- use the provided value if it exists
- otherwise use the fallback value
This works, but it can make function calls more verbose.
3. Use a struct with Default
This is a strong choice when a function has many settings.
#[derive(Default)]
struct AddOptions {
a: i32,
b: i32,
}
fn add(opts: AddOptions) -> i32 {
opts.a + opts.b
}
fn main() {
let result1 = add(AddOptions { a: 1, b: 2 });
let result2 = add(AddOptions { a: 10, ..Default::default() });
println!("{}", result1);
println!("{}", result2);
}
If the derived defaults are not what you want, implement Default manually:
struct AddOptions {
a: i32,
b: i32,
}
impl Default for AddOptions {
fn default() -> Self {
Self { a: 1, b: 2 }
}
}
fn add(opts: AddOptions) -> i32 {
opts.a + opts.b
}
Now you can override only the fields you want while keeping clear defaults.
Step by Step Execution
Consider this example:
fn add(a: Option<i32>, b: Option<i32>) -> i32 {
let a = a.unwrap_or(1);
let b = b.unwrap_or(2);
a + b
}
fn main() {
let result = add(Some(10), None);
println!("{}", result);
}
Step by step:
maincallsadd(Some(10), None).- Inside
add, the parameteraisSome(10). a.unwrap_or(1)checks whetheracontains a value.- Since it is
Some(10), the result becomes10. - The parameter
bisNone. b.unwrap_or(2)sees there is no value, so it uses2.- The function returns
10 + 2, which is12. mainprints12.
This shows how Rust can simulate default arguments using Option<T> and fallback values.
Real World Use Cases
Common places this idea appears
Command-line tools
A CLI might accept optional flags. If the user does not provide a value, your program uses a sensible default.
fn run(port: Option<u16>) {
let port = port.unwrap_or(8080);
println!("Starting server on port {}", port);
}
Configuration objects
Applications often have many settings such as timeout, retries, and log level. A config struct with Default makes this manageable.
Constructors
A type might provide:
new()for standard defaultswith_capacity(...)or similar methods for customization
Library APIs
Rust libraries often avoid many positional arguments. Instead, they use:
- builder patterns
- option structs
- helper constructors
This keeps code readable as APIs grow.
Real Codebase Usage
In real Rust codebases, developers usually avoid trying to imitate default arguments too closely. Instead, they choose patterns that scale well.
Common patterns
Helper functions for common cases
Useful when there are only one or two default combinations.
fn connect(host: &str, port: u16) {
println!("Connecting to {}:{}", host, port);
}
fn connect_local() {
connect("localhost", 5432);
}
Guarding optional input with Option<T>
This is common when values truly may be absent.
fn fetch(limit: Option<usize>) {
let limit = limit.unwrap_or(100);
println!("Fetching up to {} items", limit);
}
Config structs with Default
This is common in libraries and applications.
struct ServerConfig {
host: String,
port: u16,
use_tls: bool,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
use_tls: false,
}
}
}
Builder-style APIs
When there are many optional settings, builders are often clearer than long parameter lists.
#[derive(Debug)]
struct ServerConfig {
host: String,
port: u16,
}
impl ServerConfig {
fn new() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
}
}
fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
}
These patterns make defaults visible and easier to maintain.
Common Mistakes
1. Trying to write default values in the parameter list
This is not valid Rust.
fn add(a: i32 = 1, b: i32 = 2) -> i32 {
a + b
}
Use a helper function, Option<T>, or a config struct instead.
2. Using Option<T> when values are not truly optional
This works technically, but may make your API harder to use.
fn connect(host: Option<&str>, port: Option<u16>) {
let host = host.unwrap_or("localhost");
let port = port.unwrap_or(5432);
}
If every caller almost always uses defaults, a config struct or helper function may be clearer.
3. Deriving Default when the generated values are wrong
This code compiles, but 0 may not be the default you want.
#[derive(Default)]
struct AddOptions {
a: i32,
b: i32,
}
This gives a = 0 and b = 0. If you want 1 and 2, implement Default manually.
4. Using too many positional parameters
This becomes hard to read:
fn create_user(name: &str, age: u8, active: bool, admin: bool, timeout: u32) {
// ...
}
Prefer a struct when the number of parameters grows.
5. Forgetting return types
Rust functions that return a value should declare it explicitly.
fn add(a: i32, b: i32) -> i32 {
a + b
}
Older Rust examples may show int, but modern Rust uses types like i32, u32, and usize.
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Helper functions | A few common defaults | Simple and readable | Can multiply into many functions |
Option<T> parameters | Truly optional values | Flexible | Calls can become noisy with Some(...) and None |
Struct + Default | Many settings | Scales well, self-documenting | More code than a tiny function |
| Builder pattern | Complex configuration | Very readable for many optional fields | More setup code |
Option<T> vs config struct
| Choice | Use when |
|---|---|
Option<T> | Only one or two values may be missing |
| Config struct | You have several related settings |
Helper function vs builder
| Choice | Use when |
|---|---|
| Helper function | You want one or two convenient shortcuts |
| Builder | You need a customizable API with many optional settings |
Cheat Sheet
// Rust does NOT support default function arguments
fn add(a: i32, b: i32) -> i32 {
a + b
}
Common replacements
1. Helper function
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn add_default() -> i32 {
add(1, 2)
}
2. Option<T>
fn add(a: Option<i32>, b: Option<i32>) -> i32 {
a.unwrap_or(1) + b.unwrap_or(2)
}
3. Struct with Default
struct AddOptions {
a: i32,
b: i32,
}
impl Default for AddOptions {
fn default() -> Self {
Self { a: 1, b: 2 }
}
}
Key rules
- Rust function parameters cannot have default values in the signature.
- Prefer explicit APIs over hidden defaults.
- Use helper functions for small cases.
- Use
Option<T>for truly optional values. - Use
Defaultand structs for larger configurations.
Modern Rust note
- Use
i32,u32,usize, etc. - Do not use old
intsyntax from very early Rust examples.
FAQ
Does Rust support default function parameters?
No. Rust does not allow default values in function parameter lists.
What is the Rust alternative to default arguments?
Common alternatives are helper functions, Option<T>, config structs with Default, and builder patterns.
Should I use Option<T> to simulate default arguments?
Only when the value is genuinely optional. If many settings need defaults, a struct is usually clearer.
How do I set custom default values in Rust?
Implement the Default trait manually:
impl Default for MyType {
fn default() -> Self {
Self { /* custom values */ }
}
}
Why doesn't Rust include default arguments?
Rust generally prefers explicit function calls and clear API design over hidden behavior in parameter lists.
Is a builder pattern better than Option<T>?
For complex configuration, yes. Builders are often easier to read and maintain.
Can I overload functions in Rust like other languages?
Not in the usual way. Rust does not support traditional function overloading by parameter list. Developers usually use different function names, traits, or configuration types.
Mini Project
Description
Build a small Rust program that calculates the price of an order using sensible defaults. This demonstrates how to replace default function arguments with a configuration struct and the Default trait.
Goal
Create a function that calculates a final price while allowing callers to override only the settings they care about.
Requirements
- Create a struct to store pricing options.
- Implement
Defaultwith meaningful values. - Write a function that uses the options struct to calculate a total.
- Show one call using all defaults and another overriding only one field.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.