跪拜 Guibai
← All articles
Frontend · Flutter · Android

Dart 3.13's Primary Constructors Are a Syntax Rewrite, Not Just Sugar

By 恋猫de小郭 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The new constructor syntax and accompanying lints mark a deliberate, tooling-enforced style shift across the entire Dart and Flutter ecosystem. Teams that ignore it will accumulate technical debt as official docs, packages, and examples converge on the new form, and the `final` parameter pitfall can break existing code on upgrade.

Summary

Dart 3.13's Primary Constructors let you declare constructor parameters and instance fields in the class header using `final` and `var`. Parameters without those modifiers remain ordinary constructor parameters and do not become fields, a deliberate design choice that keeps the constructor API explicit. The feature also adds a primary constructor body via `this { ... }` and a new initializer scope, preventing the syntactic cliff where adding a single log line would previously force a rewrite back to the full verbose form.

Alongside this, ordinary constructors can now be declared with `new()` and `factory()` instead of repeating the class name, removing a historical redundancy that complicated future plans for static extension members. The team chose to derive fields from constructor parameters rather than the reverse, arguing that constructor signatures are public API surfaces that need precise control over named/positional parameters, defaults, and const.

Six new lints and IDE assists actively push code toward the new style, signaling that this is not an optional edge syntax but the beginning of a major style migration across the Dart and Flutter ecosystem. Two compatibility pitfalls exist: `final` on function parameters may become a compile-time error, and existing methods named `factory` could be misparsed as factory constructors.

Takeaways
Primary Constructors use `final` and `var` in the class header to simultaneously declare constructor parameters and instance fields; plain parameters without these modifiers do not become fields.
A new primary initializer scope lets field initializers read ordinary constructor parameters, moving simple initialization logic out of the initializer list and next to the field declaration.
The `this { ... }` body and `this : assert(...)` initializer list prevent the syntactic cliff where adding a small amount of logic previously forced a full rewrite to the old verbose constructor form.
All generative constructors in a class with a primary constructor must redirect to it, because field initializers depend on the primary constructor always executing.
Ordinary constructors can now use `new()` and `factory()` instead of repeating the class name, a change driven partly by future static extension member plans.
Six new lints and IDE quick-fixes actively migrate code to the new style; the `unnecessary_type_name_in_constructor` lint marks the old `ClassName()` form as BAD.
Using `final` on a regular function parameter may become a compile-time error in Dart 3.13, and existing methods literally named `factory` could be misparsed as factory constructors.
Conclusions

The Dart team explicitly split the long-requested Data Class feature into two separate problems—concise field declaration and value semantics—and solved only the first, leaving `equals`, `hashCode`, and `copy` for later.

Choosing to derive fields from constructor parameters rather than the reverse was a pragmatic call: constructor signatures are public API, and auto-deriving them from fields would create ambiguity around parameter order, optionality, and defaults.

The `const new()` syntax looks jarring but is a logical consequence of decoupling constructor declarations from the class's textual name, a move that also unblocks future language features like constructors in extensions.

The syntactic cliff problem is real and underappreciated; a one-line logic change that forces a 20-line diff discourages incremental refactoring and pushes teams to over-engineer classes early.

Official lint-driven migration means this is not an opt-in experiment—it is a coordinated push toward a new canonical style, similar in weight to the null safety migration.

Concepts & terms
Declaring Parameter
A constructor parameter marked with `final` or `var` in a primary constructor that simultaneously serves as both a constructor parameter and an instance field declaration.
Primary Initializer Scope
A new scope in Dart 3.13 that allows field initializers inside a class body to read ordinary (non-declaring) primary constructor parameters, which was previously only possible inside the constructor initializer list.
Syntactic Cliff
A design problem where a small functional change (like adding a log line to a constructor) forces a disproportionate syntax rewrite, causing large diffs and discouraging incremental changes.
Generative Constructor
A constructor that creates a new instance of a class, as opposed to a factory constructor which may return a cached instance or a subtype. In Dart 3.13, all generative constructors in a class with a primary constructor must redirect to it.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗