279

Is it possible to create a function with a default argument?

fn add(a: int = 1, b: int = 2) { a + b }
3
  • 4
    #6973 contains several work-arounds (using a struct).
    – huon
    Commented Jun 5, 2014 at 9:04
  • In 2020, how do you can code it? Commented May 26, 2020 at 12:18
  • 1
    @puentesdias The accepted answer is still the correct answer. There is no way to do it in Rust, and you have to either write a macro, or use Option and explicitly pass None.
    – Jeroen
    Commented May 26, 2020 at 12:46

10 Answers 10

312

Since default arguments are not supported you can get a similar behavior using Option<T>

fn add(a: Option<i32>, b: Option<i32>) -> i32 {
    a.unwrap_or(1) + b.unwrap_or(2)
}

// Usage example:
let result = add(Some(3), Some(4));

This accomplishes the objective of having the default value and the function coded only once (instead of in every call), but is of course a whole lot more to type out. The function call will look like add(None, None), which you may or may not like depending on your perspective.

If you see typing nothing in the argument list as the coder potentially forgetting to make a choice then the big advantage here is in explicitness; the caller is explicitly saying they want to go with your default value, and will get a compile error if they put nothing. Think of it as typing add(DefaultValue, DefaultValue).

You could also use a macro:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

macro_rules! add {
    ($a: expr) => {
        add($a, 2)
    };
    () => {
        add(1, 2)
    };
}
assert_eq!(add!(), 3);
assert_eq!(add!(4), 6);

The big difference between the two solutions is that with "Option"-al arguments it is completely valid to write add(None, Some(4)), but with the macro pattern matching you cannot (this is similar to Python's default argument rules).

You could also use an "arguments" struct and the From/Into traits:

pub struct FooArgs {
    a: f64,
    b: i32,
}

impl Default for FooArgs {
    fn default() -> Self {
        FooArgs { a: 1.0, b: 1 }
    }
}

impl From<()> for FooArgs {
    fn from(_: ()) -> Self {
        Self::default()
    }
}

impl From<f64> for FooArgs {
    fn from(a: f64) -> Self {
        Self {
            a: a,
            ..Self::default()
        }
    }
}

impl From<i32> for FooArgs {
    fn from(b: i32) -> Self {
        Self {
            b: b,
            ..Self::default()
        }
    }
}

impl From<(f64, i32)> for FooArgs {
    fn from((a, b): (f64, i32)) -> Self {
        Self { a: a, b: b }
    }
}

pub fn foo<A>(arg_like: A) -> f64
where
    A: Into<FooArgs>,
{
    let args = arg_like.into();
    args.a * (args.b as f64)
}

fn main() {
    println!("{}", foo(()));
    println!("{}", foo(5.0));
    println!("{}", foo(-3));
    println!("{}", foo((2.0, 6)));
}

This choice is obviously a lot more code, but unlike the macro design it uses the type system which means the compiler errors will be more helpful to your library/API user. This also allows users to make their own From implementation if that is helpful to them.

3
  • 8
    this answer would be better as several answers, one for each approach. i want to upvote just one of them
    – joel
    Commented Dec 19, 2019 at 13:06
  • 17
    Your comment would have been more useful if you had mentioned which approach had your preference. ;-) I guess it was the macro Commented Jul 18, 2021 at 1:24
  • When I use a function which has argument of type Option without giving any value to that argument, the rust-analyser throw an err for not passing all value to it. I have to pass None to that function to work, can I avoid this? Commented Mar 6, 2024 at 9:40
128

No, it is not at present. I think it likely that it will eventually be implemented, but there’s no active work in this space at present.

The typical technique employed here is to use functions or methods with different names and signatures.

7
  • 6
    @ner0x652: but note that that approach is officially discouraged. Commented Mar 1, 2017 at 3:49
  • 2
    @JeroenBollen The best I can come up with in a couple of minutes’ searching is reddit.com/r/rust/comments/556c0g/…, where you have people like brson who was the Rust project leader at the time. IRC might have had more, not sure. Commented Nov 22, 2017 at 1:42
  • 5
    "I think it likely that it will eventually be implemented" - why? Doesn't it add additional runtime overhead? It seems against the whole "zero-cost abstraction" philosophy if rust were to add it. Commented Oct 31, 2021 at 12:18
  • 2
    @DylanKerler They could do something similar to monomorphization which would just add compile-time overhead Commented Nov 28, 2021 at 23:32
  • 1
    The alternative is generally taking Option<T> and filling in the default via .unwrap_or_else(|| …) or similar; or perhaps taking T and having the caller write T::default(). If anything, first-class default argument values could be more efficient as that filling of a default value could be reliably shifted to the caller where the compiler reckons it’s worthwhile, without needing to inline the whole thing, resulting in less work being done in cases where a value is provided—though normally the optimiser will make the two approaches much of a muchness. Commented Dec 15, 2021 at 1:52
97

No, Rust doesn't support default function arguments. You have to define different methods with different names. There is no function overloading either, because Rust use function names to derive types (function overloading requires the opposite).

In case of struct initialization you can use the struct update syntax like this:

use std::default::Default;

#[derive(Debug)]
pub struct Sample {
    a: u32,
    b: u32,
    c: u32,
}

impl Default for Sample {
    fn default() -> Self {
        Sample { a: 2, b: 4, c: 6}
    }
}

fn main() {
    let s = Sample { c: 23, ..Sample::default() };
    println!("{:?}", s);
}

[on request, I cross-posted this answer from a duplicated question]

1
  • Thanks for sharing. What about a trait object default value: Box<dyn TraitObject> ? Commented Mar 4, 2022 at 6:26
25

Rust doesn't support default function arguments, and I don't believe it will be implemented in the future. So I wrote a proc_macro duang to implement it in the macro form.

For example:

duang! ( fn add(a: i32 = 1, b: i32 = 2) -> i32 { a + b } );
fn main() {
    assert_eq!(add!(b=3, a=4), 7);
    assert_eq!(add!(6), 8);
    assert_eq!(add(4,5), 9);
}
15

If you are using Rust 1.12 or later, you can at least make function arguments easier to use with Option and into():

fn add<T: Into<Option<u32>>>(a: u32, b: T) -> u32 {
    if let Some(b) = b.into() {
        a + b
    } else {
        a
    }
}

fn main() {
    assert_eq!(add(3, 4), 7);
    assert_eq!(add(8, None), 8);
}
5
  • 8
    While technically accurate, the Rust community is vocally divided on whether or not this is a "good" idea. I personally fall into the "not good" camp.
    – Shepmaster
    Commented Aug 16, 2017 at 3:10
  • 3
    @Shepmaster it can possibly increase code size, and it is not super readable. Are those the objections to using that pattern? I've so far found the trade-offs to be worthwhile in service of ergonomic APIs, but would consider that I might be missing some other gotchas. Commented Aug 16, 2017 at 6:43
  • This code implies the presence of function overloading to the casual reader. The fact that its possible makes it permissible, indicating a possible language design hole?
    – George
    Commented Apr 1, 2022 at 13:16
  • 1
    Upvoted for being interesting and adding to the discussion. Not convinced that it's the best answer, but shouldn't necessarily be at the bottom of the answers either IMO... Commented Aug 3, 2022 at 14:33
  • 1
    Today it is better to use this with impl Into<Option<>>. Commented Jan 25, 2023 at 13:03
14

Another way could be to declare an enum with the optional params as variants, which can be parameterized to take the right type for each option. The function can be implemented to take a variable length slice of the enum variants. They can be in any order and length. The defaults are implemented within the function as initial assignments.

enum FooOptions<'a> {
    Height(f64),
    Weight(f64),
    Name(&'a str),
}
use FooOptions::*;

fn foo(args: &[FooOptions]) {
    let mut height   = 1.8;
    let mut weight   = 77.11;
    let mut name     = "unspecified".to_string();
    
    for opt in args {
        match opt {
            Height(h) => height = *h,
            Weight(w) => weight = *w,
            Name(n)   => name   =  n.to_string(),
        }
    }
    println!("  name: {}\nweight: {} kg\nheight: {} m", 
             name, weight, height);
}
    
fn main() { 

    foo( &[ Weight(90.0), Name("Bob") ] );

}

output:

  name: Bob
weight: 90 kg
height: 1.8 m

args itself could also be optional.

fn foo(args: Option<&[FooOptions]>) {
    let args = args.or(Some(&[])).unwrap();
    // ...
}
9
  • I liked this answer if you also want to make the arg optional you can also use optional and some like this: args: Option<&[FooOptions] Some(&[option] Commented Sep 23, 2021 at 18:46
  • 1
    @EduardoLuisSantos, great idea. I added an example along those lines. Thanks =)
    – Todd
    Commented Sep 23, 2021 at 19:12
  • Also I just tested this approach (mixed with the Optional) and compare the function against some equivalent python code and Python was on average 3 times faster, most probably due to this approach, I still like it more than write many functions but looks to be slower. Commented Sep 23, 2021 at 22:20
  • I wouldn't expect this approach to passing arguments to a function to be the most efficient. It's a little surprising that Python would be 3x faster. I could see PyPy3 being 3x faster, but interpreted Python vs. release build of Rust app? @EduardoLuisSantos
    – Todd
    Commented Sep 23, 2021 at 23:04
  • 1
    @JulianH, The looping over each variable does add some overhead, but not much. So yes.. you are trading some efficiency for "ergonomics". However, the claim above about Python being 3x faster is dubious. A good example where not compiling for release can create a misperception in comparable performance: Python vs. Rust.
    – Todd
    Commented Nov 3, 2021 at 19:16
13

Building on previous answers, keep in mind you can create new variables with the same name as existing ones ("variable shadowing"), which will hide the previous one. This is useful for keeping code clear if you don't plan to use the Option<...> anymore.

fn add(a: Option<i32>, b: Option<i32>) -> i32 {
    let a = a.unwrap_or(1);
    let b = a.unwrap_or(2);
    a + b
}
2
  • 1
    This is really neat!
    – Saurabh
    Commented Sep 7, 2023 at 1:59
  • 1
    I like this better than any other options!
    – rfsbsb
    Commented Apr 20, 2024 at 21:17
3

There's a default_args crate for that.

Example:

use default_args::default_args;

default_args! {
    fn add(a: int = 1, b: int = 2) {
        a + b
    }
}

Note that you now call your function with a macro call. (Ex: add!(12))

-1

Ask yourself, why do you want default arguments? There are many answers depending upon your reasons:

If you've far too many arguments, then ideally restructure your code into more different structs, like some builder pattern, maybe via functional record updates (FRU).

pub struct MyFunction { ... many arguments ... }
impl Default for MyFunction { ... }
impl MyFunction {  go(self) { ... }  }

Invoke like

MyFunction { desired arguments, ..Default::default() }.go()

Your builder could often be some related type, which makes method chaining nicer. In these, you could hide arguments at the type level, assuming users do not embed your intermediate type.

pub struct MyWorker<R: RngCore = OsRng> { ... }

If non-defaults wind up rare, then you could expose the via some trait being used anyways.

In schnorrkel for example, I needed a R: RngCore argument for test vectors, as well as niche users who wanted derandomized signatures. Yet, I wanted the wider ecosystem to use only well randomized signatures. I'd already adopted the merlin::Transcript abstraction for Fiat-Shamir transforms. I therefore provide only OsRng via the trait, but you can change the type behind the trait for test vectors or whatever. https://github.com/w3f/schnorrkel/blob/master/src/context.rs#L94-L103

1
  • The reason I look up this question is that I try to have an optional argument in a function, which I found the use of Option<T>, but the rust-analyser throw err when I use this function without assigning None to the respective argument(s). Commented Mar 6, 2024 at 9:48
-1

I would like to propose this approach

///
/// ## Argument
/// should_eat_veg: Option<bool> **default=false**
///
fn plan_my_meal(should_eat_veg: Option<bool>) -> Meal {
  let should_eat_veg = should_eat_veg.map_or(false, |v| v);
  ...
}
  • use Option<T> type declaration for argument with default value, pass None for default
  • "unwrap" and assign default value of argument in function body
1
  • 1
    The top answer already mentions that. Commented Nov 25, 2023 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.