- Start Date: 2015-01-26
- RFC PR #: (leave this empty)
- Rust Issue #: (leave this empty)
Summary
Add keyword arguments to Rust in a backwards-compatible way.
Motivation
Allow another kind of argument in Rust. Current arguments distinguish themselves from other types by their order. If there is no semantic reason for a certain order, the argument order in functions is basically arbitrary. Consider a call like this:
window.addNewControl("Title", 20, 50, 100, 50, true);
First of all, the call tells nothing the user about what those values mean. Secondly, their order is arbitrary and must be memorized. What I propose is that this call would rather look like:
window.addNewControl(
title => "Title",
xPosition => 20,
yPosition => 50,
width => 100,
height => 50,
drawingNow => true);
While you might argue that this is more verbose, a lot of libraries in JavaScript actually have a convention with calling with associative arrays like:
window.addNewControl({ xPosition: 20, yPosition: 50, width: 100, height: 5,
drawingNow: true });
If this wasn’t a convenient pattern, nobody would bother to do it.
An additional benefit is that this leads to an easy implementation of optional parameters and optional parameters. In languages like PHP, default parameters must come at the end of the function and must have a value. But in languages that support some sort of currying, not specifying the last parameter gives you a partially-applied function! This means that default parameters and currying/partial application are at odds with each other normally because you can’t tell when a function is partially-applied or has default parameter.
The design with keyword arguments doesn’t have this problem: you can execute the function when the last positional argument is given and list all the keyword arguments out of order. That means you can keep passing arguments to a partially-applied function and it won’t execute until the last positional argument is given. Furthermore, you can have optional parameters in the beginning, middle and end of functions without any restrictions other than that they have to be keyword arguments.
Detailed design
Currently Rust has
fn slice(&self, begin: usize, end: usize) -> &'a str
fn slice_from(&self, begin: usize) -> &'a str
fn slice_to(&self, end: usize) -> &'a str
This can be changed to
fn slice(&self, from => begin: usize, to => end: usize) -> &'a str
fn slice(&self, from => begin: usize) -> &'a str
fn slice(&self, to => end: usize) -> &'a str
Note that these are three different functions that have three different signatures. The keywords a function accepts is part of its signature. You can call these functions like this:
foo.slice(from => 5); //equivalent to current foo.slice_from(5)
foo.slice(to => 9); //equivalent to current foo.slice_to(9)
foo.slice(from => 5, to => 9); //equivalent to current foo.slice(5, 9)
foo.slice(from => 9, to => 5); //equivalent to current foo.slice(5, 9)
if Rust had some kind of way to curry functions added later, you could curry the arguments and then manually pass the &self
as the last argument to the function:
let x = curried_slice(from => 5); //curried
x(foo); //foo.slice_from(5);
let y = curried_slice(from => 5); //curried
let z = y(to => 10); //curried
z(foo); //foo.slice(5, 10);
So this feature is future-proof for a possible currying method in Rust. In JavaScript a lot of libraries cannot be used by some kind of a curry()
function because the order of the arguments is wrong - the authors never considered that someone would try to curry their functions and partial application in JS is pretty useless because of this (you sometimes want to partially apply the SECOND argument and it’s painful to do this). Having a design that already solves issues with argument order is an indirect benefit.
Drawbacks
This is a more complicated design than just having default arguments and overloading. Now there are two different types of arguments (positional and keyword) and they might interact with each other, lifetimes, traits, closures in different ways. It doesn’t solve the problem of currying when there are no positional arguments in the first place, so you need at least one positional argument to curry in a pain-free way.
Alternatives
A better design to the above function might be designing it like so:
let title = "Title";
let position = Position(20, 50);
let dimensions = Dimension(100, 50);
window.addNewControlDrawingNow(title, position, dimensions);
Now the function takes three parameters, and we assigned them meaningful names. We have created two different functions addNewControl
and addNewControlDrawingNow
instead of passing a boolean to choose whether the control draws now.
While this design is better, it still doesn’t solve the problem of having to remember of what order to put dimensions
, position
, and the title
. At least the compiler will now verify that the types are correct. It is still up to the programmer to actually name those variables well, instead of the API specifying what the keywords should be.
If keyword arguments themselves are not implemented, then there’s also the issue of overloading to enable better API design.
Unresolved questions
Should closures have keyword arguments as well?