In Dart 3.13, what do primary constructors and the concise new and factory constructor forms change, and what breaks when a package adopts language version 3.13?
answer
- constructor and fields in the header
- var or final makes a field
- this block for list and body
- new name(...) inside the body
- final on ordinary parameters now errors
basics
~20 sDart 3.13 lets a class header declare fields and the main constructor, as in class Money(final int cents, final String currency);, and lets body constructors use new or factory without the class name. Adopting it makes final on ordinary parameters an error.
solid answer
~40 sPrimary constructors are a brevity feature with no new semantics. `class Money(final int cents, final String currency);` declares two final fields and the unnamed constructor that sets them; only parameters marked `var` or `final` induce fields. An initializer list or body goes in a `this : ... { ... }` block inside the class, `class const Money(...)` makes it const (with no body block), and `class Money._(...)` names it. In-body constructors can drop the class name: `new(...)`, `new origin()`, `factory parse(...)`. Upgrading the SDK constraint to `^3.13.0` has costs: `final` or `var` on any other parameter becomes an `extraneous_modifier` error, a class with a primary constructor cannot also have non-redirecting generative in-body constructors, and a method named `factory` with no return type now parses as a constructor.
code
dart · 16 linesclass Money._(final int cents, final String currency) {
this : assert(cents >= 0), assert(currency.length == 3);
factory parse(String text) {
final parts = text.trim().split(' ');
if (parts.length != 2) throw FormatException('bad money', text);
return Money._((double.parse(parts[0]) * 100).round(), parts[1]);
}
new euros(int cents) : this._(cents, 'EUR');
}
void main() {
print(Money.parse('4.20 EUR').cents); // 420
print(Money.euros(99).currency); // EUR
}go deeper
Recall that Dart 3.13 can declare a class's fields and main constructor in its header with var or final parameters.
Explain declaring versus ordinary parameters, the this block for initializer lists and bodies, const placement, and the concise new and factory forms.
Plan the upgrade: raise the SDK constraint, clear extraneous_modifier errors with dart fix, then convert value types where the header reads better.
Set a codebase convention for when primary constructors are preferred, so mixed styles do not become an inconsistent patchwork.
## What the feature is Dart 3.13, released on 2026-08-12, added **primary constructors**. The release notes call it a **brevity feature**: it adds no new runtime semantics, only a shorter way to write a class whose main constructor mirrors its fields. It is enabled per package by the **language version**, i.e. an SDK constraint lower bound of `3.13` (`sdk: ^3.13.0`). ```dart // Before class Money { final int cents; final String currency; const Money(this.cents, this.currency); } // Dart 3.13 primary constructor class const Money(final int cents, final String currency); ``` ## The pieces - **Declaring parameters.** A header parameter marked `var` or `final` induces an instance field of the same name. A parameter without either is an ordinary parameter and declares no field. - **The `this` block.** An initializer list and/or body lives in the class body as `this : assert(cents >= 0) { ... }` or, list only, `this : assert(cents >= 0);`. A class has at most one such block. - **Scopes.** In non-late field initializers and in the initializer list, a header name refers to the **parameter**; inside the body block, a declaring parameter's name refers to the **field**. - **`const`.** Written before the class name: `class const Money(...)`. A const primary constructor cannot have a body block — an initializer list ending in `;` only. - **Named primary constructors.** `class Money._(final int cents, final String currency);` makes the primary constructor private, a common base for public factories. - **Super parameters** work as usual: `class Price(super.cents, super.currency, final double taxRate) extends Money;` - An empty class body `{}` can be replaced by `;`. ## Concise in-body constructors Independently of primary constructors, 3.13 lets constructors in the body omit the class name: | Traditional | Concise | |---|---| | `Money(this.cents, this.currency);` | `new(this.cents, this.currency);` | | `Money.zero() : cents = 0, currency = 'EUR';` | `new zero() : cents = 0, currency = 'EUR';` | | `const Money.euro(int c) : this(c, 'EUR');` | `const new euro(int c) : this(c, 'EUR');` | | `factory Money.parse(String s) { ... }` | `factory parse(String s) { ... }` | Note there is **no dot** between `new` and the name. ## Restrictions 1. A class with a primary constructor **cannot declare other non-redirecting generative constructors** in its body (`non_redirecting_generative_constructor_with_primary`); extra constructors must redirect to it or be factories. This guarantees the primary constructor runs for every instance. 2. A primary constructor **cannot itself redirect**. 3. Declaring parameters **cannot be `late` or `external`**; declare such fields in the body. 4. Header parameters are **read-only** in initializers and the initializer list. 5. A field cannot be initialised **twice** — at its declaration and by the primary constructor. 6. The `this` block cannot be `async`, `async*`, `sync*` or use `=>`. ## What breaks on upgrade - **`final`/`var` on ordinary parameters.** Because these modifiers now mark declaring parameters, writing `void log(final String msg)` in a function, method, closure or ordinary constructor is a compile-time error, `extraneous_modifier`. `dart fix` removes them; teams that wanted immutable parameters switch to the `parameter_assignments` lint. On 3.12 or lower, the `avoid_final_parameters` and `var_with_no_type_annotation` lints find the modifiers before the upgrade. - **A method called `factory` with no return type** (`factory() {}`) now parses as a concise factory constructor; give it an explicit return type. ## Adopting it sensibly The feature pays off most on small value types, data carriers and enhanced enums. For classes with complex invariants, a traditional constructor with an explicit initializer list can stay clearer. Mixed styles are legal, so migration can be incremental: raise the language version, fix the `extraneous_modifier` errors, then convert classes where the header genuinely reads better.
- In Dart 3.13, how do you add a second generative constructor to a class that has a primary constructor?Make it redirect to the primary constructor, for example `new euros(int cents) : this._(cents, 'EUR');`, or make it a factory. A non-redirecting generative in-body constructor is an error when a primary constructor exists, because the language guarantees the primary constructor runs for every instance.
- In Dart 3.13, how do you give a primary constructor an assert or a body?Add a `this` block inside the class: `this : assert(cents >= 0);` for an initializer list only, or `this : assert(cents >= 0) { ... }` with a body. A const primary constructor may have the initializer list but no body block, and the block cannot be `async` or use `=>`.
saying these in an interview costs you the question
- Every primary constructor parameter becomes a field, with or without var or final
- Primary constructors add new runtime semantics such as automatic equality
- A class with a primary constructor can freely add other generative constructors
- Upgrading to language 3.13 leaves final on ordinary parameters as a harmless style choice
- Concise named constructors are written new.origin() with a dot