Open pnkfelix opened 10 years ago
Nonetheless, We believe we produce a reasonable 1.0 version of Rust without support for this.
depends on how you see it. the API will definitely be well-considered given the constraints of no default/kw arguments.
but once those are added, i’m sure the API will be considered lacking, especially in areas where new defaults render old functions obsolete.
a good example (if the new syntax sugar wouldn’t exist) would be ImmutableSlice
:
fn slice(&self, start: uint, end: uint) -> &'a [T];
fn slice_from(&self, start: uint) -> &'a [T];
fn slice_to(&self, end: uint) -> &'a [T];
slice_from
and slice_to
will be immediately obsolete once you can just leave out start
or end
from slice
. i bet there are hundreds of examples more.
@flying-sheep so then you deprecate such methods, just like today, no?
@pnkfelix I think his argument is that we don't want to be stuck with a ton of deprecated cruft in the stdlib, but I'm still personally not too sympathetic to the need to have default arguments before 1.0.
yeah, that’s my argument. either cruft, or lengthy deprecation formalities and rust 2.0 a year after 1.0 (semver requires a major version bump for breaking changes)
While I'd prefer to have optional/keyword args before 1.0, I believe the problem with deprecated functions crufting up the API can be substantially lessened by removing such functions from the generated API docs. This is how the Qt project (and D AFAIK) handles API deprecation; the deprecated stuff continues working but developers writing new code don't see it.
Of course, the generated docs should have a setting/link/button to show the deprecated API items but it should be off by default.
I think this is also a good idea in general; just a couple of days ago I accidentally used a deprecated function because it seemed like a good pick and I didn't notice the stability color.
Rustdoc's handling of deprecated items definitely needs some improvement - see rust-lang/rust#15468 for some discussion.
See the "Struct sugar" RFC for another take.
I'd like to see some of these RFCs revived in the near future, if someone has time to do so.
Agreed, there's a whole bunch of different keyword arguments proposals floating around and there's been a few discussions which seemed to die off a few months ago... would love to hear the current standpoint on this.
Ok, 1.0 released, even more, can we please discuss it again? especially default arguments.
This issue is open, it's free to discuss.
This issue is open, it's free to discuss.
(though its possible an https://internals.rust-lang.org post might be a better UI for undirected discussion ... we didn't have the discuss forums when we set up these postponed issues...)
I'd love to see keyword arguments. I opened a thread on /r/rust with some comments about them before finding this issue. I guess /r/rust is an appropriate place for "undirected discussion" too?
In any case, this should be done in such a manner that it does not cause very inconsistent libraries, perhaps by letting the named parameters be optional? For example the names could be given by the argument name. Such that, the function:
fn func(a: u8, b: u8) -> u8;
can be called both with and without named parameters, for example:
func(a: 2, b: 3)
or something along this, while still being able to do:
func(2, 3)
Also, this feature could easily be misused by taking named parameters instead of structs, which, I think, is a bad thing.
I think it's because supposedly people are thinking about some kind of heterogenous variadicity (like the case of println, which is currently done with macros), and that isn't possible with arrays.
I see, but that's why we got macros. If you want heterogenous variadicity, you gotta go with macros, after all the Rust macro system is very powerful.
I agree, macros are appropriate for this.
When I was first learning Rust pre-1.0, hearing that keyword arguments were not going to be implemented for 1.0 seemed like a terrible choice. From a background in Ruby/Python/JavaScript, keyword arguments are a very common and natural way to create nice APIs.
However, after actually using Rust for a while, I've actually grown to like the struct/builder pattern much more than keyword arguments. The problem with making keyword arguments easy is that it encourages functions that do too much by taking a bunch of different options.
If, instead, you are restricted to positional arguments, then you're more inclined to keep functions small, with a smaller surface area for their signatures. If there a small number of variations on the behavior of a function, these can exist as different named functions (e.g. alternate constructors.) If there really are a large number of variables needed for a function, pulling those variables out into a dedicated struct provides basically all the benefits of keyword arguments, plus the possibility of reuse.
I think if keyword arguments are added, they should just be syntactic sugar for functions that take a struct argument.
If you want heterogenous variadicity, you gotta go with macros, after all the Rust macro system is very powerful.
And then you have to limit the length of your heterogeneous lists to ~10 to avoid horrible metadata bloat. Macros help, but it's still a half-measure.
@jimmycuadra I agree. Functions should be small and compact, however sometimes you want to be able to know the argument order without looking up the signature (which keyword arguments solve), even though structs and other type constructions should always be prefered over named parameters, when dealing with larger amount of inputs.
One thing that's important to me is consistency with the current call syntax (such that it's up to the caller whether to use named arguments or not). This can be achieved in the way I described in my previous comment.
@petrochenkov The problem you outline is a problem with macros, not a need for method variadicity.
Also, this feature could easily be misused by taking named parameters instead of structs, which, I think, is a bad thing.
No, using structs instead of named arguments is a bad thing. And using array instead of arguments is a horrible thing.
I agree, macros are appropriate for this.
For named arguments? Or for optional arguments? Writing macros for EACH function, export/import it - it's very over-verbose.
I think if keyword arguments are added, they should just be syntactic sugar for functions that take a struct argument.
No way. Better not implement it at all than this.
Functions should be small and compact
Let each user decide how their functions should look. Don't roll into Go-ideology "we don't have it because you don't need it and we know better".
Really frustrated to see such comments. All modern languages except Go have it, but Rust "don't need it, because macros". What a shame.
No, using structs instead of named arguments is a bad thing.
Not true. Structs are strongly typed (hence less prone to errors) and makes it possible to efficiently reuse codes. Your functions shouldn't take a large number of arguments anyway.
And using array instead of arguments is a horrible thing.
This is not true. Using arguments instead of arrays is a horrible thing.
For named arguments? Or for optional arguments? Writing macros for EACH function, export/import it - it's very over-verbose.
It not very often you run into lack of variadic functions, and when you do, you can write a macro, and when generic integers lands, you can just use arrays. Often functions takes a fixed number of arguments anyways.
Also, note that the macro syntax will soon be revised.
Let each user decide how their functions should look. Don't roll into Go-ideology "we don't have it because you don't need it and we know better".
One of Rust's design philosophies is that Rust should not lead the user into bad code, bugs, and logic errors (this is the reason why we don't have increments, for example). We're all lazy after all, but there is no reason to encourage being lazy.
Your functions shouldn't take a large number of arguments anyway.
I will decide it, not you.
Structs are strongly typed
function arguments too.
Often functions takes a fixed number of arguments anyways.
If your functions often take fixed number of arguments, all other users should obey?
One of Rust's design philosophies is that Rust should not lead the user into bad code
And again - what is bad code will decide user, not some very arrogant community members.
If your functions often take fixed number of arguments, all other users should obey?
I'm just saying that introducing syntactic sugar for such a small special case is not worth it. This job is better suited for macros.
And again - what is bad code will decide user, not some very arrogant community members.
Sure, user should not be forced to not writing bad code, but neither should they be encouraged to.
Just a friendly reminder everyone: let's try to keep conversation civil and constructive! Thanks.
Are keyword-based arguments must be mandatory for all or it is up to me?
@KalitaAlexey I'm not exactly sure what you're asking, but I supose you mean "Is it mandatory to provide the name of the parameters to the function, if we get keyword arguments". And the answer is no, because that would break backwards compatibility.
So. With keyword arguments there is should be possibility to write
fn addObject(object: Object, flag: bool);
fn addObject(object: Object, size: usize);
Is it?
Sure, user should not be forced to not writing bad code, but neither should they be encouraged to.
And there is no =
sign between "optional arguments", "named arguments" and "bad code". Maybe some personal opinions only. For somebody 3 arguments it's too much, for somebody it's ok. Nobody should be forced to ask "can I use 4 arguments in this function please?".
I guess this issue is about how to add these features properly or conclude that there is no proper solution, rather than debate if we support them or not.
After using OCaml for a few months, I'm a huge fan of keyword arguments. I have a lint for when keyword arguments are used positionally, and I'd be a fan of allowing something similar in Rust.
Edit: to clarify, this is a +1 for syntactically allowing keyword arguments to be used positionally, if we can also get an optional lint/warning of some kind for when this happens.
Just a friendly reminder everyone: let's try to keep conversation civil and constructive! Thanks.
+1. It's incredibly easy to start discrediting ideas when they come from a rude source.
If you want heterogenous variadicity, you gotta go with macros, after all the Rust macro system is very powerful.
@Ticki This is definitely how it is now, but I haven't seen an inherent argument for keeping it this way. That said, in my experience, variadic functions are few and far between, so I don't necessarily feel that the relatively incremental gain of variadic functions is worth the extra language and compiler complexity. The only difference to the user is an extra !
character. It is a little heavier on the implementation side, but not by a huge amount, and I've heard whispers of a macro system overhaul that might even make this nicer?
Let each user decide how their functions should look. Don't roll into Go-ideology "we don't have it because you don't need it and we know better".
@e-oz I agree with this. I feel it both condescending and incredibly frustrating when a language or library does not support something because in the past, the authors decided what a user should and shouldn't do. Obviously Rust enforces lots of things that users shouldn't do, but it's pretty good about giving escape hatches to users who say "no, I really do want to do this".
Functions should be small and compact, however sometimes you want to be able to know the argument order without looking up the signature (which keyword arguments solve)
@Ticki This is to me one of the huge benefits of keyword arguments. It's easier for not only the writer, but the reader of code later! It also makes it harder to accidentally pass same-typed arguments in the wrong order.
...even though structs and other type constructions should always be prefered over named parameters, when dealing with larger amount of inputs.
@Ticki I don't see why this is true? Structs and builders are a lot more boilerplate for the same functionality as keyword + optional arguments.
Also, this feature could easily be misused by taking named parameters instead of structs, which, I think, is a bad thing.
@Ticki One lovely place to use keyword arguments is in constructors. It means the struct internals can remain private, while having all the nicety of position-independent labeled struct creation.
The problem with making keyword arguments easy is that it encourages functions that do too much by taking a bunch of different options.
@jimmycuadra This is people using a useful tool for poor reasons, and I don't see it as a reason to keep the tool away from everybody else.
@bfops Great comment.
@Ticki I don't see why this is true? Structs and builders are a lot more boilerplate for the same functionality as keyword + optional arguments.
The reason I think struct should be prefered over named arguments is that:
You're able to define methods on struct. You cannot define methods on named parameters (as a collection), due to their "untyped" nature. One thing you might do is to begin to pass what should be a struct as parameters (since this is easy and ergonomical with named parameters).
That said, I'm not against nor supporting it, I'm just providing cases for/against the proposed features.
You're able to define methods on struct. You cannot define methods on named parameters (as a collection), due to their "untyped" nature. One thing you might do is to begin to pass what should be a struct as parameters (since this is easy and ergonomical with named parameters).
@Ticki You can define closures locally, which is essentially equivalent, no? And it only borrows the arguments it actually refers to - with a struct, you can't move/borrow individual members without moving/borrowing all.. I think?
You can define closures locally, which is essentially equivalent, no?
I'm not sure what you're trying to say here. Closures are just anonymous implementing a particular traits.
And it only borrows the arguments it actually refers to - with a struct, you can't move/borrow individual members without moving/borrowing all.. I think?
True.
I think you misunderstood my comment (and it's my fault, I'm horrible at wording my thoughts).
The individual parameters are obviously still typed, but the collection of arguments is not. This is not a problem when there's only 4 arguments. But it is when there are more, which is often the case when you used named parameters.
The nature of the parameters are the same in both cases (named or tuple parameters), but it gets problematic when there are many arguments.
Too many arguments is often a sign of using too few data structures, which, in worst case, can lead to spaghetti code (because of rewriting chunks of code which could just be defined as methods on your struct).
Not sure if any of that made sense?
Ah, so it sounds like what you're saying is not that when a function has many parameters it's necessarily better to group them all as a struct, but rather that it's likely that some of those arguments probably make sense to group into structs? I assumed you were talking about the builder pattern, where the parameters for a given function are put all into a single datatype, meant to be used with that one function.
I'm not sure what you're trying to say here. Closures are just traits.
By this I meant
struct FooParams {
x: int,
y: &'static str,
z: char,
}
impl FooParams {
fn bar() -> float { ... }
}
fn foo(params: FooParams) {
...
}
is roughly equivalent to
fn foo(x: int, y: &'static str, z: char) {
let bar = || {
...
};
...
}
The "methods" on the struct-of-parameters become closures over the relevant parameters.
I ask again. Currently there is no ability to declare two functions with same name. I cannot do
fn add(&mut self, object: Object);
fn add(&mut self, boxed_object: Box<Object>);
If we will support keyword-based arguments then it will be allowed?
@KalitaAlexey No, that's out of the scope of this issue.
@ticki So why then have keyword-based arguments?
@KalitaAlexey because that’s the most natural way to call a function with multiple optional arguments which can be confused in order. e.g. OpenOptions
:
instead of doing:
let f = OpenOptions::new()
// .read(true)
// .write(true)
// .append(true)
// .truncate(true)
// .create(true)
.open("path/to/file");
//or the shorthands:
let f2 = File::open("path/to/file2");
let f3 = File::create("path/to/file3");
the API could have been
let f2 = File::open("path/to/file2", read=true);
let f3 = File::open("path/to/file3", write=true, create=true, truncate=true);
although maybe File::create("path")
as alias for the latter would still have its place…
I'll write up a RFC on default struct fields, which I believe solves both the problem with default parameters and named parameters.
I don't feel that default struct fields address named parameters in satisfying way. Structs add a lot more boilerplate than "first-class" named parameters do. On the other hand, having named + default parameters gives us default struct fields "for free", since Foo::new
can now have named and default parameters.
@bfops I would argue that extending the struct builders for supporting default fields would make structs able to replace named parameters, because you'd then be able to leave fields out giving them a default value.
@Ticki please stop. It's not thread "do we need it or not". If you don't need it - just don't write anything.
i’m sorry but i have to agree with @e-oz. this discussion is misplaced. we’re not talking about structs here.
@Ticki You can already do that in a struct builder, by making the field an option, and initializing it when the builder is "invoked" to create the struct.
My argument is that structs and builders are a lot more boilerplate and heavily disincentivize using named parameters entirely, especially for small functions. As you've said, small functions are much more desirable and common, and making those functions pleasant is a huge priority for me personally.
You can already do that in a struct builder, by making the field an option, and initializing it when the builder is "invoked" to create the struct.
Then you get a performance penality and you'd have to handle the fallback value yourself.
@flying-sheep The issues are orthogonal, and one might be able to replace the other. Sorry for hijacking this thread.
A portion of the community (and of the core team) sees one or more of the following features as important for programmer ergonomics:
This issue is recording that we want to investigate designs for this, but not immediately. The main backwards compatibility concern is about premature commitment to library API's that would be simplified if one adds one or more of the above features. Nonetheless, We believe we can produce a reasonable 1.0 version of Rust without support for this.
(This issue is also going to collect links to all of the useful RFC PR's and/or Rust issues that contain community discussion of these features.)