Default function arguments in Rust

Jeroen picture Jeroen · Jun 4, 2014 · Viewed 35.4k times · Source

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

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

Answer

ampron picture ampron · Feb 12, 2016

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.