dart-lang / dart-lang/language

const-by-default constructors

Open
#3,399 5 comments 13 reactions 0 assignees View on GitHub
enhanced-const
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.