In Dart, what does it mean that generics are reified, and what can code do with a type parameter like T at run time?
answer
- type arguments survive compilation
- is List<String> actually works
- x is T inside the generic
- T as a Type value
- no T() and no static calls
basics
~20 sReified means every generic object keeps its type arguments at run time, so is List<String> checks, x is T tests and <T>[] literals work. T still cannot be constructed or used to call static members.
solid answer
~40 sDart keeps type arguments at run time: a `<String>[]` knows it is a `List<String>`, so `names is List<String>` is `true` and `names is List<int>` is `false`. Inside generic code `T` is a real value you can use: `value is T`, `value as T`, `<T>[]` to create a correctly typed list, and `T` itself as a `Type` for logging or comparison. `Iterable.whereType<T>()` relies on exactly this. What you still cannot do is call a constructor or static member through `T`, so `T()` and `T.fromJson(...)` do not compile; generic code takes a factory function instead. Flutter leans on reification too: `context.dependOnInheritedWidgetOfExactType<T>()` finds an ancestor by the runtime type `T`. Languages that erase type arguments, such as Java, cannot test `is List<String>` at run time.
code
dart · 23 linesclass Page<T> {
Page(this.items);
final List<T> items;
bool accepts(Object? value) => value is T;
Type get itemType => T;
List<T> emptyLike() => <T>[];
}
void main() {
final names = <String>['ada'];
print(names is List<String>); // true
print(names is List<int>); // false
final page = Page<int>([1, 2]);
print(page.accepts(3)); // true
print(page.accepts('3')); // false
print(page.itemType == int); // true
print(page.emptyLike() is List<int>); // true
final mixed = <Object>[1, 'two', 3];
print(mixed.whereType<int>().toList()); // [1, 3]
}go deeper
Recall that Dart keeps type arguments at run time, so is List<String> works.
Explain what you can do with T at run time, is T, as T, <T>[], and why T() and static calls through T are not allowed.
Use reification deliberately, for type-keyed lookups and typed collection creation, while passing factories for construction and parsing.
Weigh APIs keyed on runtime types against explicit registration, considering minified web builds and the readability of type-driven lookups.
## Reified versus erased A **generic type** such as `List<E>` is instantiated with a **type argument**, as in `List<String>`. A language can either keep that argument in the running program (**reified generics**) or check it at compile time and then drop it (**erasure**). Dart reifies: the dart.dev language tour says generic types *carry their type information around at runtime*. Java is the usual contrast; it erases type arguments, so a running Java program can ask whether an object is a `List`, but not whether it is a `List<String>`. ## What reification lets Dart code do | Operation | Example | Works because | |---|---|---| | test a generic type | `names is List<String>` | the list object records `String` | | test against a type parameter | `value is T` inside `Page<T>` | `T` is bound to a real type at run time | | cast to a type parameter | `value as T` | the cast is actually checked | | create a typed collection | `<T>[]`, `<String, T>{}` | the new object gets the real `T` | | read the type | `T`, `runtimeType` | `T` is available as a `Type` value | | filter by type | `items.whereType<T>()` | implemented with an `is T` test | A consequence worth remembering: a list's **runtime** type argument is the one it was **created** with, not the one of the variable holding it. A `List<Object>` variable can refer to a `List<String>` object, and `is List<String>` on it returns `true`. ## What reification does not give you Having `T` at run time does not make it a class reference: 1. **No constructor calls.** `T()` does not compile; the type parameter may not even have an unnamed constructor, or may be abstract. 2. **No static members.** `T.fromJson(json)` and `T.values` do not compile; Dart has no static access through type variables. 3. **No reflection on members.** Knowing `T` does not let you list its fields without `dart:mirrors`, which Flutter does not support. The standard Dart answer is to **pass behaviour in**: a `T Function(Map<String, dynamic>) fromJson` parameter, a factory closure, or a list of values. ## Where Flutter depends on it - `context.dependOnInheritedWidgetOfExactType<T>()` and `findAncestorStateOfType<T>()` walk the element tree comparing each ancestor against the runtime type `T`. - Packages that look things up by type, such as dependency-injection containers keyed by `T`, store and compare `Type` values at run time. - `whereType<T>()` filters mixed lists safely. None of these would work under erasure without passing an explicit type token. ## Static type versus runtime type argument Two different types are in play for every generic value: 1. The **static type** is what the compiler knows from declarations, such as the variable `List<Object> items`. 2. The **runtime type argument** is what the object was created with, such as `<String>['a']`. Assignments, method calls and inference work with the static type. `is`, `as`, `whereType` and type-keyed lookups work with the runtime one. Most surprising generic behaviour in Dart, such as a cast that fails although "all the elements are right", comes from the two differing, so when a check misbehaves, ask what the object was **created** as, not what the variable says. ## Runtime cost and caveats - Every generic object carries its type arguments, and `is`/`as` checks against generic types do real work. The cost is small but not zero, so avoid heavy `is List<Foo>` checks in hot loops. - The printed form of a `Type`, such as `T.toString()`, is fine for debugging but should not be used as a stable identifier; web builds can minify names. - The runtime type argument is fixed when the object is created. A `List<dynamic>` whose elements happen to all be strings is still not a `List<String>`, which is the source of a classic JSON-parsing bug. ## Summary - Dart keeps type arguments, so generic `is` checks, `is T`, `as T` and `<T>[]` behave as they read. - `T` is a type, not a class: no `T()`, no `T.staticMember`. - Pass factories or callbacks when generic code must create or parse values.
- Why does `T.fromJson(json)` not compile inside a generic Page<T>, and what do you do instead?A type parameter is a type, not a class reference, and Dart has no static or constructor access through type variables. Pass the behaviour in: a `T Function(Map<String, dynamic>) fromJson` parameter, called for each item. Callers supply `Order.fromJson` as a constructor tear-off.
- A `List<Object>` variable holds a list created as `<String>['a']`. What does `is List<String>` return?`true`. The check looks at the object's runtime type, which was fixed when the list was created as a `List<String>`; the variable's static type does not change it. The reverse also holds: a list created as `List<Object>` holding only strings is not a `List<String>`.
saying these in an interview costs you the question
- Dart erases type arguments at run time, like Java.
- Inside a generic class, T() creates a new instance of the type argument.
- value is T inside generic code always returns true.
- A List<dynamic> becomes a List<String> once all its elements are strings.
- T.values or T.fromJson can be called when T is bound to a suitable class.