Skip to content

const-by-default constructors #3399

Open
@rakudrama

Description

@rakudrama

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:

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.

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: <Widget>[
          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<Widget> 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhanced-constRequests or proposals about enhanced constant expressions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions