Skip to content

Compiler should try to make everything const #1410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
feinstein opened this issue Jan 19, 2021 · 27 comments
Closed

Compiler should try to make everything const #1410

feinstein opened this issue Jan 19, 2021 · 27 comments
Labels
enhanced-const Requests or proposals about enhanced constant expressions feature Proposed language feature that solves one or more problems

Comments

@feinstein
Copy link

feinstein commented Jan 19, 2021

I mainly use Dart for Flutter and declaring Widgets as const brings performance improvements. On the other hand it's boring to type const all the time, or verifying which constructors are const, or even having to fix a constructor call that just got broken because it was declared as const and now can't be evaluated at compile time.

A linter warning helps to remember to use const, but actually using them isn't much comfortable.

It would be amazing if the compiler could automatically understand what can be promoted to const and optimize the code as const behind the scenes.

@feinstein feinstein added the feature Proposed language feature that solves one or more problems label Jan 19, 2021
@mateusfccp
Copy link
Contributor

What about the cases where you don't want const? What do you propose?

@feinstein
Copy link
Author

I tried to think about those cases but couldn't find any that I wouldn't want const. Could you give me some examples?

@mateusfccp
Copy link
Contributor

Any case where you want your classes to be separate instances and refer to different objects although with the same parameters.

@feinstein
Copy link
Author

Could you give me an example?

This sounds a bit like bad practice. The kind of thing Value Objects (built_value) try to solve.

@feinstein
Copy link
Author

Maybe we can have noconst for those cases?

@mateusfccp
Copy link
Contributor

Could you give me an example?

This sounds a bit like bad practice. The kind of thing Value Objects (built_value) try to solve.

In a OO context it is not a bad practice, although me myself never use it this way, as I usually program in a funcional way.

@lrhn
Copy link
Member

lrhn commented Jan 21, 2021

One example is var token = Object();
Here you want to create a new unique object that is not equal to any other object. If that became var token = const Object();, the object would be canonicalized and equal to any other Object() anywhere in the program.

Also, what about [], which creates a new mutable list. I could be const, but then it would be immutable.
And then that can occur deep inside another object: var foo = Bar(Baz(Qux([])));. That too can be const if [] is const, and won't be const if [] is not const. Should it?

I guess that's cheating because [] actually creates objects with different behavior depending on whether it's const or not.
If we restrict it to invocations of const constructors with only const arguments, then there wouldn't be a difference in behavior, only in identity.
We toyed with this during the development of Dart 2.0, where we made new optional, but found it too risky.
A small and subtle change deep in an object construction expression could change whether the entire expression is constant. If you actually wanted the expression to be constant, but someone changed const someConstant = 42; to final someConstant = 42; and you use someConstant in your expression, then it would just silently stop being constant, and you would likely never notice.
That's why you have to ask explicitly for const, not just to document that it's what you intent, but also to allow the compiler to recognize that it's what you intend and warn you when it stops being true.

@feinstein
Copy link
Author

feinstein commented Jan 21, 2021

I see... So could we have a shortcut for const? Like .Widget() or *Widget or &Widget or another leading character, just like _ works for private library members.

This could make things a lot more convenient and comfortable in Flutter. Also we would get a performance increase as more people won't ignore const for lazyness or readability/80 characters limit.

@jaddison06
Copy link

To offer my perspective on this within Flutter: I find const annoying when I'm rapidly prototyping w/ hot reload as I have to recompile to change something declared as const. For this reason I think automatically making everything const would be a pain, but it would definitely offer performance benefits in production.

@cedvdb
Copy link

cedvdb commented Feb 7, 2021

it would definitely offer performance benefits in production.

I've seen that being claimed from time to time. Does it really have a non negligible impact ? Has someone done some testing ? Maybe one of the command can while in to shine some light on that

@feinstein
Copy link
Author

Yes it does, specially for complex animations. Imagine you have to recreate a complex widget tree 60 times per second, with const you can optimize a lot of it.

@cedvdb
Copy link

cedvdb commented Feb 7, 2021

Have you tested it ? Is there somewhere I can see the results ? I'm not trying to be condescending, I just would like proof of those claims. In my tests which could have very well been wrong, I didn't see a difference so I assumed it was a myth and preemptive optimisation. However my tests might have been faulty as I tried that while learning flutter, so very newbie to the language.

@feinstein
Copy link
Author

I didn't, but I remember seeing something a while ago.

@jaddison06
Copy link

Just did a v small preliminary test with a few Text & Icon widgets, rebuilding the widget tree very fast. Bear in mind this was very minimal compared to the widget tree of a complete app. However, with Flutter's profile build I saw an average of about 5fps increase when using const. I'll see if I can write a more complete example later today.

@cedvdb
Copy link

cedvdb commented Feb 7, 2021

It would be nice to have the source @jaddison06 . I could see const having an impact at the top of the tree.
I had imagined the compilation process could have inferred by itself something is a const when it could.

@jaddison06
Copy link

I reckon my test is flawed for the same reason. As I said, I'll have a look at doing a complete example, and post the source of that.

@rrousselGit
Copy link

this is speculation. making widget const does not call its build method at a compile time

It doesn't need to.

The benefit of using const is that when animating a tree:

  • the memory allocation is performed only once
  • the widget builds only once

This is because even if its parent rebuild, a const widget will not rebuild. Since widgets are immutable, Flutter is able to know that this widget did not change and won't rebuild it.

So yes, using const for widgets can have a significant impact

@rrousselGit
Copy link

rrousselGit commented Feb 13, 2021

re: widget builds only once
this is same if you don't change its key

This is false.

Here's a demonstration:

void main() {
  runApp(
    StreamBuilder<void>(
      stream: Stream.periodic(Duration(seconds: 1), (_) {}),
      builder: (context, value) {
        return MyWidget();
      },
    ),
  );
}

class MyWidget extends StatelessWidget {
  const MyWidget();
  @override
  Widget build(context) {
    print('did build MyWidget');
    return Container();
  }
}

This snippet will print did build MyWidget every second.

Change the code to return const MyWidget(); and it will print did build MyWidget only once.

That means without const, we are rebuilding MyWidget (and its descendants, here Container, which itself uses other widgets) every second.

That's a significant cost.

@feinstein
Copy link
Author

Yes it does, specially for complex animations. Imagine you have to recreate a complex widget tree 60 times per second, with const you can optimize a lot of it.

this is speculation. making widget const does not call its build method at a compile time

Yes, that's why I am pointing out that const can make a difference, you just proved my point.

@eernstg eernstg added the enhanced-const Requests or proposals about enhanced constant expressions label Dec 1, 2021
@OlegBezr
Copy link

OlegBezr commented May 9, 2022

I might be late to the discussion but there are some tools that can help to avoid manually fixing const warnings and speed up prototyping. If using VSCode, you can add "editor.codeActionsOnSave": { "source.fixAll": true }, to the settings.json file. This would automatically apply all const fixes (and other fixes) whenever you save a file in VSCode. I think such tools eliminate all the inconvenience and there's no need to change the compiler.

P.S. Originally discovered the solution here: https://www.youtube.com/watch?v=_RGgJFCRyaM&list=WL&index=16&ab_channel=FunwithFlutter

@mraleph
Copy link
Member

mraleph commented May 9, 2022

@OlegBezr unconditionally applying const to all constructor invocations that can be const might change the behaviour of the program and consequently introduce hard to debug issues (if you rely somewhere on object identity).

@lrhn
Copy link
Member

lrhn commented May 9, 2022

For history, during the Dart 2.0 language design, we considered automatically making all expressions that could be constant also be constant. Examples included things like Duration(seconds: 1), which might as well be constant.

The reason we ended up not doing so was that the failure modes were scary, and that it wasn't clear how to handle list/map literals.

If you write MyPotentiallyConstantClass(<int>[1]), then that expression can be constant, but it changes the behavior of the list if it is. You get an immutable list, and then there would be syntax to ask that list to be non-constant. We might have to introduce MyPotentiallyConstantClass(new <int>[1]). Even worse, almost every list and map literal seemed like it would default to constant (because it could), and most of those are actually intended to be mutable.
So, we'd have to not default collection literals to constant, only constructor calls, and require you to write MyPotentiallyConstantClass(const <int>[1]) to ensure that the list, and therefore the entire expression, is constant.

With a rule like that, every time you forget to write that const, or make some other minuscule mistake deep in your large presumably-constant expression, you get a non-constant instead. There is no warning, all you'll see is that your program might run a little slower. Good luck debugging that!

A feature which an easy-to-hit failure case, and with almost zero ability for tools to to catch that failure (because it's still valid code with the same static type and at least the same capabilities), meant that we ended up preferring the current approach where you have to ask for something to be constant.
You can still forget to do that, but at least the mistake is at the top-level and hopefully slightly easier to find. If you have a deeply nested expression which cannot be constant, in an expression that you explicitly asked to be constant, you get a static error.

@feinstein
Copy link
Author

Very insightful, thanks for the clarification Lasse!

@cyberpwnn
Copy link

Why not autoconst only objects with all final literals and work your way up from there.

A(int, int) could be const
B(A, int) could also be const
C(List) cannot be const
D(C, int) also cannot be const because C is not const.

The pain of having to fix code during rapid prototyping is the problem. I'd rather have things that are obviously const be const for me without the warning and without the keyword. Sacrificing const in more grey areas is worth the price.

@lrhn
Copy link
Member

lrhn commented Oct 16, 2023

We considered making "potentially constant expression with all constant arguments/elements/types" be automatically constant.

The problem was collections. Should [1] be automatically constant?
(Obviously not, that would be immensely breaking.)

Should W([1]) be constant if W has a const W(List<int> _) constructor?
Well, it should if its arguments are constant. But [1] isn't constant. But it could be. But it shouldn't in general.

It was then considered too dangerous to make [1] constant automatically.

Maybe it would be more viable to say that in the context of a const constructor parameter, which obviosuly claims to be able to accept an immutable list, but it's still something that can easily go wrong if you make just a little mistake.

That is the main argument against implicit const:
If being constant is important, then it can't be left to being implicit, because then you won't notice if something isn't constant. Being implicit means it works either way.
If being constant is not important, why even bother.

So if you want something to be constant, you need to say that it's constant, so that the compiler can warn you if it isn't.
(Or you may actually want the ability to say that something "should be constant if possible", or even "should be as much constant as possible", in the places where that's needed, without it affecting the rest of the language. See #3399 for something like that. I suggest it should be a lint that you opt in to, because it can have false positives that you'd want to //ignore.)

@feinstein
Copy link
Author

feinstein commented Oct 16, 2023

@lrhn thanks for the thorough explanation, as usual, I think those are fair considerations and the lint solution should be enough.

@JustTheCoolest
Copy link

To offer my perspective on this within Flutter: I find const annoying when I'm rapidly prototyping w/ hot reload as I have to recompile to change something declared as const. For this reason I think automatically making everything const would be a pain, but it would definitely offer performance benefits in production.
@jaddison06

We could just have different compiler/IDE configurations for development and production modes, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhanced-const Requests or proposals about enhanced constant expressions feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests