Note: For historical reasons, this feature is called “generic methods”, but it applies equally well to instance methods, static methods, top-level functions, local functions, and even lambda expressions.
Initially a proposal, generic methods are on their way to being fully supported in Dart. Here is how to use them.
When they were still being prototyped, an older comment-based syntax was designed so that the static analysis could be implemented and tested before the VM and compilers needed to worry about the syntax. Now that real syntax is allowed everywhere, this doc has been updated.
Type parameters for generic methods are listed after the method or function name, inside angle brackets:
/// Takes two type parameters, [K] and [V]. Map<K, V> singletonMap<K, V>(K key, V value) { return <K, V>{ key, value }; }
As with classes, you can put bounds on type parameters:
/// Takes a list of two numbers of some num-derived type [T]. T sumPair<T extends num>(List<T> items) { return items[0] + items[1]; }
Class methods (instance and static) can be declared to take generic parameters in the same way:
class C { static int f<S, T>(int x) => 3; int m<S, T>(int x) => 3; }
This even works for function-typed parameters, local functions, and function expressions:
/// Takes a generic method as a parameter [callback]. void functionTypedParameter(T callback<T>(T thing)) {} // Declares a local generic function `itself`. void localFunction() { T itself<T>(T thing) => thing; } // Binds a generic function expression to a local variable. void functionExpression() { var lambda = <T>(T thing) => thing; }
We do not currently support a way to declare a function as returning a generic function. This will eventually be supported using a typedef
.
You've seen some examples already, but you can use a generic type parameter almost anywhere you would expect in a generic method.
Inside the method's parameter list:
takeThing<T>(T thing) { ... } // ^-- Here.
Inside type annotations in the body of the method:
useThing<T>() { T thing = getThing(); //^-- Here. List<T> pair = [thing, thing]; // ^-- And here. }
In the return type of the method:
T itself<T>(T thing) => thing; //^-- Here.
As type arguments in generic classes and method calls:
useThing<T>(T thing) { var pair = <T>[thing, thing]; // ^-- Here. var set = new Set<T>()..add(thing); // ^-- And here. }
Note that generic methods are not yet supported at runtime on the VM and dart2js. On those platforms, uses of generic method type arguments are treated like dynamic
today. So in this example, pair
's reified type at runtime will be List<dynamic>
and set
will be Set<dynamic>
.
There are two places you cannot use a generic method type parameter. Both are because the VM and dart2js don‘t support reifying generic method type arguments yet. Since these expressions wouldn’t do what you want, we've temporarily defined them to be an error:
As the right-hand side of an is
or is!
expression.
testType<T>(object) { print(object is T); // ^-- Error! print(object is! T); // ^-- Error! }
As a type literal:
printType<T>() { Type t = T; // ^-- Error! print(t); }
Once we have full runtime support for generic methods, these will be allowed.
Most of the time, when you call a generic method, you can leave off the type arguments and strong mode's type inference will fill them in for you automatically. For example:
var fruits = ["apple", "banana", "cherry"]; var lengths = fruits.map((fruit) => fruit.length);
The map()
method on Iterable is now generic and takes a type parameter for the element type of the returned sequence:
class Iterable<T> { Iterable<S> map<S>(S transform(T element)) { ... } // Other stuff... }
In this example, the type checker:
List<String>
for the type of fruits
based on the elements in the list literal.String
for the type of the lambda parameter fruit
passed to map()
..length
, it infers the return type of the lambda to be int
.map()
as int
, and the resulting sequence is an Iterable<int>
.If inference isn't able to fill in a type argument for you, it uses dynamic
instead. If that isn‘t what you want, or it infers a type you don’t want, you can always pass them explicitly:
// Explicitly give a type so that we don't infer "int". var lengths = fruits.map<num>((fruit) => fruit.length).toList(); // So that we can later add doubles to the result. lengths.add(1.2);