Irbisa · Cheatsheet
Dart Type System in Depth
Top and bottom types, reified generics, variance, inference, FutureOr, extension types, Never
Dart has three top types:
Object?,dynamicandvoid. How do they differ, and why isObjectnot one of them?What is the
Nevertype, and when should a function returnNeverinstead ofvoid?// wrong: void; the call is an ordinary statement, flow continues past itvoid failVoid(String m) => throw StateError(m); // right: Never; the call does not returnNever fail(String m) => throw StateError(m);…What does it mean that Dart's type system is sound, and which type checks still happen at run time?
How does flow analysis decide what a null check or
istest promotes, and what cancels the promotion?void demo(Object value, String? path) { if (value is! String) return; print(value.length); // String for the rest of the body if (path == null) throw ArgumentError.notNull('path'); print(path.length); // String: the null branch cannot continue…Dart generics are reified, unlike Java's. What can you do with a type argument at run time, and what still can't you do?
import 'dart:convert'; void main() { final tags = jsonDecode('["a", "b"]'); print(tags.runtimeType); // List<dynamic>, fixed when the list was created…What is the difference between
x is Tandx.runtimeType == T, and why shouldruntimeTypestay out of your logic?import 'dart:convert'; class Animal {} class Dog extends Animal { void bark() {}…How does Dart's type inference work, and when does it silently fall back to
dynamic?import 'dart:convert'; class Animal {}class Dog extends Animal {}class Cat extends Animal {}…This code assigns a
List<Dog>to aList<Animal>and then adds aCat. Does it compile, and what happens when it runs?class Animal {}class Dog extends Animal {}class Cat extends Animal {} void addStray(List<Animal> animals) => animals.add(Cat()); void main() { final dogs = <Dog>[Dog()]; final List<Animal> animals = dogs; print(animals.length); addStray(animals); print(dogs.length);}What does the
covariantkeyword do on a parameter, and why shouldoperator ==keepObject other?class Animal {} class Dog extends Animal {} abstract class Handler { void handle(covariant Animal a); // overrides may narrow `a`…This parser has no
asanywhere, yet it crashes withtype 'Null' is not a subtype of type 'int'. Where did the cast come from?class User { final int id; final String name; User(this.id, this.name); factory User.fromJson(Map<String, dynamic> json) { final int id = json['id']; return User(id, json['name']); }} void main() { print(User.fromJson({'name': 'Ada'}).name);}Find the bug:
get<AuthRepository>()throws "AuthRepository is not registered" right after the app registered the repository.abstract interface class AuthRepository {}class HttpAuthRepository implements AuthRepository {} class Locator { final _services = <Type, Object>{}; void register<T extends Object>(T service) => _services[T] = service; T get<T extends Object>() => _services[T] as T? ?? (throw StateError('$T is not registered'));} void main() { final locator = Locator()..register(HttpAuthRepository()); print(locator.get<AuthRepository>());}Why can a
void Function(Object)be passed where avoid Function(Dog)is expected, while avoid Function(Dog)cannot fill avoid Function(Animal)slot?class Animal {} class Dog extends Animal { void fetch() {}}…Your
maxOf<T extends Comparable<T>>works forDateTimeand your ownMoney, butmaxOf<int>will not compile. Why, and what do you write instead?// F-bound: T must be comparable to its own kindT maxOf<T extends Comparable<T>>(List<T> xs) => xs.reduce((a, b) => a.compareTo(b) >= 0 ? a : b); class Money implements Comparable<Money> { final int cents;…What is an extension type, how does it differ from a wrapper class, and where does its guarantee disappear at run time?
extension type UserId(String value) { bool get isValid => value.isNotEmpty; // String toString() => ...; // error: conflicts with a member of Object} // `implements` exposes the representation's members…Find the bug: full phone numbers show up in the logs, although every log field goes through
mask.extension type Email(String value) {}extension type Phone(String value) {} // Masks personal data before a log field is written.String mask(Object? field) { if (field is Email) { return '***@${field.value.split('@').last}'; } if (field is Phone) { return '***${field.value.substring(field.value.length - 4)}'; } return '***';} void main() => print(mask(Phone('+77011234567')));What does this print, and what does it reveal about
FutureOr<T>as a type?import 'dart:async'; FutureOr<Object> load() => Future<String>.value('data'); Future<void> main() async { final raw = load(); print(raw is Future); final v = await raw; print(v.runtimeType); print(<FutureOr<Object?>>[] is List<Object?>);}A codebase full of
Map<String, dynamic>analyses clean and still crashes on casts. What dostrict-casts,strict-raw-typesandstrict-inferenceeach catch?# analysis_options.yamlinclude: package:flutter_lints/flutter.yaml analyzer: language: strict-casts: true # String name = json['name']; -> error…You are publishing a package whose main class is generic. How do you keep its public API type-safe for callers?
// wrong: unbounded; Cache<String, int?> makes `null` mean two thingsclass LooseCache<K, V> { final _m = <K, V>{}; V? get(K key) => _m[key]; // "absent" or "stored null"?}…