This has already been discussed. I will try to sum-up what was said. I also added two new points: #[non_exaustive]
and #[unstable]
arguments.
- This need to be opt-in, because of semver implication. The identifiers of the fields (just like identifier for struct fields) are part of the public API. A library author should be able to change the name of the parameter of a function that he didn’t intent to stabilize.
This can be easily solved by using curly braces at the declaration site to express the intent of the author to allow both positional and named usage.
Declaration:
// named argument + positional argument
fn f1{foo: i32, bar: usize} -> String;
// positional argument
fn f2(foo: i32, bar: usize) -> String;
Usage:
// f1 was declared with named argument syntax
let s1 = f1{foo: 18, bar: 21}; // named argument -> OK
let s1 = f1(18, 21); // positional argument -> OK
// f2 was declared with positional-only syntax
// let s2 = f2{foo: 18, bar: 21}; // doesn’t compile
let s2 = f2(18, 21); // positional argument -> OK
This also solve the issue of namespace clashed between struct Foo { ... }
and fn Foo { ... }
since current code will not "magically" be using the named argument syntax. A warning could be added against declaring both struct Foo {...}
and fn Foo(...)
.
- Open question: should the name of the argument be part of the type of the function? ie. should
fn f1{foo: usize}
share the same type as fn f2{bar: usize}
? What would be the syntax of a named function pointer, and the associated Fn
traits.
I think that both could be implicitly coercible into Fn(usize)
if a named function can also be called using positional syntax.
- This syntax doesn’t support calling a function with both positional + named argument
In python, you can specify if an argument is positional only, positional + named or named only:
def foo(x1, x2, /, y1, y2, *, z1, z2)
The above python declaration declares a function foo
with positional-only arguments x1
and x2
, positional or named arguments y1
and y2
, and finally named-only arguments z1
and z2
.
foo(1, 2, 3, 4, z1=5, z2=6) # x1=1, x2=2, y1=3, y2=4, z1=5, z2=6
foo(1, 2, x1=1, x2=4, z1=5, z2=6) # same thing
foo(1, 2, z2=6, z1=5, x2=4, x1=3) # valid -> named argument can be re-ordered
foo(x1=1, x2=2, x1=1, x2=4, z1=5, z2=6) # invalid -> x1 and x2 and positional-only
foo(1, 2, 3, 4, 5, 6) # invalid: z1 and z2 are named-only
- This syntax is compatible with (a possible future addition of) optional arguments. It could use the same syntax than what
struct
would get. Example of that kind of possible syntax:
struct Struct1 {
foo: usize = 1, // possible syntax to provide default with const initializer
bar: usize = 2,
}
let s1 = Struct1 { foo: 1, bar: 2 };
let s1 = Struct1 { .. } // use default specified by default field
let s1 = Struct1 { foo: 1, .. }; // use the default for bar
let s1 = Struct1 { bar: 2, .. }; // use the default for foo
struct Struct2 {
foo: usize, // no default
bar: usize = 2,
}
let s2 = Struct2 { foo: 1, bar: 2 };
// let s2 = Struct2 { .. } // invalid, foo must be specified
let s2 = Struct2 { foo: 1, .. }; // use the default for bar
// let s2 = Struct2 { bar: 2, .. }; // invalid, foo must be specified
#[derive(Default)] // could also be a manual implementation of `Default`
struct Struct3 {
foo: usize,
bar: usize,
}
let s3 = Struct3 { foo: 1, bar: 2 };
let s3 = Struct3 { ..default() } // use default specified by default field
let s3 = Struct3 { foo: 1, ..default() }; // use the default for bar
let s3 = Struct3 { bar: 2, ..default() }; // use the default for foo
fn func { foo: usize = 1, bar: usize = 2 };
func(1, 2); // positional
func(1, ..); // use default for bar
func(..); default for both foo and bar
func{foo: 1, bar: 2}; // named
func{foo: 1, ..}; // default for bar
func{bar: 2, ..}; // default for foo
func{..}; // default for both foo + bar
// note: it could also be func(..default()) or any other syntax if more explicitness is needed
- Open question: should argument re-ordering when calling a named-argument function be supported, just like reordering field when creating a structure is possible ?
struct Struct {
foo: usize,
bar: usize,
}
fn function {
foo: usize,
bar: usize,
}
let s = Struct { bar: 18, foo: 18 }; // valid
fn function{ bar: 18, foo: 18 }; // should this be valid?
For a language symmetry, it should be allowed, but I’m not sure it’s wise, since function(18, 21)
, looks a lot like function{bar: 18, foo: 21}
even if the arguments where swapped.
However, If optional arguments are added, it may make some sense to allow argument re-ordering:
fn func{foo: usize = 1, bar: usize = 2, baz: usize = 3}; // hypothetical syntax with named + optional argument
func{bar: 22, baz: 33, ...} // hypothetical syntax where we expand all the default
func{baz: 33, bar: 22, ...} // should this be rejected if the above is accepted?
- Open question: if optional arguments are added, should
#[non_exhaustive]
be supported to be able to add more arguments (assuming a default is provided) in a backward compatible way?
#[non_exhaustive]
fn func{foo: usize, bar: usize} -> String;
// calling code:
let f1 = func{foo: 1, bar: 2, ..};
// In a later version, `func` could become
#[non_exhaustive]
fn func{foo: usize, bar: usize, baz: usize = 3} -> String;
// which is semver compatible since the calling code doesn’t need to be modified
Could such function be used with a Fn
trait, or converted to a function pointer?
- Open question: if optional arguments + non exhaustive are added, could an argument with a default be "unstable", and thus not part of the stable API? (it’ just like non-stabilized function in std, than are hidden behind a flag)
#[non_exhaustive]
fn func{foo: usize, bar: usize, #[unstable] baz: usize = 3} -> String;
let s1 = func{foo: 1, bar: 1, ..}; // OK
let s2 = func{foo: 1, bar: 2, baz: 33, ..}; // KO: use of unstable argument
let s3 = func{foo: 1, bar: 2, #[unstable] baz: 33, ..}; // OK (note: syntax 100% bikesheddable)
// Note: a later version could become in a sem-ver compatible way
#[non_exhaustive]
fn func{foo: usize, bar: usize, #[unstable] foobar: usize = 3} -> String;
// or even
#[non_exhaustive]
fn func{foo: usize, bar: usize}
// In both case `s3` would be broken, but that’s ok since
// the user explicitly opt-in into a beta feature