Modern Dart Syntax (Dart 3.0 - 3.12+)
Leverage the latest syntax features introduced in Dart 3.x to write clean, concise, type-safe, and highly optimized code.
Contents
- Private Named Parameters
- Extension Types
- Wildcards for Unused Elements
- Records and Pattern Matching
- Primary Constructors
Private Named Parameters
Introduced in Dart 3.12
Dart now supports initializing private fields directly using named parameters in constructors. This removes the need for manual assignments in the initializer list.
Usage
- When declaring a named constructor parameter, prefix the private field name with
this.: - The compiler automatically strips the leading underscore (
_) at the call site.
class User {
final String _name;
final int _age;
// Initializing private fields directly
User({
required String this._name,
required int this._age,
});
String get name => _name;
int get age => _age;
}
void main() {
// Call site does NOT include the underscore
final user = User(name: 'Alice', age: 30);
print(user.name); // Alice
}
Extension Types
Introduced in Dart 3.3
Extension types are compile-time-only wrapper classes around an underlying representation type. They provide a zero-cost abstraction, meaning they do not allocate wrapper objects in memory at runtime, unlike standard classes.
Rules & Patterns
- Expose a new interface for an existing type without runtime allocation overhead.
- Declare using
extension type Name(RepresentationType value). - Use when designing performance-sensitive APIs, domain-specific ID wrappers, or when wrapping JS interop objects.
// Exposes a zero-cost wrapper around an integer
extension type Id(int value) {
// Add custom helper methods
bool get isValid => value > 0;
}
// Wrapping a list to restrict available methods
extension type IntList(List<int> list) {
int get head => list.first;
void printSummary() {
print('Length: ${list.length}, Head: $head');
}
}
void main() {
final myId = Id(42); // Zero overhead, compiled directly to 42
print(myId.isValid); // true
}
Wildcards for Unused Elements
Introduced in Dart 3.7
You can use the wildcard _ to denote unused elements. This prevents linter warnings and clearly signals to other developers (and AI agents) that a variable or import is intentionally ignored.
Wildcard Scopes
- Unused Parameters: Use
_(and__,___if nesting) in callbacks. - Unused Local Variables: Declare variables as
_if they are only needed to trigger side effects. - Unused Imports: Import a package as
_to run its initialization logic without binding names.
import 'package:init_library/init.dart' as _;
void processItems() {
final items = ['apple', 'banana', 'orange'];
// Wildcards in list mapping
final indexed = items.mapIndexed((index, _) => 'Item $index');
// Wildcard for unused exception variable
try {
performDangerousTask();
} catch (_) {
print('Failed to perform task.');
}
}
Records and Pattern Matching
Introduced in Dart 3.0
Records allow returning multiple values from a function without the boilerplate of a custom class. Pattern matching extracts those values or destructures objects cleanly.
Syntactic Best Practices
- Positional Records:
(double, double) getCoordinates() => (37.77, -122.41); - Named Records:
({String name, int age}) getDetails() => (name: 'Bob', age: 25); - Destructuring: Assign directly to matching structures:
// 1. Positional Record Destructuring
final (lat, lng) = getCoordinates();
// 2. Named Record Destructuring
final (name: userName, age: userAge) = getDetails();
// 3. Object Destructuring with Patterns
if (user case User(name: final name, age: > 18)) {
print('$name is an adult');
}
Primary Constructors
Introduced in Dart 3.12 (Experimental)
Primary constructors provide a concise syntax for defining classes with automatic field declarations and assignments, similar to other modern languages.
// Shorthand for immutable data structures
class Point(int x, int y);
// Equivalent to:
// class Point {
// final int x;
// final int y;
// Point(this.x, this.y);
// }