What about traits?
fn bar<F: TraitFoo>(pub f: TraitFoo = Foo::new()){}
What about traits?
fn bar<F: TraitFoo>(pub f: TraitFoo = Foo::new()){}
I guess it would be Fn(u32, u16, u8)
since it should be compatible with the positional form. But I am not sure what the implications would be. This is reaching the limit of my knowledge of Rust and the compiler
I added another alternative proposed in the discussion on the tracking issue. For better exposure I will paste it here too.
Add a special trait, for example Params: Default
and have syntactic sugar for invoking functions that have their last argument implementing Params
.
Also add syntactic sugar for automatically defining an anonymous params struct.
For example:
#[derive(Params, Default)]
struct ExplicitParams {
herp: u8,
derp: i8,
}
// Use this form when you have many parameters
fn explicit(durr: &str, named: ExplicitParams) { ... }
// Use this from when you only have few parameters
// The params struct is automatically defined for you which takes away a lot of the verbosity
fn implicit(foo: &str = "xy", bar = 1u8) { ... }
fn main() {
explicit("unnamed", derp = 2);
// desugared: explicit("unnamed", ExplicitParams { derp: 2, ..Default::default() })
implicit(bar = 2);
// desugared: implicit(AnonymousStruct { foo: "xy", bar: 2 })
}
This would also take care of default arguments at the same time!
Note that the ambiguity with type ascriptions only exists if the expression being ascribed consists of a single identifier (referring to a variable or constant), as an argument name is always a single identifier. Cases like
func(foo: ...)
But there is rarely a need to write a type ascription of a variable/constant, or a reason why one would do so by mistake. Since type ascription is not stable, the parser could be changed to carve out the special case of āidentifier followed by colon in argument positionā for keyword arguments, and parse as type ascription in all other cases.
Type ascription can do two things: affect type inference of the value on the left, and perform implicit coercions. For the former, since the type in question is the variableās type, it would make much more sense to just put it on the variable declaration. The latter will usually happen automatically. There are exceptions, like if @ubsanās example is changed to use a variable:
fn print<T: std::fmt::Debug>(t: T) { println!("{:?}", t); }
fn main() {
let r: &i32 = &0;
print(r: *const i32);
}
ā¦but I donāt think theyāre common at all. (By the way, current nightly doesnāt like this example with #![feature(type_ascription)]
- it errors with a type mismatch - but based on the RFC I think it should be allowed.) If you encountered such an ambiguity, you could fix it just by surrounding the whole thing in parentheses ā print((r: *const i32))
ā or alternately by changing :
to as
(though that might produce a warning).
Also, there arenāt that many implicit coercions, and with the raw pointer ones, the parser could have a special diagnostic: *mut
and *const
cannot be the start of an expression, so the fragment func(identifier: *mut
or func(identifier: *const
could produce an error explicitly stating that parentheses are required. (Or it could just automatically disambiguate as an ascription, though thatās probably more confusion than itās worth.) Similarly, the current parser error that tends to crop up if you write a generic type where itās expecting a value:
error: chained comparison operators require parentheses
--> <anon>:6:18
|
6 | foo(&Iterator<i32>);
| ^^^^^^
|
= help: use `::<...>` instead of `<...>` if you meant to specify type arguments
could be changed to note cases where a type ascription might have been intended instead of a keyword argument.
I donāt have a concrete suggestion right now, so some thoughts:
pub
notation or not - it kind of makes sense, but is also something of a stretch. In particular pub(restricted)
makes no sense in this context.(Once again, I also which we could distinguish between values and types syntactically - it would make life so much easier, but probably uglier).
Iām against using :
because of the parsing ambiguity mentioned. There are similar issues with =
, and something like foo(x => y)
seems unappealing.
Has anyone mentioned using anonymous structs? If you just allow anonymous structs, like we can write (x, y, z)
instead of Type(x, y, z)
, in both pattern and expression position, then the API can be
// Definition
fn free_fall({z0, v0, g}: {f64, f64, f64}) { ... }
// Call
free_fall({z0: 100.0, v0: 0.0, g: 9.81});
I would add two extensions, but they arenāt needed for a first prototype:
Let the function definition put the types inside the anonymous struct:
// old
fn free_fall({z0, v0, g}: {f64, f64, f64}) { ... }
// new
fn free_fall({z0: f64, v0: f64, g: f64}) { ... }
Just like you can āautomaticallyā unpack a pattern with let Type { x, y, z } = ...
rather than let Type { x: x, y: y, z: z }
, make it possible to automatically pack with Type { x, y, z }
as an expression to mean Type { x: x, y: y, z: z }
. This prevents the only case I actually consider ugly:
// old
http.get("https://www.rust-lang.org/en-US/", {timeout: timout});
// new
http.get("https://www.rust-lang.org/en-US/", {timeout});
This proposal requires very little new. You miss out on the āseamless upgradeā, but I think this claim is seriously overestimated in importance. Right now, people write builders when named arguments would make sense, and thus most APIs donāt need to be upgraded. When youāre writing a function, itās generally clear whether it would benefit from named arguments.
Itās worth noting that this gives a clear path to defaults, by providing syntax sugar for
http.get("https://www.rust-lang.org/en-US/", {..HTTP_GET_DEFAULTS});
Note that because these are anonymous types, one can reasonably allow HTTP_GET_DEFAULTS
to be a āsubtypeā of the actual default arguments.
What if we made the user write parentheses in this case? The call would become print((&0: *const i32))
.
Some more thoughts and a sort-of proposal:
:
for specifying the actual argsSo, my sort-of proposal (which is similar to some other suggestions above) is to separate named arguments into their own block, indicated with braces and which comes after other arguments in both declaration and use. The correspondence with anonymous structs should be obvious, but I am not proposing adding these to the language in general.
Examples:
// Named and un-named.
fn foo(x: i32, { y: i32, z: i32 }) { ... }
foo(42, { z: 0, y: -1 });
// Only unnamed
fn bar({ x: i32 }) { ... }
bar({ x: 42 });
// Defaults:
fn foo(x: i32, { y: i32 = 32, z: i32 }) { ... }
foo(42, { z: 0 });
Downsides:
fn foo(x: i32, {}}
mean?Parsing:
{ ... }
as a pattern, and if there is no type following, treat it as a ānamed argument blockāFunction types:
I think the syntax extends easily to function types, but only if we allow anon structs outside of function calls. Without that, I donāt see a āniceā way forward. But we could do something hacky with named structs and an attribute or something + some extra sugar for the ()
generics notation.
Random thought:
I wonder if we could use a similar syntax (anon struct) in the declaration of trait fields (RFC 1546). That syntax with struct
was suggested in the discussion thread there.
That seems no better than saying "don't use type ascription in function calls". If we do this in terms of anonymous structs, it makes sense to disambiguate the same way we disambiguate (x)
from (x,)
: require writing {x: y,}
instead of {x: y}
.
I think it is different (though reasonable people could differ on how different):
The trouble with the disambiguating comma is that we currently use it to inform the parser that the surrounding expression should be treated as a tuple rather than a delimited expression. However, doing this for anon structs feels like the wrong default - it seems like using type ascription in a block in a function call would be super-rare, so making that the default and requiring a comma for the far more common operation of naming an arg, seems backwards.
What about just requiring a keyword to begin the named arguments. Then, we can reliably switch context.
fn foo(x: i32, use y: i32, z: i32) { ... }
foo(42, use y: -1, z: 0);
Iām not a fan of use
on the basis that itās doing something āunnaturalā relative to what it currently means, but it does solve the parsing (both compiler and human) issues, as far as I can see. Or, we could steal a trick from varargs:
fn foo(x: i32, ..{y: i32, z: i32}) { ... }
foo(42, ..{y: -1, z: 0});
This could also later be adapted for āexplodingā tuples for varargs. Combine with variadic tuples and anonymous structs, and youāve got something pretty extensible and powerful. This is a little ugly, though, and might warrant some additional shortcut:
fn increase(..{by: i32}) { ... }
increase(..{by: 3});
I have no idea what this would mean for types.
I assume that should be a :
not a =
?
it should yes, Iāll edit
Y'know, it could be made to make sense. What if pub
on arguments was only required to use them as keyword arguments from outside the module? Within a module, it would be allowed freely, since that doesn't create the same backward-compatibility hazard - which also means reduced clutter, since non-pub
fns would never need to use pub
on their arguments.
Edit: Unless that's what everyone was assuming anyway. (I read the original RFC as always requiring pub
for keyword arguments.)
Currently I donāt have a significant need/desire for named arguments in Rust (but perhaps Iād like default arguments).
Named arguments were discussed many times for D language, but in the end they were refused (even if in D the syntax for type ascription is different and doesnāt clash the most natural syntax for named arguments).
Ada language uses the ā=>ā syntax:
Foo (Arg_2 => 0.0, Arg_1 => 1);
Here you see some alternative syntaxes: https://rosettacode.org/wiki/Named_parameters
In Scala: http://docs.scala-lang.org/sips/completed/named-and-default-arguments.html
To face the problem of freezing argument names in the API, Scala uses this:
def inc(x: Int, @deprecatedName('y) n: Int): Int = x + n
See: http://lampwww.epfl.ch/~hmiller/scaladoc/library/scala/deprecatedName.html
Named arguments is not near the top of the list of features I miss in Rust.
Of all the options presented so far, I strongly prefer using func(x: y)
as the call syntax, which as @comex points out, could be done by having the parser recognize an identifier followed by a colon as a named argument, and otherwise parse as an expression. To disambiguate the example, I would probably write print((r): *const i32)
instead of print((r: *const i32))
, though obviously both would work.
I find this idea intriguing. (I read the RFC the same way you did.)
What if you want
struct A{
a: u8,
b: bool
}
fn foo(pub A{a:c, b:d}) -> u16{1}
In wich ways can you call it?
foo(A{a:10, b:true});
//or
foo(A{c:10, d:true});
//or even
foo(A{a:10, c:true});
Or are patterns forbidden for named arguments?
It is already possible by:
fn foo(A { a, b }: A) -> u16;
I mean do you have to use the struct field names (a,b) or the pattern bind names (c,d) as names for the arguments?
You can https://is.gd/CTZRZp
struct A{
a: u8,
b: bool
}
fn foo(A { a: c, b: d }: A) -> u16 {
println!("{:?} {:?}", c, d);
42
}
I believe Swift has a shorthand for this case (_ x
or something). I'm pretty well persuaded that one ought to separate the "external name" from the "internal name" -- I found the increment example alone pretty persuasive. Maybe it's just because I like smalltalk. I also like that it provides a natural form of opt-in.
Regarding the parsing ambiguities, I definitely agree that foo(x: y)
is the natural syntax. It's unfortunate that it collides with ascription though -- I really don't want to require parentheses to disambiguate, that is basically always regrettable.
I am wondering though if changing ascription syntax isn't a better choice. =) It's isn't stable, after all. Just something to consider.