Perhaps it might be a better idea to turn the need for specification in optional arguments around. Instead of being omitted entirely, placeholders are put in for arguments for which the default value should apply. A possible starting point:
In the function signature, arguments which are optional have, in their type signatures, default value assignments, which are used if the placeholder is specified. Here, the equals sign, followed by the value to use as a default, is used for this purpose, somewhat like an assignment statement.
fn increment(value: &mut i64, by: i64 = 1) {
// If a _ is put in for by, then 1 is used for by
*value += by
}
Then, in the caller, instead of being omitted entirely, a distinctive placeholder is used for arguments for which the default value should be used. Here, the placeholder used is _, in similar fashion to dummy values in patterns.
let mut target = 0;
increment(&mut target, _);
// _ is a placeholder, which is filled in with 1
// in accordance with increment's argument defaults
assert_eq!(target, 1);
If a different value is desired for an optional argument, it can be used instead of the placeholder at no additional syntactic cost.
increment(&mut target, 2);
assert_eq!(target, 3);
It makes sense to have optional arguments after mandatory arguments here, unlike in some other ideas for optional arguments, since the placeholders make explicit which specific arguments are omitted and are intended to be filled in with defaults.
fn f(a: i64, b: i64 = 4, c: i64) { /* irrelevant */ }
// In user code...
f(3, 4); // Wrong number of arguments
f(3, 4, _); // Optional placeholder in mandatory argument position
f(3, _, 4); // Okay, equivalent to f(3, 4, 4)
Changing a mandatory argument to be optional later is also backwards compatible (but changing the default value or changing the argument back to a mandatory one is still breaking).
// Much later...
fn f(a: i64, bee: i64 = 4, c: i64 = 6) { /* irrelevant */ }
// After updates...
f(3, 4, _); // Now this is okay, and is equivalent to f(3, 4, 6)
f(3, _, 4); // Still okay, and still equivalent to f(3, 4, 4)