dart-lang / dart-lang/language
const-by-default constructors
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
Today, a `const` constructor for class `Foo` can be invoked as `new Foo()`, `const Foo()` or simply `Foo()`.
In the last case, the invocation defaults to `new Foo()` unless the expression is in a `const` context, in which case it can only be `const`, so it defaults to `const`.
In the case of Flutter widget trees, the defaulting to `new Foo()` is the wrong choice. It would be better to default to `const Foo()` if at all possible.
There are several lints that try to help the developer work around the lack of const-by-default:
- [`prefer_const_constructors`](https://dart.dev/tools/linter-rules/prefer_const_constructors) to encourage the developer to put `const` at each constructor in the widget tree expression
- [`prefer_const_literals_to_create_immutables`](https://dart.dev/tools/linter-rules/prefer_const_literals_to_create_immutables) to encourage the developer to put `const` on some subexpressions even when the constructor is not `const`.
- [`unnecessary_const`](https://dart.dev/tools/linter-rules/unnecessary_const) to help the developer remove `const` from a const context.
If the developer has a `const` expression and changes some deep sup-expression to be non-const, the expression is now an error. The developer removes the high `const` to fix the error. The lints now encourage adding `const` all along the side-trees of the spine of the expression tree leading to the original edit.
### Example
As an example of this experience, copy the following program into dartpad.
```dart
import 'package:flutter/material.dart';
/// Flutter code sample for [Divider].
void main() => runApp(const DividerExampleApp());
class DividerExampleApp extends StatelessWidget {
const DividerExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Divider Sample')),
body: const DividerExample(),
),
);
}
}
class DividerExample extends StatelessWidget {
const DividerExample({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Column(
children: [
Expanded(
child: ColoredBox(
color: Colors.amber,
child: Center(
child: Text('Above'),
),
),
),
Divider(
height: 20,
thickness: 5,
indent: 20,
endIndent: 0,
color: Colors.black,
),
Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
'Subheader',
textAlign: TextAlign.start,
),
),
Expanded(
child: ColoredBox(
color: Colors.blue,
/*54*/ //color: Theme.of(context).colorScheme.primary,
child: Center(
child: Text('Below'),
),
),
),
],
),
);
}
}
```
Now use a computed color: uncomment line 54 and comment-out line 53.
You will see 7 errors for 'invalid const value'.
The remedy is to remove the `const` at line 26.
You will now see 9 lint warnings to prefer `const`.
The remedy is to add `const` in 4 places.
The lints have quick-fixes to help with this process, but pushing `const` around the code would be _completely unnecessary_ if the widget constructors defaulted to `const` whenever possible.
### Questions
Should const-by-default be the default behaviour? This would break `identical(Object(), Object())`. One can always 'escape' the behaviour with an explicit `new`, but this idea is probably too breaking.
If const-by-default is not the default behaviour, should the language add an opt-in syntax, e.g. putting the pseudo-keyword `prefer` in front of `const`, i.e.
```
class Foo {
final List children;
final Color color;
prefer const Foo({this.children = const [], this.color = Colors.black});
}
```
The opt-in could be via a class modifier: `const class Foo { }`. This makes all constructors const-by-default.
A class or constructor opt-in would nicest for Flutter - the preference for `const` is really an implementation detail of Flutter, so should be 'batteries included' as much as possible.
Should a const-by-default constructor that can't be `const` because of one argument make the other argument expressions be const if possible? e.g. should `Foo(color: computed(), children: [Foo()])` make the `children:` list be const?
Should a non-const constructor (or for that matter, a static method) be able to impose a const-by-default context on an argument by adding `const` or `prefer const` to the parameter declaration?
Another option would be to have syntax to introduce a const-by-default _context_, say, `const?`. In the big example, writing `return const? Center(...` would infect the whole expression with const-by-default. I think this is worse than the above ideas, mainly because the Flutter developer still has to do _something_.
Contributor guide
Assessment
This issue has not been assessed yet.