DEV Community

Kirill Vasiltsov
Kirill Vasiltsov

Posted on • Originally published at kirillvasiltsov.com

Optional arguments in Rust

If you come to Rust from languages like Javascript which have support for optional arguments in functions, you might find it inconvenient that there is no way to omit arguments in Rust. However, there are good ways to simulate optionality and I'm even starting to think that they make code more readable than simply omitting things here and there.

Approach #1: two functions

This is the least creative approach, but absolutely legitimate. The idea is to create two functions with different number of arguments.

fn add_two(a: usize, b: usize) -> usize {
    a + b
}

fn add_three(a: usize, b: usize, c: usize) -> usize {
    a + b + c
}
Enter fullscreen mode Exit fullscreen mode

The third argument here is "optional" in the sense that you have the option to not use the function and use add_two instead.

Approach #2: Option

Option is meant to represent something that might exist or might not exist. We can definitely use it to represent optionality in function arguments!

fn greet_name_optional(name: Option<String>) {
    println!("Hello, {}!", name.unwrap_or(String::from("world")))
}

greet_name_optional(None);
greet_name_optional(Some(String::from("Ferris")));

// Hello, world!
// Hello, Ferris!
Enter fullscreen mode Exit fullscreen mode

unwrap_or is handy when you want to provide a "default" value if None is passed. The cons of this approach is that you still have to pass None to greet_name_optional when there is nothing else to pass and wrap existing values in Some.

Approach #3: Enum

Just like Option is simply an enum, we can create our own enum that better represents the thing that the function expects.

enum Name {
    Empty,
    Is(String),
}
Enter fullscreen mode Exit fullscreen mode

This also allows us to provide the default value by implementing the Default trait:

impl Default for Name {
    fn default() -> Self {
        Name::Is(String::from("world"))
    }
}
Enter fullscreen mode Exit fullscreen mode

The function then becomes very intuitive and easy to read:

fn greet_name(name: Name) {
    match name {
        Name::Empty => println!("Hello, {}!", Name::default()),
        Name::Is(value) => println!("Hello, {}!", value),
    }
}

greet_name(Name::Empty);
greet_name(Name::Is(String::from("Ferris")));

// Hello, world!
// Hello, Ferris!
Enter fullscreen mode Exit fullscreen mode

I personally prefer the third approach for its readability, but it requires some work, because you might need to implement other traits like Display too.

Top comments (2)

Collapse
 
wulymammoth profile image
David

I'm still learning Rust, but this is interesting to read about, because the multiple function heads version is typically what's done in some functional languages to simulate variadic functions. Thank you for sharing :)

Collapse
 
jfliu profile image
Junfeng Liu

There is a better approach: xion.io/post/code/rust-optional-ar...