dart-lang / language

Design of the Dart language
Other
2.66k stars 205 forks source link

Rethink `required` to be optional for non-nullable named parameters without default values #3206

Open Cat-sushi opened 1 year ago

Cat-sushi commented 1 year ago

There already have been several discussions such as #156, #878, #2050, and so on. But, I think it is good time to rethink required to be optional.

Supporse,

class const C({/* final */ required int i});

final can be inferred by the compiler, because otherwise it is an error. On the other hand, required is required! I feel it weird. Especially in context of primary constructors, terse syntax is justice. Specify primary constructors by eernstg · Pull Request #3023 · dart-lang/language

In addition, under migration to null-safe code, {int i} had to be decided/ marked whether nullable {int? i} or required {required int i} by users. Now, Dart 3 is fully null-safe and the migration tool has gone.

lrhn commented 1 year ago

If the request is to be able to omit required in primary constructors only, then we can discuss it.

For all other methods and constructors, the arguments from the existing issues still apply.

Omitting a required from a primary constructor would then be allowed if, well, not being required would be a compile-time error.

That would be a named parameter with no default value that:

The problem with being implicit is that it allows errors to propagate.

If you intended a parameter to be required, but its type ended up being nullable, the parameter is now optional. Someone starts using the constructor and omits the parameter. It's now a breaking change to add required to the parameter.

Or you intend a parameter to be optional, but accidentally erased its default value, and it's now required. You never intended that, but you get no warning because the parameter is just silently made required. All your tests happen to pass the parameter, because your test goes through a helper method like:

void testFoo(String name, Foo expectedResult, int arg1, {String arg2 = theDefaultValue}) {
  test(name, () {
    expect(Foo(arge, arg2: arg2), equals(expectedResult));  // arg2 always passed!
  }
}

so you never actually test that the default value is there.

Safety sometimes require redundancy.

Allowing you to omit required in every primary constructor is probably too much. Allowing it only for extension type, here there must always be precisely one required parameter, may be more reasonable. (If we allow named parameters in their primary constructors at all.)

I'm not sold.

(I'd much rather just make every parameter required if it has no default value. Then you must add = null to make nullable parameters optional, but we can remove both [...] and required syntaxes then. Bigger change, but if we are changing things, might as well do it properly!)

Cat-sushi commented 1 year ago

I knew default values are not parts of function signatures. But, removal/ change of default values is almost always breaking, even with nullable parameters, regardless of required. And, it's a responsibility of the function designer, but not of compiler. So, I don't worry about accidental removal of default values very much.