While discussing C++ overloading on Zulip, a bunch of us (re?)discovered a pretty cute idea, which I'm taking the liberty to flesh out here.
This RFC comes from the realization that "we have named arguments at home" and "we have function overloading at home":
- Named arguments at home:
struct MethodArgs {
a: u32,
b: Option<String> = None, // note the default field value here
c: bool,
}
impl Bar {
fn method(&self, args: MethodArgs) { /* whatever */ }
}
bar.method(MethodArgs { a: 42, c: true, .. }); // using the `..` default field value syntax
- Function overloading at home:
trait MethodArgs: std::marker::Tuple {
fn call_method(self, this: &Foo);
}
impl MethodArgs for (i32, String) {
fn call_method(self, this: &Foo) { /* actual code */ }
}
impl MethodArgs for (i32,) {
fn call_method(self, this: &Foo) { /* actual code */ }
}
impl Foo {
fn method(&self, args: impl MethodArgs) { args.call_method(self) }
}
foo.method((42,));
foo.method((42, "asdf".to_owned()));
Proposed feature
This RFC proposes a .. syntax when declaring function arguments, which we can apply to our examples above as follows:
impl Bar {
fn method(&self, ..args: MethodArgs) { ... }
}
bar.method(a: 42, c: true, ..);
impl Foo {
fn method(&self, ..args: impl MethodArgs) { args.call_method(self) }
}
foo.method(42);
foo.method(42, "asdf".to_owned());
The syntax is ..ident: Type in function arguments, and is called "splatting". It is only allowed on the last argument of a function. It is allowed if Type is a struct or if it implements std::marker::Tuple. This then changes the call syntax of the function.
Struct case
struct Args { /* whatever */ }
fn foo(x: T, y: U, ..args: Args) { /* whatever */ }
foo(<expr1>, <expr2>, x: <expr3>, y: <expr4>)
// actually means, in the normal call syntax:
foo(<expr1>, <expr2>, Args { x: <expr3>, y: <expr4> })
foo(<expr1>, <expr2>, x: <expr3>, y: <expr4>, ..)
// actually means, in the normal call syntax:
foo(<expr1>, <expr2>, Args { x: <expr3>, y: <expr4>, .. })
Tuple case
trait FooArgs: Tuple { /* whatever */ }
fn foo(x: T, y: U, ..args: impl FooArgs) { /* whatever */ }
foo(<expr1>, <expr2>)
// actually means, in the normal call syntax:
foo(<expr1>, <expr2>, ())
foo(<expr1>, <expr2>, <expr3>)
// actually means, in the normal call syntax:
foo(<expr1>, <expr2>, (<expr3>,))
foo(<expr1>, <expr2>, <expr3>, <expr4>)
// actually means, in the normal call syntax:
foo(<expr1>, <expr2>, (<expr3>, <expr4>))
Related work
There are a large number of named arguments proposals on this forum. The one that looks closest to this one is Struct sugar .This is also quite related to Pre-RFC v2 - Static Function Argument Unpacking . Haven't looked into function overloading.
Possible extension
I propose that, if a function uses splatting, then foo(<expr1>, <expr2>, ..<expr3>) is allowed if expr3 has exactly the expected type, and skips the splatting. E.g. with my two running examples above, we could also write this:
let args = MethodArgs { a: 42, c: true, .. };
bar.method(..args); // passes the struct directly
let args = (42, "asdf".to_owned());
foo.method(..args); // passes the tuple directly
I do not propose that this syntax should be allowed on functions that don't use splatting, but I know such a thing has been proposed before (e.g. Pre-RFC v2 - Static Function Argument Unpacking).
Actually
..expris already a half-open range. We should probably use...instead...