-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
optional parameters #152
optional parameters #152
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
- Start Date: 2014-07-03 | ||
- RFC PR #: (leave this empty) | ||
- Rust Issue #: (leave this empty) | ||
|
||
# Summary | ||
|
||
This RFC proposes to add optional parameters to Rust functions. | ||
|
||
# Motivation | ||
|
||
Currently in Rust there are a lot of functions that do the same thing, but take a different number of parameters. | ||
The current design forces those functions to have different names. | ||
This causes the standard library to have badly-named functions. | ||
|
||
# Detailed design | ||
|
||
Optional arguments are an implicit form of overloading. | ||
Java has a very complicated overloading design that includes overloading by static types. | ||
Overloading on types mixed with type inference might be very confusing. | ||
However, overloading based on arity is very simple and clear. | ||
Java also forces the writer of the function to write out a new function signature for every possible variant. | ||
A nice syntax for optional parameters is preferable to the Java approach. | ||
|
||
So `to_str_radix(&self, radix: uint) -> String` can be now written as `to_str(&self, ?(radix: uint))` | ||
Since this is sugar for full overloading, a function declared with an optional parameter like this satisfies both | ||
```rust | ||
pub trait ToStr { | ||
fn to_str_(&self) -> String; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the trailing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, it's a typo |
||
} | ||
``` | ||
|
||
and | ||
|
||
```rust | ||
pub trait ToStrRadix { | ||
fn to_str(&self, radix: uint) -> String; | ||
} | ||
``` | ||
|
||
traits. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand what you mean by "satisfies both traits". Rust isn't duck-typed, and therefore you only satisfy a trait if you explicitly implement it. Merely implementing a function with a compatible type signature doesn't allow you to do anything generically. |
||
|
||
Inside the body of the function, the arguments will be pattern matched something like this: | ||
|
||
```rust | ||
let rad = match args { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this implies that functionality that was previously "executed" at compile time (i.e. calling a different method to get different behaviour) would now, instead, be executed at runtime, as a switch over the number of arguments. Am I interpreting this correctly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this something like Javascript's implicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a problem, since the contents have different types. |
||
[_, radix] => radix, | ||
_ => 10 | ||
}; | ||
``` | ||
|
||
This allows for default arguments or actually doing completely different in each case. | ||
|
||
This will let Rust get rid of the sheer multitude of functions that only differ by a few parameters like | ||
`concat` and `connect`; `split` and `splitn`. You can even go further and have a boolean indicating whether to | ||
use terminator semantics and put that into `split`, eliminating `split_terminator`. | ||
You could also have another boolean indicating whether it is reversed, eliminating `rsplitn`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note here, but boolean flags are an API anti-pattern. I'd rather have separately-named functions. |
||
|
||
In this way, the library design is better, allowing auto-completion on `split`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what way does the lack of optional parameters impede autocompletion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could want to "concat by character" and be looking for all functions that start with |
||
There are less names in the std library. Some naming problems go away. | ||
|
||
The easiest design is to allow all trailing parameters to be optional. | ||
That means that no mandatory parameter can come after an optional one. | ||
This design still allows to simplify naming in the standard library before 1.0 ships and the names are set in stone. | ||
Further refinements like trailing varargs, keyword arguments are possible in a backwards-compatible way. | ||
|
||
# Drawbacks | ||
|
||
Currently, you are able to write a function that differs by name to achieve the same effect. | ||
This works for `slice_from` and `slice_to` and `slice`. | ||
They are aptly named for what they do and can be easily autocompleted. | ||
However, just one `slice` with two optional args (defaulting to 0 and the length of the string) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we have code examples for how the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'll strengthen that: this needs more examples of declaration and of calling (e.g. I don't see any use of the |
||
is much more elegant and doesn't clutter up the standard library with extra functions. | ||
|
||
Of course, as I mentioned earlier, this also interacts with traits since now you satisfy two traits with one function. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another reminder that this is not how traits work in Rust (see above). |
||
This probably interacts with closures and/or lifetimes in some way as well. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it would be impossible for any function to return a reference that in any way relied upon the lifetime of an optional input. |
||
So the correct standard library design must be weighed against adding yet another feature to Rust. | ||
|
||
# Alternatives | ||
|
||
Another proposal is to somehow represent optional arguments as some kind of an Option type. | ||
The drawback of this proposal is that Option is a library type. It would have to be baked into the language instead. | ||
|
||
Another alternative is to keep the full overloading syntax. This eliminates having to destructure the args array. | ||
While this makes it a pain to rewrite all the possible variants, it's extremely explicit and clear. | ||
If you want either one or three arguments only, it won't accept two. | ||
For any function with k mandatory parameters and n total parameters the current proposal accepts all arities between k and n. | ||
|
||
An alternative to trailing optional arguments is keyword arguments. This allows optional arguments in any place as long as | ||
the following required arguments or following optional arguments are called by their keywords to resolve ambiguities. | ||
This proposal has the downside of a more complicated argument resolution system (allowing some arguments to be called by position | ||
and some by keyword). It can also be implemented in a backwards-compatible way post 1.0 so it's not a 1.0 priority. | ||
|
||
# Unresolved questions | ||
|
||
If destructuring the arguments array is necessary, should there be some kind of a keyword for them. | ||
Is there any way to implement it without using a keyword, other than Java-style overloading? | ||
|
||
If varargs are included in the proposal, then the `main` function could be written as | ||
|
||
```rust | ||
fn main(...arguments: String) | ||
``` | ||
|
||
which could be nice. But then again, using std::os::args() is not a huge problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't argue that some of our stdlib functions could be better named, but finding better names is not impossible. I also don't argue that optional parameters will allow strictly nicer names some of the time; god knows I've wished for them before. But keep in mind that up till now our stance has been that being forced to name your functions explicitly has been considered a feature, and that the lack of overloading/optional parameters has been an explicit choice (though entirely subjective). To overcome this inertia, you'd be best served by showing before/after comparisons of how real-life stdlib functions would look (both in definition and in usage) if this change were accepted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd rather resubmit this as Java-style overloading. It's easier with static typing + no nulls. In C# you'd just go with unwrap(default = null) and call it a day. Not so easy in Rust since Option is more syntactically noisy.