[Pre-RFC] named arguments

No. Struct { a : b }, a is a field name and b is a value. In Struct { a : b : c }, a is a field, b is a value, and c is a type. No ambiguity.

In this case, f(x: y) is currently a valid expression if y is a type and x is a value.

1 Like

You're forgetting about generics.

My point is that the syntax chosen already means something in the same position, and that meaning is not redundant.

What about a different syntax?

Something like:

fn x(pub a: i32) -> i32 {
  return a
}

x(a => 1);

Even better, we could say that named arguments take precedence over type ascription. The user can always add parentheses to override this. Type ascription is unstable, so I think this would be okay, especially since we should be able to emit a very informative error message (most named arguments would not be the same name as a type).

I also think that named arguments should be able to be declared other than in the first position, to aid partial transitioning to named arguments.

Why not just =(eq sign) ?

5 Likes

This current compiles:

fn foo<T: std::fmt::Debug>(a: T) {
    println!("{:?}", a);
}
fn main(){
    let mut a = 0;
    foo(a=5);
}

Nevertheless, the equal sign has an advantage that it is already chosen by println!.

fn main(){
    let mut a = 0;
    println!("{a:?}", a=5);
}
3 Likes

How about a compromise?

f(x := y)

Or just = with some back compat hack in the compiler (ā€œif a is in scope, then a = x is assignment, otherwise it is a named argumentā€). Itā€™s harder to implement, but user visible syntax should be more important than implementation corner cases.

1 Like

Or replace type ascription sigil with ~?

Is there any reason
foo.enumerate().collect(): Vec<_> vs
foo.enumerate().collect()~ Vec<_>

Why add as alternative? You already have a nice scheme for named parameters just use different keyword or combination (e.g. pub use) to enforce named-only args.

So having fn window(pub use Title: &str, pos: Position, dim: Dimensions). means you have to say window(title: "Title", Pos(0,0), Dim(100,100))

Most languages use = to assign a value to a named argument. If we can get away with that, it would be the best syntax IMHO. However, I think this rule would cause a lot of confusion! Consider:

let mut x = 5;

// Do something with x

foo(x=x)   // Now what's that?

Is this an assignment or a named argument?

That would not be a good idea. Types are already indicated in variable bindings as let a: type = ..., in functions, etc with the colon :. Having two symbols for the same meaning at different places would be extremely confusing.

pub use has not the correct semantic at all. Having both forms of named arguments would require two keywords or having an extra symbol that differentiates between the two. I'm not strictly against adding both forms if that's what people want, but it adds a bit more complexity to the language.

3 Likes

But isnā€™t there already the same distinction for structs?

For the definition of a struct after the colon comes a type and for the creation after the colon comes a value.

Now the same would apply for functions. For the definition of a function after the colon comes a type and for the application of function after the colon - the keyword argument - comes a value.

Well to be fair, assigning types using and struct values using : is already a bit confusing.

3 Likes

Struct definitions, patterns, and constructors are already defined such that the syntax isnā€™t ambiguous. The problem with function arguments is that function arguments are expressions, and expressions can already be of the form a : b.

Having function arguments suddenly not be expressions, but some new production that is an expression except where it isnā€™t would be a weird corner case, and Yet Another Thing that would have to be explained.

I mean, Iā€™d love named arguments with the name: value syntax, but if it comes down to that versus a simpler, more consistent grammar, Iā€™d rather the simpler, more consistent grammar.

name = value is problematic for similar reasons: it already means something else. Context-dependent rules suck, for the most part. Heck, people already have issues with the mustachioed insect operator (::<).

On the other hand, if you use a different syntax, there would then be three ā€œbind name to valueā€ syntaxes. If you change the type ascription syntax, you now have two ā€œspecify typeā€ syntaxes.

This is probably a no-win situation for some part of the languageā€¦

4 Likes

Ok, I haven't been aware about this one.

I don't think that using = would be that problematic, it's still quite intuitive, it's still different to variable setting by the let, and other languages are using it in the same way.

It would be somehow annoying that there's no consistency with the creation of structs, but perhaps the struct creation could support both, the colon and the = ?

Comparing it with Haskell, it never was a big issue for me that Haskell uses the = for the creation of records and their pattern matching.

Yes, it might be a bit of a language quirk, but adding a complete new syntax doesn't seem that much better.

I think the problematic part is that x=y is an expression in a same manner x+y is an expression. It evaluates to (). So foo(x=y) is the same as foo( () ).

The thing is which part of grammar is going to be more widely used? Type coercion or named parameters? I definitely think most code will use named/default params than type coercion/ascription, so it makes sense for named parameters to use the least noisy syntax and move type ascription to something else.

1 Like

You misunderstand: my complaint is with constructs that entail descriptions like "function arguments are expressions, except for type ascription, which in this context means something different." Every single exception or special case in the grammar is mental overhead. One thing I adore about Rust is how it keeps that sort of thing to a minimum. a: b, ::<, pub, and "paths are relative except when they aren't" are the only major ones that come to mind.

Two problems: one, that's not what's being proposed here; two, expr: Type is designed to look like let pat: Type, and const name: Type, and static name: Type, and name: Type (in function arguments), and name: Type (in struct definitions). If you change ascription to something else, you're still introducing a kink in the grammar.

5 Likes

But isn't assigning struct field a: 32 already a kink in the grammar that doesn't follow name: Type? I mean, because structs are as they are you can't not introduce a kink (i.e. foo(a := 4)), and it's a small price to pay for named/default parameters.

2 Likes

How about to disallow foo(a=5)? Is anyone really need ability to assign some variable inside function call? Is it hard to check whether we are in function call and forbid assignment?

1 Like

Hmm. So, things I like:

  • pub to ā€œcommitā€ to a name
  • defaulted type parameters (I know they are separate, but I think I want them)

Things I donā€™t like:

  • parsing ambiguities around foo(x: ...), not sure yet what is my preferred solution, but I like foo(x => value)

Things Iā€™m not sure about:

  • ā€œsometimesā€ positional, ā€œsometimesā€ by name, pub use ā€“ I donā€™t know how I feel about ā€œfine-grainedā€ control. Maybe we should just say you must match how function is declared (use a keyword if one is present, use positional otherwise).

All of this leads to me an alternate though. Maybe we can adopt the => sigil in all locations (instead of pub):

fn foo(x => T) { ... }// declare keyword-style argument

foo(x => value)

Seeing it written out makes me less sure if I like it, mostly because => is 4 characters whereas : is two.

1 Like