240

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

fn add(a: int = 1, b: int = 2) { a + b }
vallentin
  • 23,478
  • 6
  • 59
  • 81
Jeroen
  • 15,257
  • 12
  • 59
  • 102
  • 4
    [#6973](https://github.com/mozilla/rust/issues/6973) contains several work-arounds (using a struct). – huon Jun 05 '14 at 09:04
  • In 2020, how do you can code it? – puentesdiaz May 26 '20 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 May 26 '20 at 12:46

9 Answers9

268

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

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ampron
  • 3,146
  • 2
  • 17
  • 13
  • 7
    this answer would be better as several answers, one for each approach. i want to upvote just one of them – joel Dec 19 '19 at 13:06
  • 8
    Your comment would have been more useful if you had mentioned which approach had your preference. ;-) I guess it was the macro – Romain Vincent Jul 18 '21 at 01:24
116

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.

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • 5
    @ner0x652: but note that that approach is officially discouraged. – Chris Morgan Mar 01 '17 at 03:49
  • @ChrisMorgan Do you have a source for that being officially discouraged? – Jeroen Nov 19 '17 at 21:00
  • 2
    @JeroenBollen The best I can come up with in a couple of minutes’ searching is https://www.reddit.com/r/rust/comments/556c0g/optional_arguments_in_rust_112/, where you have people like brson who was the Rust project leader at the time. IRC might have had more, not sure. – Chris Morgan Nov 22 '17 at 01:42
  • 4
    "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. – Dylan Kerler Oct 31 '21 at 12:18
  • @DylanKerler They could do something similar to monomorphization which would just add compile-time overhead – Jack Clayton Nov 28 '21 at 23:32
  • 1
    The alternative is generally taking `Option` 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. – Chris Morgan Dec 15 '21 at 01:52
93

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]

vallentin
  • 23,478
  • 6
  • 59
  • 81
eulerdisk
  • 4,299
  • 1
  • 22
  • 21
23

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);
}
zhengmian hu
  • 241
  • 3
  • 2
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);
}
squidpickles
  • 1,737
  • 19
  • 27
  • 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 Aug 16 '17 at 03: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. – squidpickles Aug 16 '17 at 06: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 Apr 01 '22 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... – Bryan Larsen Aug 03 '22 at 14:33
  • 1
    Today it is better to use this with `impl Into – Chayim Friedman Jan 25 '23 at 13:03
11

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();
    // ...
}
Todd
  • 4,669
  • 1
  • 22
  • 30
  • 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] – Eduardo Luis Santos Sep 23 '21 at 18:46
  • 1
    @EduardoLuisSantos, great idea. I added an example along those lines. Thanks =) – Todd Sep 23 '21 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. – Eduardo Luis Santos Sep 23 '21 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 Sep 23 '21 at 23:04
  • Trade comfort for performance? – JulianW Nov 03 '21 at 15:57
  • 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](https://stackoverflow.com/a/69324578/7915759). – Todd Nov 03 '21 at 19:16
  • I am just curious about finding such solution to a general problem in a compiler language. Imo. since the question was asked that general, noone should find a solution with performance drawbacks, at least not in this specifc case. – JulianW Nov 05 '21 at 14:00
  • For things that aren't performance critical, sometimes ergonomics outweighs other factors. For instance an easy to use API for something that's only called once or a few times to configure something. The builder pattern is another pattern addressing this problem, but that also has some performance overhead, but it's a popular pattern nonetheless @JulianH – Todd Nov 05 '21 at 20:39
7

Building on previous answers, keep in mind you can create new variables with the same name as existing ones, 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
}
nathanfranke
  • 775
  • 1
  • 9
  • 19
1

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

Elijah Mock
  • 587
  • 8
  • 21
0

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

Jeff Burdges
  • 4,204
  • 23
  • 46