290

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). 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. Commented May 26, 2020 at 12:46

10 Answers 10

325

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.

Sign up to request clarification or add additional context in comments.

3 Comments

this answer would be better as several answers, one for each approach. i want to upvote just one of them
Your comment would have been more useful if you had mentioned which approach had your preference. ;-) I guess it was the macro
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?
131

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 Comments

@ner0x652: but note that that approach is officially discouraged.
@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.
"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.
@DylanKerler They could do something similar to monomorphization which would just add compile-time overhead
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.
|
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 Comment

Thanks for sharing. What about a trait object default value: Box<dyn TraitObject> ?
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);
}

Comments

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 Comments

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 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.
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?
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...
Today it is better to use this with impl Into<Option<>>.
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 Comments

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]
@EduardoLuisSantos, great idea. I added an example along those lines. Thanks =)
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.
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
@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.
|
14

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 Comments

This is really neat!
I like this better than any other options!
4

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))

Comments

-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 Comment

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).
-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 Comment

The top answer already mentions that.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.