|
| 1 | +- Start Date: 2015-01-26 |
| 2 | +- RFC PR #: (leave this empty) |
| 3 | +- Rust Issue #: (leave this empty) |
| 4 | + |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +Add keyword arguments to Rust in a backwards-compatible way. |
| 9 | +This allows overloading that is safe in regards to type inference while being decided at compile time. |
| 10 | + |
| 11 | +# Motivation |
| 12 | + |
| 13 | +Allow another kind of argument in Rust. Current arguments distinguish themselves from other types by their order. |
| 14 | +If there is no semantic reason for a certain order, the argument order in functions is basically arbitrary. |
| 15 | +Consider a call like this: |
| 16 | +```rust |
| 17 | +window.addNewControl("Title", 20, 50, 100, 50, true); |
| 18 | +``` |
| 19 | + |
| 20 | +First of all, the call tells nothing the user about what those values mean. |
| 21 | +Secondly, their order is arbitrary and must be memorized. What I propose is that this call would rather look like: |
| 22 | +```rust |
| 23 | +window.addNewControl( |
| 24 | + title => "Title", |
| 25 | + xPosition => 20, |
| 26 | + yPosition => 50, |
| 27 | + width => 100, |
| 28 | + height => 50, |
| 29 | + drawingNow => true); |
| 30 | +``` |
| 31 | + |
| 32 | +While you might argue that this is more verbose, this is already the standard in the JavaScript ecosystem. |
| 33 | +A lot of libraries in JavaScript actually have a convention with calling with associative arrays like: |
| 34 | +```JavaScript |
| 35 | +window.addNewControl({ xPosition: 20, yPosition: 50, width: 100, height: 5, |
| 36 | + drawingNow: true }); |
| 37 | +``` |
| 38 | +If this wasn't a convenient pattern, nobody would bother to do it. |
| 39 | + |
| 40 | +An additional benefit is that this leads to an easy implementation of optional parameters and optional parameters. |
| 41 | +In languages like PHP, default parameters must come at the end of the function and must have a value. |
| 42 | + |
| 43 | +You can have optional parameters in the beginning, middle and end of functions without any default value. |
| 44 | +This means that this design allows for truly optional parameters without any run-time cost of using an `Option`. |
| 45 | + |
| 46 | +# Detailed design |
| 47 | +Currently Rust has |
| 48 | + |
| 49 | +```rust |
| 50 | +fn slice(&self, begin: usize, end: usize) -> &'a str |
| 51 | +fn slice_from(&self, begin: usize) -> &'a str |
| 52 | +fn slice_to(&self, end: usize) -> &'a str |
| 53 | +``` |
| 54 | + |
| 55 | +This can be changed to |
| 56 | + |
| 57 | +```rust |
| 58 | +fn slice(&self, from => begin: usize, to => end: usize) -> &'a str |
| 59 | +fn slice(&self, from => begin: usize) -> &'a str |
| 60 | +fn slice(&self, to => end: usize) -> &'a str |
| 61 | +``` |
| 62 | + |
| 63 | +Note that these are three different functions that have three different signatures. |
| 64 | +The keywords a function accepts is part of its signature. You can call these functions like this: |
| 65 | + |
| 66 | +```rust |
| 67 | +foo.slice(from => 5); //equivalent to current foo.slice_from(5) |
| 68 | +foo.slice(to => 9); //equivalent to current foo.slice_to(9) |
| 69 | +foo.slice(from => 5, to => 9); //equivalent to current foo.slice(5, 9) |
| 70 | +foo.slice(from => 9, to => 5); //equivalent to current foo.slice(5, 9) |
| 71 | +``` |
| 72 | + |
| 73 | +The trait looks like |
| 74 | + |
| 75 | +```rust |
| 76 | +Fn(&self, from => begin: usize, to => end: usize) -> &'a str |
| 77 | +//which desugars to |
| 78 | +Fn<({from: usize, to: usize}, &Self), &'a str> |
| 79 | +``` |
| 80 | + |
| 81 | +So this is equivalent for using a `struct` to pass around your keyword parameters. |
| 82 | +But now you are able to use the same function name several times as long as the keywords are different. |
| 83 | + |
| 84 | +# Drawbacks |
| 85 | + |
| 86 | +This is a more complicated design than just having default arguments and overloading. |
| 87 | +Now there are both positional and keyword arguments and they interact with lifetimes, traits, and closures. |
| 88 | + |
| 89 | +# Alternatives |
| 90 | + |
| 91 | +A better design to the above function might be designing it like so: |
| 92 | + |
| 93 | +```rust |
| 94 | +let title = "Title"; |
| 95 | +let position = Position(20, 50); |
| 96 | +let dimensions = Dimension(100, 50); |
| 97 | +window.addNewControlDrawingNow(title, position, dimensions); |
| 98 | +``` |
| 99 | + |
| 100 | +Now the function takes three parameters, and we assigned them meaningful names. |
| 101 | +Instead of passing a boolean, we created functions that do different things. |
| 102 | +They could be named something like `addNewControl` and `addNewControlDrawingNow`. |
| 103 | + |
| 104 | +While this design is better, it still doesn't solve the problem of having to remember of the order. |
| 105 | +Where do you put `dimensions`, `position`, and the `title`? |
| 106 | +At least the compiler will now verify that the types are correct. |
| 107 | +It is still up to the programmer to actually name those variables well, instead of the API specifying what the keywords should be. |
| 108 | + |
| 109 | +If keyword arguments themselves are not implemented, then there's also the issue of overloading to enable better API design. |
| 110 | +Another proposal would be to assign keywords to all current Rust code with their local names. |
| 111 | +So something like: |
| 112 | + |
| 113 | +```rust |
| 114 | +foo.slice(begin => 5, end => 10); |
| 115 | +``` |
| 116 | + |
| 117 | +This will solve the problem of the naming of parameters, but won't solve the problem of overloading. |
| 118 | +Because this design this allows you to call this method like: |
| 119 | + |
| 120 | +```rust |
| 121 | +foo.slice(x, y); |
| 122 | +``` |
| 123 | + |
| 124 | +This means that you potentially can't infer the types of the arguments if they were overloaded. |
| 125 | +Another overload could add another `slice()` that takes a different type of parameter in the first slot. |
| 126 | +What type would `x` be then? |
| 127 | +Even though it has a different name, being able to call keyword arguments without keywords breaks overloading. |
| 128 | + |
| 129 | +With mandatory keywords it is always known what type a certain keyword is. |
| 130 | + |
| 131 | +# Unresolved questions |
| 132 | + |
| 133 | +The trait desugaring is an addition based on the discussion on the internals board. |
| 134 | +Could it come first (before the self parameter) or does it have to come last? |
| 135 | + |
| 136 | +Another possibility is a different syntax for keywords. Maybe it would be better to unify it with the struct syntax. |
| 137 | +So something like `foo.slice(begin: 5, end: 10)` because it desugars to a keyword struct anyway. |
| 138 | + |
| 139 | +But what would the declaration look like? |
0 commit comments