summaryrefslogtreecommitdiff
path: root/src/blog/named-optional-args.kuht
blob: 874ea57050d49a3159daf9fddfdf3f0e95ae8a2d (plain)
<import "base.kuht" as "base" />

<head>
	<title>Named and Optional Arguments are Awesome</title>
	<meta name="description" content="Whenever someone asks what my least favorite part of Rust is, my answer is always the same: it doesn't have named or optional arguments. This article explores the ideas for adding them, both good and bad." />
</head>

<body>

<article>

<h1>Named and Optional Arguments are Awesome</h1>

<p>
	Whenever someone asks what my least favorite part of Rust is, my answer is
	always the same: it doesn't have named or optional arguments.
</p>

<p>
	I have a small list of programming languages I tolerate: Rust, C#, TypeScript,
	Dart, Python. Of those languages, Rust is the hardest to get named arguments
	out of. I'm going to start this post by going over the other languages and how
	they've achieved named arguments. Then, we'll look at Rust, the problems that
	have resulted from not having named arguments, and proposals for how Rust
	could get them in the future.
</p>

<h2>How Other Languages Handle Named Parameters</h2>

<h3>Dart</h3>

<p>
	Dart is designed specifically for graphical user interfaces. GUI components
	tend to have lots of optional parameters, so Dart tried very hard to get them
	right. And I think it does the best job out of any language we're going to
	talk about today.
</p>

<p>
	First, let's look at a normal function, just to familiarize ourselves with the
	language:
</p>

<pre>
Widget Text(String text) {
	// ...
}
</pre>

<p>
	That's pretty normal as far as languages go. You can probably guess what it
	does, even if you don't know Dart. Dart was basically designed in a lab to be
	easy for programmers to learn.
</p>

<p>
	Now, let's add a named parameter to set the size of the text, and default it to
	16 pixels:
</p>

<pre>
Widget Text(String text, {int fontSize = 16}) { /* ... */ }

// usage:
Text("Hello, world!", fontSize: 24)
</pre>

<p>
	Named parameters are wrapped in curly braces. This makes them look similar to
	a map. Named parameters can also be defaulted to null, or even required.
</p>

<pre>
Widget Text({
	int fontSize = 16,
	Color? color,
	required String text,
});
</pre>

<p>
	Dart also supports optional positional arguments by wrapping the parameter in
	square brackets.
</p>

<pre>
// From the Dart documentation
String say(String from, String msg, [String device = 'carrier pigeon']) {
  var result = '$from says $msg with a $device';
  return result;
}

assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');
</pre>

<h3>C#</h3>

<p>
	C#'s handling of named parameters is also pretty good. In fact, any parameter can be
	named.
</p>

<pre>
void ExampleMethod(string foo, string bar)
{
	Console.WriteLine($"{foo} {bar}")
}

ExampleMethod(bar: "Hello", foo: "world");
</pre>

<p>
	And any parameter can have a default value, as long as they're specified after
	the required parameters.
</p>

<pre>
void ExampleMethod(string foo, string bar = "world")
{
	Console.WriteLine($"{foo} {bar}")
}

ExampleMethod("Howdy");
</pre>

And that's all there is to it. I like the simplicity of it.

<h3>TypeScript</h3>

<p>
	TypeScript's handling of named parameters is not very good, in my opinion.
	But optional positional parameters aren't named, and they work pretty well, so
	let's start with that.
</p>

<pre>
function example(required: string, optional?: string = "world"): string {
	return `${required} ${optional}`;
}

example("Hello") === "Hello world";
example("Hello", "TypeScript") === "Hello TypeScript";
</pre>

<p>
	That seems fine to me. If you don't specify the default value, then it gets
	set to <code>undefined</code>. The problem is that there isn't a good built-in
	syntax for named parameters, so we have to use other TypeScript features to
	hack it in.
</p>

<pre>
function example(
	required: string,
	{
		requiredNamed,
		namedOptional = "world"
	}: { requiredNamed: string, namedOptional?: string }
): string {
	return `${required} ${requiredNamed} ${namedOptional}`;
}

example("Hello", { requiredNamed: "beautiful" });
</pre>

<p>
	Here, we're taking advantage of both anonymous types and destructuring. The
	second parameter of the function has an anonymous object type, which contains
	two fields: <code>requiredNamed</code> and <code>namedOptional</code>. Then
	we destructure this parameter so we can use the fields in the function.
</p>

<p>
	The most annoying part of this is that we have to define the field names
	twice. Once for the type, and once for the function parameters. This is
	unnecessary verbosity that any good language should try to avoid. The function
	call is also more verbose than necessary, because of the curly braces.
</p>

<p>
	Unfortunately, this syntax is used all of the time in React. Components are
	typically a function that takes one argument, which is an object. So when you
	define a component, you have to do this constantly.
</p>

<h3>Rust</h3>

<p>
	Some people have tried to do named parameters in Rust. This generally doesn't
	work very well. The most common approach is to define a struct for the
	function, and then have it implement the <code>Default</code> trait, so that
	we can end the struct initializer with <code>..Default::default()</code>.
</p>

<pre>
struct ExampleParams {
	foo: String,
	bar: String,
}

impl Default for ExampleParams {
	fn default() -> Self {
		Self {
			foo: "Hello".into(),
			bar: "Hello".into(),
		}
	}
}

fn example(Example { foo, bar }) -> String {
	format!("{foo} {bar}")
}

assert_eq!(
	example(Example { foo: "Hello".into(), ..Default::default() }),
	"Hello world".to_string()
);
</pre>

<p>
	I complained before about how verbose TypeScript's version is, but this is
	even worse, because the types are not anonymous. And worse, if your default
	values are not the default of the types (e.g. empty string for <code>String</code>),
	then you need to write a whole function to set the default values. It also
	only works if every single parameter has a default value. If only some of the
	parameters have default values, then you need an even more verbose solution.
</p>

<h2>Why Rust Needs Optional Parameters</h2>

<p>
	The standard library doesn't tend to follow the above pattern (for good reason,
	in my opinion). But there are still several cases where the standard library
	probably would have benefitted from it.
</p>

<p>Take some of the factories of <code>HashMap</code>, for example.</p>

<pre>
impl&lt;K, V> HashMap&lt;K, V, RandomState> {
	pub fn new() -> HashMap&lt;K, V, RandomState>;
	pub fn with_capacity(capacity: usize) -> HashMap&lt;K, V, RandomState>;
}

impl&lt;K, V, A: Allocator> HashMap&lt;K, V, RandomState, A> {
	pub fn new_in(alloc: A) -> Self;
	pub fn with_capacity_in(capacity: usize, alloc: A) -> Self;
}

impl&lt;K, V, S> HashMap&lt;K, V, S> {
	pub const fn with_hasher(hash_builder: S) -> HashMap&lt;K, V, S>;
	pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashMap&lt;K, V, S>;
}

impl&lt;K, V, S, A: Allocator> HashMap&lt;K, V, S, A> {
	pub fn with_hasher_in(hash_builder: S, alloc: A) -> Self;
	pub fn with_capacity_and_hasher_in(capacity: usize, hasher: S, alloc: A) -> Self;
}
</pre>

<p>
	With just three optional parameters, we need eight functions to represent all
	of the overloads. Each one of them needs to have their own name, since Rust
	doesn't actually have overloads. And the programmer and/or code reviewer must
	either memorize or look up the order of the parameters when one of the more
	complex factories are used.
</p>

<p>
	Another problem that comes up is the fact that, when the parameters are
	unnamed, it can be easier to make mistakes. Consider the following print
	function.
</p>

<pre>
fn print(text: &str, bold: bool, italics: bool, underline: bool);
</pre>

<p>
	It's easy to mix up the parameters here because they don't have names. If I
	forget that the <code>italics</code> parameter is third and not second, then I
	can end up accidentally bolding my text instead.
</p>

<p>
	Some functions just have lots of parameters as well. Imagine if I added color,
	underline color, background color, blinking text, hidden text, circled text,
	and fast blinking text above. Nobody wants to pass in 14 arguments to a
	function, of which several will have sensible defaults. And I don't see much
	reason to name a new struct if it's only going to be used for the one function.
</p>

<h2>Proposals for Named Arguments</h2>

<p>
	First I'm going to describe some proposals that I've seen for improving named
	arguments in Rust, that I don't like. Then I'll reveal what my preferred
	solution is.
</p>

<h3>Default Field Values</h3>

<p>
	This feature is already available in Nightly Rust. And the <code>syn</code>
	create even recently added support for it. The idea is to make implementing
	the <code>Default</code> trait easier by adding special syntax for it. It's
	not proposed that this will solve named arguments by itself, but might be part
	of a larger solution.
</p>

<pre>
#[derive(Default)]
struct Pet {
	name: Option&lt;String>, // impl Default for Pet will use Default::default() for name
	age: i128 = 42, // impl Default for Pet will use the literal 42 for age
}

// Pet { name: Some(""), age: 42 }
let _ = Pet { name: Some(String::new()), .. };
// Compilation error: `name` needs to be specified
let _ = Pet { .. };
// Pet { name: None, age: 42 }
let _ = Pet::default();
</pre>

<p>
	I don't actually have a problem with the proposal itself. I even created my
	own library to implement a polyfill for it
	(<a href="https://www.lib.rs/feluments">feluments</a>). It seems perfect for
	structs. But it doesn't help very much for functions.
</p>
	
<h3>Structural Records</h3>

<p>
	The RFC for default field values mentions a closed RFC for structural records.
	They can be thought of as anonymous structs or tuples with named fields.
</p>

<pre>
fn do_stuff_with(color: { red: u8, green: u8, blue: u8 }) { // More ergonomic!
	some_stuff(color.red); // *And* readable! :)
	...
	other_stuff(color.green);
	...
	yet_more_stuff(color.blue);
}

do_stuff_with({ red: 255, green: 127, blue: 63 });
</pre>

<p>
	The RFC notes that this emulates named arguments, although it does not emulate
	optional arguments. It wasn't a major motivation. The major motivation was to
	get the convenience of tuples with the readability of structs.
</p>

<p>
	Let's say for the sake of argument that this RFC succeeded. What would the
	syntax of our "Hello, world" examples look like here? Let's assume default
	field values are also merged, and they work with structural records.
</p>

<pre>
fn example(
	required: String,
	{requiredNamed, namedOptional}: {
		requiredNamed: Option&lt;String>,
		namedOptional: String = "world".into()
	}
): String {
	format!("{required} {requiredNamed} {namedOptional}")
}

example("Hello", { requiredNamed: "beautiful", .. });
</pre>

<p>
	Wow, that looks familiar. In fact it's the same syntax that TypeScript uses,
	with all the problems that come with it. You need to specify the parameter
	names twice.
</p>

<p>Structural records also come with many other challenges:</p>
<ul>
	<li>
		What happens with records that have only one field? <code>{ x }</code> can be
		interpreted as either a block expression that returns <code>x</code>, or a
		structural record expression: <code>{ x: x, }</code>. To fix the ambiguity,
		the RFC proposed requiring an extra comma: <code>{ x, }</code>, like what
		tuples do. But this requires making the Rust parser much more complicated,
		to do several tokens of lookahead.
	</li>
	<li>
		It's impossible to implement any traits on these. The RFC proposed magically
		implementing several traits. And unlike for tuples, we can't just define some
		generic trait implementations, because there can be so much variation in the
		field names.
	</li>
	<li>
		A newcomer to the language may mistake this syntax for a Python dictionary
		or Dart's map type.
	</li>
</ul>

<p>
	Interestingly, TypeScript actually has named tuple fields. But they only exist
	at the type declaration. You can't use the field names to access the fields.
</p>

<pre>
type NewLocation = [lat: number, long: number]

const newLocations: NewLocation[] = [
    [52.3702, 4.8952],
    [53.3498, -6.2603]
]

const firstLat = newLocations[0][0]
const firstLong = newLocations[0][1]
</pre>

<p>
	Rust's language team decided that although something resembling this feature
	seems useful, it would be very hard to implement and not worth the effort.
</p>

<h3>Copy C#</h3>

<p>
	If we're already agreeing on a syntax for optional fields in structs, why not
	just copy that over to functions? First, we can allow the arguments to be
	specified in any order, as long as they are named, and no out-of-order
	positional arguments are following a named argument.
</p>

<pre>
print_order_details(order_num: 31, product_name: "Red Mug", seller_name: "Gift Shop");
print_order_details(seller_name: "Gift Shop", product_name: "Red Mug", order_num: 31);
print_order_details("Gift Shop", 31, product_name: "Red Mug");
print_order_details(seller_name: "Gift Shop", 31, product_name: "Red Mug");

// this would cause an error
print_order_details(product_name: "Red Mug", 31, "Gift Shop");
</pre>

<p>
	Then, we could define default values, and allow parameters with default values
	to be unspecified.
</p>

<pre>
fn print_order_details(product_name: &str, order_num: 31, seller_name: &str = "Gift Shop");

print_order_details("Red Mug", 31);
</pre>

<p>
	Unlike for the default field values proposal, we can't use a <code>..</code>
	at the end here, because it is also a valid expression.
</p>

<p>
	The biggest problem with this idea is that it makes parameter names part of
	the public API of the function, without functions opting into it. There was
	previously an objection on the grounds of it conflicting with the proposed
	type ascription feature. But the RFC for it has since been deleted, so I don't
	think we need to worry about that anymore.
</p>

<h3>Public Arguments</h3>

<p>
	As far as I know, a formal RFC was never made for this, but a pre-RFC was
	posted to the Rust Internals Forum several years ago, and it's the most
	thorough proposal for named arguments that I know of. I'll avoid copy-pasting
	the entire RFC here, but I'll give a high level overview.
</p>

<p>Here's an example of a function with named arguments from the RFC:</p>

<pre>
pub struct Database;
pub struct RegistrationError;

pub fn register(
	pub name: String,
	pub surname: String,
	to db: Database
) -> Result&lt;(), RegistrationError> {
	/* ... */
}

register(name: "Alexis".into(), surname: "Poliorcetics".into(), to: my_db);
</pre>

<p>
	In the case of <code>to db</code>, the name of the argument is <code>to</code>,
	but it's referred to as <code>db</code> within the body of the function. For
	the other two parameters, the <code>pub</code> keyword is used to say that
	that both the name of the argument when calling the function, and the name of
	the parameter in the function body, are the same.
</p>

<p>
	The RFC goes into way more detail, proposing function overloads, using named
	arguments with the <code>Fn</code> trait, exposing names from patterns, method
	overloads, and explaining the documentation aspect.
</p>

<p>Here are the criticisms I saw in the comments:</p>
<ul>
	<li>
		Unordered named arguments was ruled out. I don't like the justification
		RustyFrog gave for this, but it seems easy to support with this model.
	</li>
	<li>
		Default parameter values also weren't proposed, even though it's the main
		reason for wanting named parameters
	</li>
	<li>
		Despite not proposing default parameter values, the pre-RFC did propose
		overloads, which are very difficult to implement in Rust, and they're not as
		useful as optional arguments. It's not proposing type-based overloads, which
		is a bit better, but still.
	</li>
	<li>It's a very big RFC, which might make it difficult to get it approved.</li>
	<li>
		There are some syntax ambiguities, like <code>fn foo(name (a, b): name)</code>,
		which is currently valid syntax. There was also a proposed type acription
		feature that it conflicted with, but as I said before, that RFC has since
		been removed.
	</li>
	<li>
		Using <code>pub</code> might be confusing, given how it's used currently. I
		don't think I mind that personally, because its effect is to make the name of
		the parameter part of a function's public API. But I get how that's a bit
		weird.
	</li>
	<li>
		The proposal for changing the <code>Fn</code> trait seemed underbaked. The
		<code>Fn(f32, f32) -> String</code> trait gets desugared to
		<code>Fn&lt;(f32, f32), Output = String></code>, but it's not clear what the
		desugaring for named parameters should look like.
	</li>
	<li>
		Despite explaining the usage for <code>Fn</code> implementations, the <code>fn</code>
		type was not mentioned at all.
	</li>
	<li>
		The pre-RFC was not clear about if named parameters were required to be used
		by name, or if having the correct position was technically sufficient.
	</li>
	<li>
		The handling of extern functions with named arguments is fairly complicated
		to the point where it would probably be better to not handle them.
	</li>
</ul>

<p>
	My understanding is that the author got busy and didn't address these concerns,
	but I think the pre-RFC serves as a good starting point.
</p>

<h3>Struct-style function calls</h3>

<p>
	This idea was proposed back in 2016 by nixpulvis. I've also seen it floating
	around in some other places.
</p>

<pre>
// 1. Tuple style method call (current functions).
fn foo(a: u32) {}
foo(2);

// 2. Struct style method call.
fn foo { a: u32 }
foo { a: 2 };
// SomeType { a: 1 } -> SomeType
// SomeFunc { a: 1 } -> Codomain
// generally_lowercase { a: 1 } -> Codomain

// 3. Together.
fn foo(a: u32) { a: u32 } {}
foo(1) { a: 2 };
</pre>

<p>
	I personally don't like it because it makes struct constructions and function
	calls look too similar. I also have no idea how the compiler would parse this.
	But then again, most languages use <code>new Foo()</code> syntax for
	constructors, so maybe somebody will like it.
</p>

<h3>Copy Dart</h3>

<p>
	Another proposal that's come up, including from myself, is to copy the Dart
	syntax, like so:
</p>

<pre>
fn foo({ foo: &str = "Hello", bar: &str = "World" }) -> String {
	format!("{foo} {bar}")
}
</pre>

<p>
	To me, this looks too much like existing pattern syntax. But I do like that it
	doesn't require an extra keyword on each argument.
</p>

<h3>Edition Separation</h3>

<p>
	Some people have proposed solving the backwards-compatibility problem by only
	allowing named arguments to be used with functions that were written in a
	future edition of Rust. To me, this feels like too large of a breaking change
	to be worth considering.
</p>

<h3>Dot Prefixes</h3>

<p>
	An RFC was opened in 2020 proposing using a dot prefix to distinguish between
	named and unnamed variables. It was not merged, because the RFC was a very
	incremental step that didn't explain the future plans for how the named
	arguments would work. Here's an example from the PR:
</p>

<pre>
fn split(string: &str, .at: char, .limit: usize, .case_sensitive: bool) {}

split("hello world", .at = ' ', .limit = 2, .case_sensitive = true);
</pre>

<p>
	The dot was chosen because it's only one character, (as opposed to four
	characters, with the <code>pub</code> keyword plus a space). But I honestly
	don't think it's intuitive enough. The dot is also reminds the user of struct
	fields, which is a completely unrelated concept.
</p>

<h2>My Proposal</h2>

<p>
	After reading all of these proposals, I feel that all of the pieces exist for
	a complete named arguments proposal that lack any of the downsides. I get the
	impression that some people are going to be opposed to the concept no matter
	what, but I tried to address as many counter-arguments for named arguments as
	possible in this post. The rest of this post will be a pre-RFC that think
	will satisfy as many people as possible. I'll monitor the comments anywhere
	this gets posted to see if there are any major criticisms. I can't guarantee
	that I'll actually make an RFC, but permission is granted to create an RFC if
	I don't. I think it's been a while since named arguments were last seriously
	proposed, so I think now is a good time for a new proposal.
</p>

<h3>Summary</h3>

<p>
	Add named and optional arguments to functions. Functions can have both
	positional and named arguments. In function calls, named arguments may be
	referred to by name, to increase readability and maintainability. Named
	arguments may also have default values, avoiding the need to specify them in
	the function call.
</p>

<h3>Motivation</h3>

<h4>Improve safety and readability</h4>

<p>
	Misremembering which parameter is at which position is a major source of bugs
	in many languages, including Rust. Take the function from the standard
	library: <code><a href="https://doc.rust-lang.org/stable/std/fs/fn.hard_link.html">hard_link</a></code>.
</p>

<pre>
pub fn hard_link&lt;P: AsRef&lt;Path>, Q: AsRef&lt;Path>>(
	original: P,
	link: Q,
) -> Result&lt;()>
</pre>

<p>
	Both parameters accept the same types, so there's no way to know which one is
	which without looking at the documentation. The consequence of misremembering
	is a runtime error or a bug.
</p>

<p>
	With named parameters, it is easy to see what is happening at the call-site.
	This also makes the resulting code more readable.
</p>

<pre>
hard_link(original: "a.txt", link: "b.txt")?;
</pre>

<p>The effect is more pronounced for functions which have more parameters.</p>

<pre>
// Without named arguments: what are these numbers?
solar_elevation(1787517098.0, 38.897957, -77.036560)

// With named arguments, it's obvious
solar_elevation(timestamp: 1787517098.0, latitude: 38.897957, longitude: -77.036560);
</pre>

<p>
	A similar effect is possible in Rust today, by creating a new struct and
	having that be the function's only parameter. But it's very verbose to create,
	so in practice, few libraries take advantage of it. Importantly, the standard
	library rarely uses this pattern.
</p>

<h4>Boilerplate reduction</h4>

<p>
	Many functions have reasonable defaults that should be passed into many of
	their parameters, but because all positional arguments are required, all of
	the parameters must be specified. Passing <code>None</code> into every
	parameter comes with readability issues.
</p>

<pre>
repository.checkout_index(None, None)?;
</pre>

<p>
	Passing a struct into the parameters does give us names, but still requires
	us to specify <code>None</code> for the optional values. Sometimes this
	boilerplate can be reduced by adding <code>..Default::default()</code> to the
	end of the constructor, <code>Default</code> cannot be implemented if any of
	the fields are not optional. Implementing <code>Default</code> is also a lot
	of boilerplate if it cannot be implemented simply by using the derive macro.
	Some of these problems can be addressed by
	<a href="https://github.com/rust-lang/rfcs/pull/3681">RFC 3681</a>, but it
	still requires a new struct to be defined specifically for one function.
</p>

<pre>
let instance = Instance::new(InstanceDescriptor {
	backends: Backends::default(),
	flags: InstanceFlags::default(),
	memory_budget_thresholds: MemoryBudgetThresholds::default(),
	backend_options: BackendOptions::default(),
	display: None,
});
</pre>

<p>
	To help reduce the boilerplate of calling such a function, many libraries
	implement builder types that don't require every value to be specified. This
	comes at the cost of even more boilerplate to create the builder, which
	becomes part of the library's public API. There are crates, such as
	<a href="https://www.crates.io/crates/bon">bon</a> to make declaring a builder
	for a struct easier, but it still requires a struct be declared for a single
	function. Libraries don't always use these crates either, as doing so
	increases compile times. The <code>Command</code> struct uses 60 SLOCs (and
	many more lines of documentation) to allow us to do this:
</p>

<pre>
Command::new("printenv")
	.stdin(Stdio::null())
	.stdout(Stdio::inherit())
	.env_clear()
	.envs(&filtered_env)
	.spawn()
	.expect("printenv failed to start");
</pre>

<p>If we had named arguments, the following function would be easy to define:</p>

<pre>
spawn(
	command: "printenv",
	stdin: Stdio::null(),
	stdout: Stdio::inherit(),
	env_clear: true,
	envs: &filtered_env
).expect("printenv failed to start");
</pre

<p>
	Bon also provides utilities for emulating named arguments in functions, but
	calling such a function involves some boilerplate as well.
</p>

<pre>
// Example from the bon README

#[builder]
fn greet(name: &str, level: Option&lt;u32>) -> String {
	let level = level.unwrap_or(0);

	format!("Hello {name}! Your level is {level}")
}

// This could be shortened to greet(name: "Bon", level: 24)
let greeting = greet()
	.name("Bon")
	.level(24)
	.call();

assert_eq!(greeting, "Hello Bon! Your level is 24");
</pre>

<p>
	In practice, the added complexity means that these patterns are only ever used
	in public APIs, so private functions have no worthwhile workaround to improve
	readability.
</p>

<h4>Extensibility without breaking changes</h4>

<p>
	Currently, adding a new parameter to an existing function is a breaking
	change. If you want to add a new field to a struct, your existing factory must
	implement a reasonable default value, and a new function must be created to
	enable the new functionality.
</p>

<p>
	This leads to some APIs having many functions which do similar things, but
	with different parameters. For example, <code>HashMap</code> from the standard
	library has the following factories.
</p>

<pre>
impl&lt;K, V> HashMap&lt;K, V, RandomState> {
	pub fn new() -> HashMap&lt;K, V, RandomState>;
	pub fn with_capacity(capacity: usize) -> HashMap&lt;K, V, RandomState>;
}

impl&lt;K, V, A: Allocator> HashMap&lt;K, V, RandomState, A> {
	pub fn new_in(alloc: A) -> Self;
	pub fn with_capacity_in(capacity: usize, alloc: A) -> Self;
}

impl&lt;K, V, S> HashMap&lt;K, V, S> {
	pub const fn with_hasher(hash_builder: S) -> HashMap&lt;K, V, S>;
	pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashMap&lt;K, V, S>;
}

impl&lt;K, V, S, A: Allocator> HashMap&lt;K, V, S, A> {
	pub fn with_hasher_in(hash_builder: S, alloc: A) -> Self;
	pub fn with_capacity_and_hasher_in(capacity: usize, hasher: S, alloc: A) -> Self;
}
</pre>

<p>
	All of these functions are shown in the documentation, polluting the API. The
	number of functions is exponential with respect to the number of parameters.
	If we ignore the differences in generic parameters (a possible solution to
	which is discussed in the "Future possibilities" section), then we could
	imagine having a single function.
</p>

<pre>
pub fn new(
	pub capacity: usize = 0,
	pub alloc: A = Global,
	pub hasher: S = RandomState,
);
</pre>

<p>
	If we wanted to some day add a <code>fill</code> parameter, we'd be able to
	add it to this existing function without it being a breaking change, and
	without doubling the number of factories for <code>HashMap</code>.
</p>

<h3>Guide-level explanation</h3>

<p>
	Parameters may be named or unnamed. By default, all parameters to a function
	are unnamed, and cannot be referred to by name. We can create a named
	parameter by adding the <code>pub</code> keyword before its name.
</p>

<pre>
fn print_labeled_measurement(pub value: i32, pub unit_label: char) {
	println!("The measurement is: {value}{unit_label}");
}

fn main() {
	print_labeled_measurement(5, 'h');
}
</pre>

<p>
	At first, this may not seem very different from unnamed parameters. But when
	a parameter is named, we may use its name when calling the function. This
	makes it easier to tell, at a glance, what the values being passed into the
	function are meant to do.
</p>

<pre>
print_labeled_measurement(value: 5, unit_label: 'h');
</pre>

<p>
	When parameters are named, they may also be re-ordered, so you don't need to
	remember the position of each one.
</p>

<pre>
print_labeled_measurement(unit_label: 'h', value: 5);
</pre>

<p>
	There are some caveats to the ordering. A function may contain both named and
	unnamed parameters. But if it does, all named arguments must be specified
	after the positional arguments, both in the function declaration and in the
	function body.
</p>

<pre>
// unit_label is named, value is not
fn print_labeled_measurement(value: i32, pub unit_label: char) {
	println!("The measurement is: {value}{unit_label}");
}

// forbidden: all named parameters must be after all unnamed parameters
fn _print_labeled_measurement(pub value: i32, unit_label: char) {
	// ...
}

fn main() {
	// forbidden: the parameter that 'h' is being passed into must be named
	print_labeled_measurement(value: 5, 'h');
	// forbidden
	print_labeled_measurement(unit_label: 'h', 5);
}
</pre>

<p>
	Named parameters may also have default values. This means that the caller does
	not need to specify the argument value when calling the function. The default
	value is using automatically.
</p>

<pre>
fn print_labeled_measurement(pub value: i32, pub unit_label: char = 'm') {
	println!("The measurement is: {value}{unit_label}");
}

fn main() {
	// unit_label is unspecified and is defaulted to 'm'
	print_labeled_measurement(5)
}
</pre>

<p>
	Named parameters cannot be used if the parameter does not have a name and is
	defined as a pattern instead. For example, the following is not valid.
</p>

<pre>
fn print_coordinates(pub &(x, y): &(i32, i32)) {
	// ...
}
</pre>

<p>
	However, a name can be given to this parameter by using an <code>@</code>
	binding.
</p>

<pre>
fn print_coordinates(pub point @ &(x, y): &(i32, i32)) {
	// ...
}
</pre>

<p>
	Traits may also choose to use named parameters, in which case the
	implementation must use the same name. However, trait function parameters
	cannot have default values.
</p>

<pre>
trait Trait {
	fn f(pub a: i32, pub b: i32);
}

impl Trait for () {
	// This is okay, because the parameter names match
	fn f(pub a: i32, pub b: i32) {}
}

impl Trait for i32 {
	// invalid: the second argument is named `b` in the definition
	fn f(pub a: i32, pub c: i32) {}
}

impl Trait for bool {
	// invalid: `b` is not public
	fn f(pub a: i32, b: i32) {}
}

impl Trait for X {
	// invalid: trait methods cannot have default parameter values
	fn f(pub a: i32, pub b: i32 = 0) {}
}
</pre>

<p>
	Because the parameter names are optional, function pointers may be created for
	functions with named parameters. However, the name will not be accessible on
	the function pointer, and default values cannot be used. This also applies for
	the <code>Fn*</code> family of traits.
</p>

<pre>
fn foo(pub a: i32, pub b: i32) {}

let f: fn(i32, i32) = foo;

f(4, 2);       // ok
f(a: 4, b: 2); // ERROR! `f` can't be called with named arguments

fn higher_order(f: Fn(i32, i32)) {
	f(4, 2);        // ok
	f(a: 4, b: 2);  // ERROR! `f` can't be called with named arguments
}
higher_order(foo);       // ok
higher_order(|_, _| {}); // ok
</pre>

<h3>Reference-level explanation</h3>

<h4>Grammar</h4>

<p>
	The only needed change to support public parameters in function declarations
	is to function parameters. First, we add the <code>pub</code> keyword, which
	must be followed by an identifier pattern. Such a pattern may also include a
	default value.
</p>

<pre>
FunctionParamPattern = PatternNoTopAlt ":" ( Type | "..." )
                     | "pub" IdentifierPattern ":" ( Type | "..." ) ( "=" Expr )?
</pre>

<p>
	For function calls, the syntax is slightly more complicated to parse, but only
	affects <code>CallParams</code>.
</p>

<pre>
CallParam  = Expression ( ":" Expression )?
CallParams = CallParam ( "," CallParam )* ","?
</pre>

<p>
	This is slightly more complicated to parse, because an identifier is also a
	valid expression. To solve this, we'll just parse an entire expression, and
	then check to see if there's a colon following it. The compiler can later
	give an error if the argument name is anything other than an identifier.
</p>

<h4>Static semantics</h4>

<h5>Function parameters</h5>

<p>
	Given a <code>FunctionParamPattern</code> where the default is specified, i.e.:
</p>

<pre>
FunctionParamPattern = "pub" pat:IdentifierPattern ":" ( ty:Type | "..." ) ( "=" expr:Expr )
</pre>

<p>Similar rules to the rules for default field values apply. Namely,</p>
<ul>
	<li>
		If the function is <code>const</code>, then the expression, <code>expr</code>
		must be a constant expression.
	</li>
	<li>The expression <code>expr</code> must coerce to the type <code>ty</code></li>
	<li>
		Generic parameters of the current items are accessible.
<pre>
fn foo&lt;const A: usize>(bar: usize = A) {}
</pre>
	</li>
	<li>
		Default const expressions are not evaluated at definition time, only during
		instantiation. This means that the following will not fail to compile:
<pre>
fn foo(a: usize = panic!(), b: usize = 42) {}

foo(a: 0);
</pre>
	</li>
	<li>
		The function's generic parameters are properly propagated, meaning the following is possible:
<pre>
fn foo&lt;T>(bar: Vec&lt;T> = Vec::new()) {}

foo::&lt;T>();
</pre>
	</li>
	<li>
		When lints check attributes such as <code>#[allow(lint_name)]</code> are
		placed on a <code>FunctionParam</code>, it also applies to the expression,
		<code>expr</code>.
	</li>
	<li>Unused parameter names will not emit a warning.</li>
</ul>

<p>
	For public parameters in general, all public parameters must be strictly after
	all non-public parameters.
</p>

<h5>Function calls</h5>

<p>
	The semantics of function calls change significantly. In general, all named
	arguments must be specified after all positional arguments. For positional
	arguments, all existing semantics apply.
</p>

<p>In the case of the following production of a named argument:</p>
<pre>
CallParam  = name:Expression ":" value:Expression
</pre>
<p>the following rules apply</p>
<ul>
	<li>The expression, <code>name</code> must be a plain identifier.</li>
	<li>
		The identifier, <code>name</code> must be the name of a public parameter of
		the function.
	</li>
	<li>
		The expression, <code>value</code> must coerce the the type of the
		aforementioned public parameter.
	</li>
</ul>

<h5>Trait declarations</h5>

<p>
	For trait declarations all the same rules apply from function declarations. In
	addition:
</p>
<ul>
	<li>Trait methods cannot have default parameter values.</li>
</ul>

<h5>Trait implementations</h5>

<p>
	The semantics of trait implementations vary slightly from the semantics for
	trait declarations.
</p>
<ul>
	<li>
		Any parameter which was declared public on the trait declaration must also
		be public in the trait implementation.
	</li>
	<li>Public parameters must have the same name that their declaration has.</li>
</ul>

<h3>Drawbacks</h3>		

<h4>Added complexity</h4>

<p>
	The language would be slightly more complex as a result of this feature. But
	this proposal is fairly minimal as far as named arguments proposals go. The
	syntax for named arguments resembles the syntax for struct fields, and the
	default value syntax is pretty similar to the syntax proposed for default
	field values.
</p>

<p>
	One concern that can come up in named argument proposals is the problem of it
	only applying to new functions, which makes it hard to remember which
	libraries. This proposal can be backported to the standard library, making the
	language feel more consistent.
</p>

<h4>Named arguments aren't mandatory</h4>

<p>
	The fact that names are optional could be seen as a compromise on the safety
	benefits. Some people may lazily choose to leave them out, in which case, no
	safety benefit is provided.
</p>

<p>
	However, the fact that they aren't mandatory allows more library authors to
	use them without worrying about breaking changes. Some authors may also be
	more inclined to support named arguments if they felt confident that it would
	not increase the verbosity of calling the function.
</p>

<p>
	This drawback does present a potential bug. If the names are removed during a
	refactor, then they may be in the wrong position after the refactor, causing
	a bug.
</p>

<pre>
// before refactor
coordinates(y: 45.6, x: 6784.0);

// after refactor: incorrect order
coordinates(y, x);
</pre>

<h5>Remedy: Add a clippy lint</h5>

<p>
	A clippy lint can be used to enforce the use of named arguments when possible.
</p>

<h5>Remedy: Require named arguments in a future edition</h5>

<p>
	Although it may be a large breaking change, future editions could likely
	require the use of named parameters, if it were deemed desirable to do so.
</p>

<h4>Limiting type ascription</h4>

<p>
	Many older proposals for named arguments noted that the syntax chosen here:
	<code>name: value</code> conflicts with the proposed type ascription feature.
	However, since then, the RFC for type ascription has been removed. And another
	type ascription RFC was rejected, partially on the basis of it potentially
	conflicting with named arguments. So this is not likely to be a concern
	anymore.
</p>

<h3>Rationale and alternatives</h3>

<h4>Why make named arguments opt-in?</h4>

<p>
	Although many languages, such as Kotlin and C#, allow any argument to be
	named, this is not practical for Rust. Changing an argument name is currently
	not a breaking change, but would become one as soon as named arguments are
	introduced. The solution is to allow authors to decide if their argument names
	are an implementation detail or not.
</p>

<h4>The <code>pub</code> keyword</h4>

<p>
	Using the <code>pub</code> keyword may seem like a strange choice, given the
	semantics. But it has essentially the effect of making the parameter name
	public, so this is defensible. Other visibility modifiers are not proposed by
	this RFC, because it's hard to imagine a situation where a public function
	would want to allow named arguments internally, but not externally.
</p>

<p>
	Other modifiers have been proposed. One could imagine adding an <code>ext</code>
	keyword to define named parameters. A previous RFC proposed using dots, but
	this was deemed to be too reminiscent of field access. Other symbols, such as
	<code>@</code> may be more acceptable. Another proposed idea is to copy Swift
	syntax and use two identifiers.
</p>

<pre>
fn foo(bar bar: &str) {}
</pre>

<p>
	Re-typing the same parameter name twice is verbose and usually unnecessary.
	It's also unnecessary since syntax exists for rebinding a variable already.
</p>

<p>
	Dart wraps named arguments in curly braces. This has the benefit of not
	requiring the <code>pub</code> keyword to be specified multiple times. However,
	in the context of Rust, this looks like a destructuring pattern, which isn't
	semantically correct.
</p>

<pre>
// In Dart
void enableFlags({bool? bold, bool? hidden})
enableFlags(bold: true)

// Similar idea, in Rust
fn enable_flags({ bold: Option&lt;bool> = None, hidden: Option&lt;bool> = None })
enable_flags(bold: Some(true))
</pre>

<p>
	Another proposal is to make function calls with named arguments look more like
	struct constructions by using curly braces instead of parentheses. This seems
	like it could be very confusing and make the distinction between the two
	features unclear.
</p>

<pre>
fn foo (a: u32) { b: u32 } {}
foo(1) { b: 2 };
</pre>

<h4>Colon vs equal sign</h4>

<p>
	Some have proposed using an equal sign (<code>=</code>) instead of a colon
	(<code>:</code>). This would make the syntax more closely resemble assignment,
	which is semantically true. It also would not possibly conflict with type
	ascription.
</p>

<p>
	The colon syntax was chosen for this RFC to be more consistent with struct
	initializers.
</p>

<h4>Argument re-ordering</h4>

<p>
	Some proposals for named arguments have omitted re-ordering of arguments for
	simplicity. For the sake of thoroughness, this RFC proposes allowing arguments
	to be re-ordered. A possible alternative would be to require arguments to
	still be in the original order. The drawback would be unnecessary friction
	when the user sorts the arguments out of order.
</p>

<p>
	Some languages allow for more flexible re-ordering than this RFC proposes. C#
	allows named arguments to appear before unnamed arguments, as long as the
	arguments are still in order. This RFC omits this feature, in order to make
	named arguments seem less confusing to users. The reordering proposed here
	still allows for more flexible argument-reordering in the future.
</p>

<h4>No default values in traits</h4>

<p>
	This RFC currently prohibits traits from making use of default parameter
	values. This is because it's unclear if default values for trait parameters
	should be defined at the trait definition or the trait implementation. It
	would likely be more predictable if default values could be defined by the
	implementation. But it's hard to imagine how a default value would allow the
	trait to be dyn-compatible. If the default values were defined at the trait
	definition, then they could stay dyn-compatible.
</p>

<h4>On const contexts</h4>

<p>
	In the default field values RFC, it was decided that default values for
	structs must be const. The reasoning is that functions with side-effects may
	be confusing for the compiler and for users. However, these justifications
	don't apply to functions, because non-const functions don't carry an
	expectation of being cheap or consistent. It is possible to always require
	default values to be const, but there's seemingly no strong reason to do so.
</p>

<p>
	However, it would be confusing if default parameters could be non-const in
	const functions, which is why they are required to be const in that case.
</p>

<h4>Named arguments aren't mandatory</h4>

<p>See the section above in "Drawbacks".</p>

<h4>Alternative: Do nothing</h4>

<p>
	The "Motivation" section explains thoroughly how the current state is very
	verbose, to the point where existing alternatives, such a struct parameters
	and builders are often not used. The existence of these patterns is evidence
	that users want some way to emulate named arguments. The fact that these
	alternatives are not used in private APIs is evidence that the status quo is
	annoyingly verbose.
</p>

<h4>Alternative: Structural Records</h4>

<p>
	It has been proposed many times that structural records, alongside default
	field values, could emulate named parameters. This is a pattern that is often
	used in languages like TypeScript. The syntax for declaring such a function
	would look like this.
</p>

<pre>
fn foo({ a, b, c }: { a: f64, b: f64, c: f64 }) {}
</pre>

<p>There are several criticisms I have of this pattern:</p>
<ul>
	<li>
		It cannot be backported to older functions without causing a breaking change,
		so the standard library could not use it.
	</li>
	<li>
		Every identifier in this declaration needs to be typed twice. It might be
		possible to introduce syntactic sugar for this, but not such syntax has been
		proposed.
	</li>
	<li>
		Structural records have had a proposed RFC in the past, but was rejected,
		mainly due to the complexity of implementing it.
	</li>
</ul>

<p>
	Structural records could serve as a temporary compromise for named arguments,
	if they are ever implemented. I'm not currently convinced that they ever will
	be implemented. If the compromise solution is harder to implement than the
	real solution, then we might as well just use the real solution.
</p>

<h4>Alternative: Struct name inference</h4>

<p>
	One proposal to make named arguments easier is to allow the name of a struct
	to be inferred when it is constructed. This would make the call sites of
	functions using struct parameters to look mostly simple.
</p>

<pre>
foo(_ { a: 1, b: "2", c: [] });
</pre>

<p>
	However, this still requires the function to support this function explicitly,
	by defining a struct to be passed in. It also still cannot be backported to
	existing functions. The only benefit this provides over the status quo is to
	avoid typing the name of the struct at the call site, which is not the main
	criticism of this pattern. It would still be unlikely for private functions to
	use this pattern.
</p>

<h4>Alternative: Implementing <code>Fn</code> traits</h4>

<p>
	I've seen this come up a couple of times, but I think most of the people
	suggesting this are doing so as a joke. Implementing <code>Fn</code> would
	be a more verbose form of the method overload feature that some other
	languages have. Overloads are difficult and controversial to implement in
	Rust, so it's unlikely this pattern would be adopted.
</p>

<h3>Prior art</h3>

<p>
	Several previous attempts at named arguments, both in Rust and in other
	languages, have been described throughout this RFC.
</p>

<p>
	A previous RFC for named arguments exists for Rust. It was rejected mainly
	because it did not describe what the future of named arguments would look like
	with optional arguments. This RFC attempts to address as many future uses of
	named arguments as possible in order to give a full picture.
</p>
<p>
	A pre-RFC was proposed by forum user Azerupi, but no RFC seemed to come as a
	result of it. Several complications were pointed out by commenters. This RFC
	attempts to address syntactic ambiguity, the type system, and opt-in naming.
</p>
<p>
	Another pre-RFC proposed using a more Swift-style syntax, which was avoided in
	this RFC. The pre-RFC also proposed overloading but not default arguments.
	Arguably the latter is more important than the former.
</p>
<p>
	As was mentioned before, bon offers a procedural macro for creating builders
	from functions. The existence of this feature shows that there is a desire for
	named arguments, that would be better handled as a language feature.
</p>

<p>
	Other lanaguages with named arguments have also been discussed. The feature
	proposed here is similar to named arguments from C# or Kotlin, with the
	difference being that thhe named arguments are opt-in. The syntaxes used by
	Dart and Swift have also been discussed. Dart uses the following syntax, which
	looks closer to destructuring than named arguments.
</p>

<pre>
void enableFlags({bool? bold, bool? hidden})
enableFlags(bold: true)
</pre>

<p>
	Swift makes all arguments named by default, allowing authors to opt-out by
	using an underscore before the variable name. This is impossible to port to
	Rust without making a breaking change to the language. Various proposals have
	tried to use slightly different syntaxes, but many of them present syntactic
	ambiguities.
</p>

<h3>Unresolved questions</h3>

<ul>
	<li>Should we require named arguments to be given in position order?</li>
	<li>Is <code>pub</code> the best keyword, or should a different keyword be used?</li>
	<li>Should default values be required to always be const?</li>
</ul>

<h3>Future possibilities</h3>

<h4>Default parameter values for non-public parameters</h4>

<p>
	In this proposal, only public parameters can have default values. Optional
	named parameters are much more flexible than optional positional parameters,
	but many languages do still support optional positional arguments. It's not
	hard to imagine extending this proposal to positional arguments, although the
	use-case would be more limited.
</p>

<h4>Default parameter values for trait methods</h4>

<p>
	Trait methods were forbidden from having default values due to the question of
	if the default value should be on the trait declaration or the trait
	implementation. Having it be on the implementation would be consistent with
	how associated types and constants work. But this would make it hard for such
	a trait to be dyn-compatible.
</p>

<p>
	One solution would be to only allow default values to be used in non-dyn
	contexts.
</p>

<pre>
trait Foo {
	fn bar(&self, baz: i32 = 0);
}

impl Foo for i32 {
	fn bar(&self, baz: i32 = i32::MAX) {}
}

fn do_bar&lt;T: Foo>(x: &T) {
	x.bar(); // baz doesn't need to be specified, since we're using generics
}

fn do_bar_2(x: &dyn T) {
	x.bar(); // ERROR: baz must be specified when using a method on a dyn object
}
</pre>

<h4>Extending traits</h4>

<p>
	Theoretically, there's nothing preventing the following, as long as a default
	parameter value is provided:
</p>

<pre>
impl&lt;T> Default for Vec&lt;T> {
	fn default(capacity: usize = 0) -> Self {
		Self::with_capacity(capacity)
	}
}
</pre>

<p>
	Although, in addition to making the behavior of the <code>default</code>
	function inconsistent, this is even harder to resolve in a <code>dyn</code>
	context. The likely remedy would be to forbid <code>Vec</code> from being
	converted into a <code>dyn Default</code>, which would make introducing the
	named parameter a breaking change.
</p>

<h4>Requiring named arguments in a future edition</h4>

<p>
	Allowing named arguments to be optional is necessary in order to backport them
	into existing functions without creating breaking changes. This comes with
	some compromises, as has been described in the "Drawbacks" section. However,
	the way this RFC is drafted makes it possible to require named arguments in a
	future edition of Rust. Specifying a named argument would not give a warning
	in either the previous edition or the new edition.
</p>

<p>
	Doing this too early would likely hurt adoption of named arguments, as
	backporting them into existing functions would become a breaking API change.
	This would also increase the verbosity of calling functions which make use of
	named parameters, hurting adoption further.
</p>

<h4>More flexible re-ordering</h4>

<p>
	As mentioned before, re-ordering of named arguments is allowed by this RFC,
	in a limited way. The limitations are kept in order to keep the rules easy to
	explain and reason about. However, some languages allow arguments to be
	re-ordered more flexibly, and this would be possible to do in the future.
</p>

<p>
	C#'s main rule is that out-of-order named arguments cannot be followed by
	positional arguments.
</p>

<pre>
print_order_details(order_num: 31, product_name: "Red Mug", seller_name: "Gift Shop");
print_order_details(seller_name: "Gift Shop", product_name: "Red Mug", order_num: 31);
print_order_details("Gift Shop", 31, product_name: "Red Mug");
print_order_details(seller_name: "Gift Shop", 31, product_name: "Red Mug");

// this would cause an error
print_order_details(product_name: "Red Mug", 31, "Gift Shop");
</pre>

<h4>Defaults Affect Inference</h4>

<p>
	In 2022, Gankra proposed using default parameter values as part of inference
	for generic type parameters. We could imagine something like this.
</p>

<pre>
fn default_hasher() -> RandomState { ... }

impl &lt;K, V, S: BuildHasher> HashMap&lt;K, V, S> {
    fn new(hasher: S = RandomState::default()) { ... }
}

// uses S=RandomState
let map = HashMap::new();
</pre>

<p>
	The downside of this particular approach, as Gankra pointed out, is that this
	would only allow the argument to be omitted if the type of <code>S</code> is
	<code>RandomState</code>. Although, that's already true with the current set
	of <code>HashMap</code> factories, unless you use <code>default</code> which
	doesn't have any parameters at all.
</p>

<h4>Generic const</h4>

<p>
	In the case of <code>Vec</code>, adding an optional <code>capacity</code>
	parameter is not possible with this iteration of the RFC, because
	<code>Vec::new</code> is a <code>const</code> function, and allocating memory
	cannot be done at compile-time. This is a very difficult problem to solve,
	and it's possible that it never will. However, if keyword generics are added
	to the language, it may be possible to do something like this:
</p>

<pre>
const&lt;C> trait Capacity: Copy {
	const&lt;C> fn allocate&lt;T>(self) -> Option&lt;Box&lt;[MaybeUninit&lt;T>]>>;

	const fn as_usize(self) -> usize;
}

const impl Capacity for () {
	const fn allocate&lt;T>(self) -> Option&lt;Box&lt;[MabyUninit&lt;T>]>> {
		None
	}

	const fn as_usize(self) -> usize {
		0
	}
}

impl Capacity for usize {
	fn allocate&lt;T>(self) -> Option&lt;Box&lt;[MaybeUninit&lt;T>]>> {
		Some(Box::default())
	}

	const fn as_usize(self) -> usize {
		self
	}
}

const&lt;C> impl&lt;T, Cap: const&lt;C> Capacity> Vec&lt;T> {
	pub const&lt;C> fn new(capacity: Cap = ()) -> Self {
		Self {
			len: 0,
			capacity: capacity.as_usize(),
			buffer: capacity.allocate(),
		}
	}
}
</pre>

<p>
	If no argument value is provided, then the function would be
	<code>const</code>. But when a <code>usize</code> is provided, then it would
	no longer be <code>const</code>.
</p>

</article>
</body>