Released · improving
Dart guide · 3/6
Dart has a sound static type system: a type the compiler infers also holds at run time. Null safety adds the distinction between values that can and cannot be null. This chapter covers null safety, collections, classes and constructors, records and patterns, and enums.
The everyday types are int, double, num (their supertype), String and bool. Every value is an object, even numbers. dynamic turns off static checking; use it sparingly.
Since Dart 3, null safety is always on. A variable can hold null only if its type ends with ?.
int count = 0; // cannot hold null
int? maybe; // can hold null (defaults to null)
int lengthOf(String? text) {
if (text == null) return 0;
return text.length; // promoted to String after the null check
}
void main() {
print(lengthOf(null)); // 0
print(lengthOf('dart')); // 4
String? input = 'abc';
print(input!.toUpperCase()); // ! asserts non-null (throws if wrong)
maybe ??= 10;
print(count + maybe!);
}The compiler follows null checks and narrows the variable's type (type promotion). Use ! only when you know more than the compiler; prefer ?? or an explicit check.
| Type | Characteristics | Literal |
|---|---|---|
List<E> | Ordered, allows duplicates | [1, 2, 3] |
Set<E> | Unique elements | {'a', 'b'} |
Map<K, V> | Values looked up by key | {'a': 1} |
Collections are generic. map and where return lazy Iterables; call toList() when you need a list.
void main() {
final numbers = <int>[5, 3, 8, 1];
numbers.add(10);
numbers.sort();
final evens = numbers.where((n) => n.isEven).toList();
final doubled = numbers.map((n) => n * 2).toList();
final total = numbers.fold(0, (sum, n) => sum + n);
print('$evens $doubled $total');
final visited = <String>{'Seoul'};
print(visited.add('Seoul')); // false: already present
final stock = <String, int>{'apple': 3, 'pear': 0};
stock['orange'] = 7;
stock.update('apple', (v) => v + 1);
for (final MapEntry(:key, :value) in stock.entries) {
print('$key: $value');
}
print(stock['missing']); // null: map lookups return V?
}Collections made with const [1, 2] or List.unmodifiable throw if modified.
Constructors can assign parameters straight to fields with this.field; Dart also has named, const and factory constructors and initializer lists. Names starting with _ are library-private.
class Point {
final double x;
final double y;
const Point(this.x, this.y); // const constructor
const Point.origin() : x = 0, y = 0; // named constructor + initializer list
factory Point.fromJson(Map<String, dynamic> json) {
return Point(json['x'] as double, json['y'] as double);
}
double get distanceSquared => x * x + y * y; // getter
Point operator +(Point other) => Point(x + other.x, y + other.y);
@override
String toString() => 'Point($x, $y)';
}
void main() {
const a = Point(1, 2);
final b = Point.fromJson({'x': 3.0, 'y': 4.0});
print(a + b); // Point(4.0, 6.0)
print(b.distanceSquared); // 25.0
}Every class defines an implicit interface others can implements. Dart 3 class modifiers (sealed, final, base, interface) control whether code outside the library may extend or implement a class.
A record is an anonymous, immutable group of values, handy for returning several values without defining a class. Positional fields are read as $1, $2, named fields by name, and records with equal fields are ==. Patterns check the shape of a value and destructure it in declarations, switch, if-case and for loops.
(double, double) minMax(List<double> values) {
var lo = values.first, hi = values.first;
for (final v in values) {
if (v < lo) lo = v;
if (v > hi) hi = v;
}
return (lo, hi);
}
({String name, int age}) loadUser() => (name: 'Jiwoo', age: 27);
void main() {
final range = minMax([3, 9, 1]);
print('${range.$1} to ${range.$2}'); // 1.0 to 9.0
final (lo, hi) = minMax([4, 2]); // destructuring
final (:name, :age) = loadUser(); // named-field destructuring
print('$lo $hi $name $age');
final Object value = [1, 2];
final text = switch (value) {
int n when n < 0 => 'negative',
int() => 'integer',
[var first, _] => 'two-element list starting with $first',
{'type': String type} => 'map of type $type',
_ => 'something else',
};
print(text);
final json = <String, Object?>{'id': 7};
if (json case {'id': int id}) {
print('id = $id');
}
}A sealed class can only be subtyped in its own library, so the compiler knows every subtype and checks that a switch covers them all. Enhanced enums can have fields, constructors and methods.
sealed class Shape {}
class Circle extends Shape { Circle(this.r); final double r; }
class Square extends Shape { Square(this.side); final double side; }
double area(Shape s) => switch (s) {
Circle(:final r) => 3.14159 * r * r,
Square(:final side) => side * side,
}; // leaving out a subtype is a compile-time error
enum Level {
low(1),
medium(5),
high(10);
const Level(this.weight);
final int weight;
bool get isUrgent => this == Level.high;
}
void main() {
print(area(Circle(1)));
for (final level in Level.values) {
print('${level.name} ${level.weight} ${level.isUrgent}');
}
}Dart's sound static types and null safety catch many errors at compile time. List, Set and Map are generic collections, and classes support this.field constructors, named constructors, and const and factory constructors. Records bundle values lightly, patterns destructure them, and combining sealed classes or enums with switch lets the compiler tell you when you missed a case.
0 comments
Sign in · Sign in to leave a comment.
Be the first to comment.