Skip to content

Reduced Boilerplate For Named Private Constructor Fields #4270

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
caseycrogers opened this issue Feb 19, 2025 · 2 comments
Closed

Reduced Boilerplate For Named Private Constructor Fields #4270

caseycrogers opened this issue Feb 19, 2025 · 2 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@caseycrogers
Copy link

caseycrogers commented Feb 19, 2025

Using private named fields in a constructor is very verbose as the type definitions and variable names need to be repeated and then the public variable name needs to be passed to the private variable:

class MyClass {
  MyClass({
    required SomeLongType<SomeLongGeneric>? foo,
    required String? bar,
  }) : _foo = foo, _bar = bar;

  SomeLongType<SomeLongGeneric>? _foo;
  String? _bar;
}

You can avoid a lot of this by not using named arguments, but named arguments are a useful readability tool.
You could give up on private fields and just make them public, but private fields are useful for a variety of reasons:

  1. You get null promotion on private attributes but not public ones
  2. Paired with getters and setters, you can control external visibility for API readability and safety (fully private, read public write private, write public read private, side effects on write)

This is especially relevant in Flutter code where named constructor arguments are pretty standard practice for widgets and you'll often have nullable arguments that manage control flow in the build function:

if (_maybeBar == null) return SizedBox.shrink();

return Column(
  children: [
    // Relying on null promotion from the early return.
    BarView(bar: _maybeBar),
    // More private field null promotion.
    if (_maybeFoo != null) FooView(foo: _maybeFoo);
  ].
);

In practice. using private fields with named constructors is so tedious that Flutter developers typically just use public fields for their widgets and either lose null promotion (and rely on ! which is brittle to refactors), or jump through hoops with locally scoped variables in the build func to retain null promotion:

// Create a locally scoped variable to get null promotion.
final bar = maybeBar;
if (bar == null) return SizedBox.shrink();

return Column(
  children: [
    // Relying on null promotion from the early return.
    BarView(bar: bar),
    // Have to use a guard clause to get an if-scoped null promoted reference.
    if (maybeFoo case final foo?) FooView(foo: foo);
  ].
);

So, I'd like some way to have private named attributes with less boilerplate. I don't have strong preferences for how, but by far the simplest approach feels like just allowing it:

class MyClass {
  const MyClass({
    // Not possible today, you get an analysis error that named arguments can't start with `_`
    required this._foo,
    required this._bar,
  });

  SomeLongType<SomeLongGeneric>? _foo;
  String? _bar;
}

void main() {
  // This is a little weird as now the private field name is exposed outside of the class,
  // but is that really so bad? Especially because it only happens when the dev
  // opts into it.
  // On some level I'd argue this is desirable-it communicates to the caller
  // that they're passing in a value that will henceforth be private.
  final myClass = MyClass(_foo: getFoo(), _bar: getBar());
}

Another approach might be some sort of shorthand syntactic sugar:

class MyClass {
  const MyClass({
    // `private` would be a keyword that tells the compiler that this is just a public proxy argument
    // for the correspondingly named private field. Probably we could come up with a less confusing
    // term to use for the keyword, this is just for sake of example.
    // This gets confusing if there are collisions with equivalently named public arguments and it feels
    // very "magical" so I'm not sure it's the best approach.
    required private this.foo,
    required private this.bar,
  });

  SomeLongType<SomeLongGeneric>? _foo;
  String? _bar;
}

void main() {
  final myClass = MyClass(foo: getFoo(), bar: getBar());
}

Ideally, whatever solution we use would also be compatible with the potentially upcoming primary constructors:

// Implicitly declares and initializes `_foo` and `_bar` private fields.
class MyClass({required SomeLongType<SomeLongGeneric>? _foo, required String? _bar});
@caseycrogers caseycrogers added the feature Proposed language feature that solves one or more problems label Feb 19, 2025
@caseycrogers caseycrogers changed the title Shorthand For Named Private Constructor Fields Reduced Boilerplate For Named Private Constructor Fields Feb 19, 2025
@mmcdon20
Copy link

I think this may be a duplicate of #2005.

@caseycrogers
Copy link
Author

Yes it's definitely a dupe, closing it as such, thanks!

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

No branches or pull requests

2 participants