rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.97k stars 1.57k forks source link

Wishlist: functions with keyword args, optional args, and/or variable-arity argument (varargs) lists #323

Open pnkfelix opened 10 years ago

pnkfelix commented 10 years ago

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.)

flying-sheep commented 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.

pnkfelix commented 10 years ago

@flying-sheep so then you deprecate such methods, just like today, no?

reem commented 10 years ago

@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.

flying-sheep commented 10 years ago

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)

Valloric commented 10 years ago

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.

sfackler commented 10 years ago

Rustdoc's handling of deprecated items definitely needs some improvement - see rust-lang/rust#15468 for some discussion.

aturon commented 9 years ago

See the "Struct sugar" RFC for another take.

gsingh93 commented 9 years ago

I'd like to see some of these RFCs revived in the near future, if someone has time to do so.

aldanor commented 9 years ago

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.

e-oz commented 8 years ago

Ok, 1.0 released, even more, can we please discuss it again? especially default arguments.

steveklabnik commented 8 years ago

This issue is open, it's free to discuss.

pnkfelix commented 8 years ago

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...)

yberreby commented 8 years ago

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?

ticki commented 8 years ago

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)
ticki commented 8 years ago

Also, this feature could easily be misused by taking named parameters instead of structs, which, I think, is a bad thing.

golddranks commented 8 years ago

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.

ticki commented 8 years ago

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.

yberreby commented 8 years ago

I agree, macros are appropriate for this.

jimmycuadra commented 8 years ago

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.

petrochenkov commented 8 years ago

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.

ticki commented 8 years ago

@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.

ticki commented 8 years ago

@petrochenkov The problem you outline is a problem with macros, not a need for method variadicity.

e-oz commented 8 years ago

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".

e-oz commented 8 years ago

Really frustrated to see such comments. All modern languages except Go have it, but Rust "don't need it, because macros". What a shame.

ticki commented 8 years ago

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.

e-oz commented 8 years ago

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.

ticki commented 8 years ago

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.

BurntSushi commented 8 years ago

Just a friendly reminder everyone: let's try to keep conversation civil and constructive! Thanks.

KalitaAlexey commented 8 years ago

Are keyword-based arguments must be mandatory for all or it is up to me?

ticki commented 8 years ago

@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.

KalitaAlexey commented 8 years ago

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?

e-oz commented 8 years ago

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?".

bombless commented 8 years ago

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.

bfops commented 8 years ago

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.

ticki commented 8 years ago

@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.

bfops commented 8 years ago

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?

ticki commented 8 years ago

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?

bfops commented 8 years ago

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.

KalitaAlexey commented 8 years ago

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?

ticki commented 8 years ago

@KalitaAlexey No, that's out of the scope of this issue.

KalitaAlexey commented 8 years ago

@ticki So why then have keyword-based arguments?

flying-sheep commented 8 years ago

@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…

ticki commented 8 years ago

I'll write up a RFC on default struct fields, which I believe solves both the problem with default parameters and named parameters.

bfops commented 8 years ago

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.

ticki commented 8 years ago

@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.

e-oz commented 8 years ago

@Ticki please stop. It's not thread "do we need it or not". If you don't need it - just don't write anything.

flying-sheep commented 8 years ago

i’m sorry but i have to agree with @e-oz. this discussion is misplaced. we’re not talking about structs here.

bfops commented 8 years ago

@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.

ticki commented 8 years ago

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.

ticki commented 8 years ago

@flying-sheep The issues are orthogonal, and one might be able to replace the other. Sorry for hijacking this thread.