Question

Default Function Arguments in Rust: What to Use Instead

rustfunctionparametersarguments

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 Default when there are many configurable values
  • Provide constructor-like methods such as new() and with_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."

Take Quiz

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:

  • add is the full function
  • add_default uses both default values
  • add_with_a lets 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:

  1. main calls add(Some(10), None).
  2. Inside add, the parameter a is Some(10).
  3. a.unwrap_or(1) checks whether a contains a value.
  4. Since it is Some(10), the result becomes 10.
  5. The parameter b is None.
  6. b.unwrap_or(2) sees there is no value, so it uses 2.
  7. The function returns 10 + 2, which is 12.
  8. main prints 12.

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 defaults
  • with_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.

Take Quiz

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

ApproachBest forProsCons
Helper functionsA few common defaultsSimple and readableCan multiply into many functions
Option<T> parametersTruly optional valuesFlexibleCalls can become noisy with Some(...) and None
Struct + DefaultMany settingsScales well, self-documentingMore code than a tiny function
Builder patternComplex configurationVery readable for many optional fieldsMore setup code

Option<T> vs config struct

ChoiceUse when
Option<T>Only one or two values may be missing
Config structYou have several related settings

Helper function vs builder

ChoiceUse when
Helper functionYou want one or two convenient shortcuts
BuilderYou need a customizable API with many optional settings
Take Quiz

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 Default and structs for larger configurations.

Modern Rust note

  • Use i32, u32, usize, etc.
  • Do not use old int syntax 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.

Take Quiz

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 Default with 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.
Take Quiz

Keep learning

Related questions

View more