Rust doesn’t support default function arguments. Or does it?

A default argument example

def my_func(req1, req2, opt1=123, opt2="abc", opt3=false):
print("req1 is {}".format(req1))
print("req2 is {}".format(req2))
print("opt1 is {}".format(opt1))
print("opt2 is {}".format(opt2))
print("opt3 is {}".format(opt3))
my_func("req1value", "req2value")
my_func("req1value", "req2value", 456)
my_func("req1value", "req2value", opt1=456, opt2="def", opt3=true)
my_func("req1value", "req2value", 456, "def", true)
my_func("req1value", "req2value", opt2="ghi")
my_func("req1value", "req2value", opt3=true, opt1=789)

The Default trait

struct MyStruct {
a: i32,
b: String,
c: bool,
}
impl Default for MyStruct {
fn default() -> Self {
MyStruct {
a: 123,
b: String::from("abc"),
c: false,
}
}
}
let my_instance = MyStruct {
a: 456,
..Default::default()
};
let my_instance2 = MyStruct {
a: 789,
c: true,
..Default::default()
};

Using the Default trait for default arguments

pub struct MyFuncOptionalArgs<'a> {
pub opt1: i32,
pub opt2: &'a str,
pub opt3: bool,
}
impl<'a> Default for MyFuncOptionalArgs<'a> {
fn default() -> Self {
MyFuncOptionalArgs {
opt1: 123,
opt2: "abc",
opt3: false,
}
}
}
pub fn my_func(
req1: &str,
req2: &str,
optional_args: MyFuncOptionalArgs
) {
println!("req1 is {}", req1);
println!("req2 is {}", req2);
println!("opt1 is {}", optional_args.opt1);
println!("opt2 is {}", optional_args.opt2);
println!("opt3 is {}", optional_args.opt3);
}
my_func("req1value", "req2value", MyFuncOptionalArgs::default());my_func(
"req1value",
"req2value",
MyFuncOptionalArgs {
opt1: 456,
opt2: "def",
opt3: true,
},
);
my_func(
"req1value",
"req2value",
MyFuncOptionalArgs {
opt2: "ghi",
..Default::default()
},
);
my_func(
"req1value",
"req2value",
MyFuncOptionalArgs {
opt3: true,
opt1: 789,
..Default::default()
},
);

Bonus: Making every argument a named argument

pub struct MyFuncArgs<'a> {
pub req1: &'a str,
pub req2: &'a str,
pub optional_args: MyFuncOptionalArgs<'a>,
}
pub struct MyFuncOptionalArgs<'a> {
pub opt1: i32,
pub opt2: &'a str,
pub opt3: bool,
}
impl<'a> Default for MyFuncOptionalArgs<'a> {
fn default() -> Self {
MyFuncOptionalArgs {
opt1: 123,
opt2: "abc",
opt3: false,
}
}
}
pub fn my_func(args: MyFuncArgs) {
println!("req1 is {}", args.req1);
println!("req2 is {}", args.req2);
println!("opt1 is {}", args.optional_args.opt1);
println!("opt2 is {}", args.optional_args.opt2);
println!("opt3 is {}", args.optional_args.opt3);
}
my_func(MyFuncArgs {
req1: "req1value",
req2: "req2value",
optional_args: MyFuncOptionalArgs::default(),
});
my_func(MyFuncArgs {
req2: "req2value",
optional_args: MyFuncOptionalArgs {
opt1: 456,
opt2: "def",
opt3: true,
},
req1: "req1value",
});
my_func(MyFuncArgs {
req1: "req1value",
req2: "req2value",
optional_args: MyFuncOptionalArgs {
opt2: "ghi",
..Default::default()
},
});
my_func(MyFuncArgs {
req1: "req1value",
req2: "req2value",
optional_args: MyFuncOptionalArgs {
opt3: true,
opt1: 789,
..Default::default()
},
});

An example in which this pattern was useful

  • Drawing sprites is an ubiquitous thing in my game that gets called from many different places.
  • There are arguments that I consider required, such as the source image of the sprite, the position it will be drawn in the screen, the size it will be drawn and the depth relative to other draw calls.
  • There are also arguments that I consider optional which are specified in just a few cases and that have sensible default values for all the other calls that don’t care about them. For example, the color that should multiply the sprite (in most cases the sprite’s original colors should be used), the rotation to rotate the sprite (in most cases there’s no rotation), the opacity(alpha) of the draw, whether just a partial region of the source image should be used, and so on.
  • Since many of these arguments have the same types (position and size for example both have type F2, which in my game is used for 2-dimensional floating point vectors), having named arguments in the call sites is super helpful to clarify which is which and avoid confusion.

Is this a zero cost abstraction?

(default arguments) mean runtime: 59.268ms, std_dev: 3.343ms
(explicit arguments) mean runtime: 59.351ms, std_dev: 3.340ms
(default arguments) mean runtime: 883.652ms, std_dev: 31.416ms
(explicit arguments) mean runtime: 167.352ms, std_dev: 7.446ms
pub struct MyFuncOptionalArgs<'a> {
pub opt1: i32,
pub opt2: &'a str,
pub opt3: bool,
pub expensive_opt: Option<Vec<i32>>,
}
impl<'a> Default for MyFuncOptionalArgs<'a> {
fn default() -> Self {
MyFuncOptionalArgs {
opt1: 123,
opt2: "abc",
opt3: false,
expensive_opt: Some(expensive_vec_instantiation()),
}
}
}
...// This instantiation still calls expensive_vec_instantiation() even though it's
// not necessary.
let args = MyFuncOptionalArgs {
expensive_opt: None,
..Default::default()
};

Could it be zero cost?

Final thoughts

--

--

--

I'm a Computer Engineer interested in all sorts of stuff. Check out my personal website at lucamoller.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Summer 2018: HackNY and Chartbeat

Memory Management : free()

Best IT Certifications to Boost Your Career in 2018

DeepNews #06 — Hardware & Software

Zomato App — Agile Methodology

How to copy objects in Java

Using Contract Testing and Cypress To Replace WebDriver at CM

Measuring success of your platform teams — part II

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Luca Mattos Moller

Luca Mattos Moller

I'm a Computer Engineer interested in all sorts of stuff. Check out my personal website at lucamoller.com

More from Medium

Base64 Encoding Implementation in Rust

My Battlesnake Server — The Basics

Rust Foo: NTP Client (Part 1)

A Beginner’s Guide to Setting up Rust on Windows.