-
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
Default and positional arguments #257
Conversation
8b72b4b
to
8a9d54d
Compare
I'd love to at least have keyword arguments. Self-documenting calls are pretty nice. I think Swift does it well, but probably overly-complex for Rust. Default arguments are also convenient, especially if it's an issue with bindings like you say. The sugar also elegantly maps to default arguments I think, as long as it's still possible to pass an explicit |
I think it would be a good thing too to have both in Rust, though I wait for the comments of the core team to form a precise opinion about it. The issue about the traits is interesting too. While in most cases, you expect indeed all the implementors to implement the same semantics and thus to use the same default value for an argument (which should then be specified by the trait, and described in its documentation), I think in some cases, the public interface of a trait might just expose the fact that an argument is optionnal, and leave the actual value of this argument as an implementation detail. That being said, I haven't found an actual use case for this, I just have the intuition that both approaches could be useful, but I may be wrong, |
This is a lovely RFC that provides a much more usable language API over #258. |
I think this would be nice in the language, but I don't like this part: fn toto(a: uint, b: uint = 2, c: uint) { ... }
toto(1, c: 22, 3); // Resolved as toto(1, 3, 22)
// Same as std::fmt format string definition. I think it is quite confusing. Maybe would be better to set a simpler rule, like: if you alter the declaration parameter ordering in the middle of a function invocation, you are forced to specify the parameters name for the rest of the function invocation. So you would be forced to do this instead: toto(1, c: 22, b: 3); |
Why not use "=" to define positional arguments (instead of ":"). This keeps things consistent with the default declaration and other languages, like Python. |
@arthurprs, I believe this is done to be consistent with structure instantiation: SomeStruct {
field1: value1,
field2: value2
} And it does look good, I think. +1 for the RFC, it would make writing expressive APIs much easier, and a possibility for adequate C++ bindings are also a huge win. |
How would this interact with FFI (exporting Rust functions to C)? Also, does the default value for an argument need to be |
+1 Would be nice to have non-static expressions. Agree with @theypsilon about |
@bvssvni That's not quite what was said:
This is useful if you have a function like:
and you want to write:
It's the reordering that's problematic. |
I posted a proposal, on Rust Discuss, for something similar to this but instead by using syntactic sugar for structs. The function would have to take a |
+1 for RFC. Personally used to using this in Python, its one of the most useful things ever for designing APIs that don't need a dozen similarly named functions for slight variations of options (or that have to take a very un-informative and verbose "options" object) |
@arthurprs, @netvl: Using |
|
||
* API design. Some functions in the Rust standard distribution libraries or in third party libraries have many similar methods that only vary by the presence of an additional parameter. Having to find and to memorize different names for them can be cumbersome. | ||
|
||
* If this feature can be added to Rust, it can't be added post-1.0, as a lot of functions in the Rust standard library must be rewritten in order to benefit this feature. |
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.
This is a bit strong, as only stable libraries would have issues with changing, and not all of the standard library will be stable. I'm also not sure that they can't be changed in a backwards compatible way.
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.
They could be changed in a source backward compatible way by adding default parameters to existing methods as long as the then useless methods were kept.
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.
and not all of the standard library will be stable
Rust standard library API isn't supposed to be frozen post-1.0?
If so, that's what I mean when I say it can't be added post-1.0, as the API itself should be changed in order to use default args.
And add it post-1.0 could lead to keep "old-fashioned" functions in the standard library (I think)
I agree with this! It would be really confusing if this wasn't the case. This is a much better rule than the one proposed in the RFC. |
|
||
* If this feature can be added to Rust, it can't be added post-1.0, as a lot of functions in the Rust standard library must be rewritten in order to benefit this feature. | ||
|
||
* Foreign bindings. This has been discussed on the mailing list recently about Qt5 bindings. Binding functions that make heavy use of default arguments or operator overloading can be very messy with the current system. |
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 do not see how this helps.
If I remember correctly, the main issue raised was the absence of overloading in the presence of a variety of parameters. fillRectangle
has 12 overloads, for example (it should have 16 to be consistent), and even trimming it down to the bare essential it retains 2:
fillRect(QRect const&, QBrush const&)
fillRect(QRectF const&, QBrush const&)
Whether you have default or positional arguments does not help in generating bindings here.
And in general, you can generate bindings by simply "forgetting" default arguments. Certainly the resulting interface is less friendly (you now have to specify all parameters on each call), but it is automatically generated with no pain.
Thus, you might actually be undermining what is otherwise an interesting proposal. I would simply consider dropping that motivation.
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.
As overloading is not the purpose of this RFC, I think this motivation is "correct" as it help fixing some issues in FFI design with C++ code (not all, but at least the "default arguments" one)
+1
|
I think visibility of a parameter name should also be considered. It is quite important since there might be someone who does not want to break backward compatibility of his library by simply changing a parameter name, and it cannot be easily addressed post-1.0. |
Using |
@summerlight avoiding unintentional dependence on parameter names is a good goal. (Though I'll note that it would be an issue even if the core team does not get around to addressing this until post 1.0) Maybe the RFC could be amended to use |
Actually, the rule I wrote in this RFC is the same as parameter ordering in format string from
As the default/named args is done by "just" sugaring function calling, the default values doesn't have constraints like this, as they are "just" placed automatically at call-time.
I'm not sure about what's the issue with parameter names. |
I think this touches on how Swift does things, where it separates parameter names into local (for use within the function body) and external (for use at the call-site) parameter names (inherited from Objective-C where both are mandatory, though not in Swift). This separation allows the local parameter name to be changed without breaking the external interface/dependent clients. I think this is a very practical and elegant way of reconciling flexibility with Swift's intended goal of first-class interop with Objective-C, where both local and external parameter names are required in every method declaration (IIRC). By default, only local parameter names are given in a function declaration, similar to most languages. Since no external parameter name is provided, the parameters need not be named at the call-site: func join(s1: String, s2: String, joiner: String) -> String {
return s1 + joiner + s2
}
join("hello", "world", ","); However, an additional external parameter name, used for naming the parameter at the call-site, can be provided preceding the local parameter name. So in the following, the first parameter name is the external parameter name and the second is the local parameter name. Note that if an external parameter name is provided, it must always be used to name that parameter whenever that function is called. Also, external parameter names are not all-or-nothing; one parameter can be given an external name and another can opt-out. func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String {
return s1 + joiner + s2
}
join(string: "hello", toString: "world", withJoiner: ", ") Swift also supports sugar for easily defining the same name for both local and external parameter names using the // instead of
// func join(string string: String, toString toString: String, withJoiner withJoiner: String) -> String {
func join(#string: String, #toString: String, #withJoiner: String) -> String {
return string + withJoiner + toString
} Since Swift also supports default parameter values, it automatically provides external names for any parameter that has a default value, so that in the following, func join(s1: String, s2: String, joiner: String = " ") -> String {
return s1 + joiner + s2
}
join("one", "two")
join("hello", "world", joiner: "-") However, it's possible to opt-out of this automatic behavior by providing an explicit external parameter name of value func join(s1: String, s2: String, _ joiner: String = " ") -> String {
return s1 + joiner + s2
} All that said, like I said in my first comment, perhaps this is overly complex for Rust's needs. Still, I thought it may be useful to compare to another recent/similar language that has tackled this problem. |
@blaenk |
I think it's an artifact of Objective-C where both are required. The naming convention in Objective-C/Cocoa/etc. has named parameters like the above, From the RFC:
Since named arguments are part of the function signature, I imagine functions that use them won't be directly exportable as |
As C ABI doesn't support default/named arguments, an |
@P1start: Ah, good point. Seems like |
If we're already going to start bikeshedding over the symbol to use, I'm strongly in favor of the one proposed in this RFC, |
mod M {
pub struct X(int);
pub fn f(x: X = X(1)) { }
}
fn main() {
M::f();
} The RFC doesn't say if this code is valid. |
@mahkoh Maybe I'm wrong, but I don't see any issue in this code. |
@KokaKiwi The parameter in X is private. |
@KokaKiwi you wrote:
but this is the point: the scenario you describe for Someone who is writing a function may not realize that they do not have the freedom to choose different names later without risking breaking code downstream. Requiring keyword parameters to be marked |
Closing as postponed; filing as part of RFC issue #323 |
join_all minor improvements
Add defaults and positional arguments for function/method/closure declaration/call in Rust.
This RFC proposal is written from the ideas and suggestions brought in the corresponding feature issue in Rust main repository rust-lang/rust#6973
Rendered view