3
votes

My code is pretty simple:

typedef GenericCallback = void Function<T>(T);

void main() {
  GenericCallback callback = <String>(String message) => printMessage(message);
}

void printMessage([String errorMessageReason]) {
  print(errorMessageReason ?? '');
}

And DartPad gives me this error at the word message in printMessage(message):

Error: The argument type 'String/*1*/' can't be assigned to the parameter type 'String/*2*/'.
 - 'String/*1*/' is from 'unknown'.
 - 'String/*2*/' is from 'dart:core'.

It looks like Dart is getting the reference from one String and not the other, how's this even possible?

1

1 Answers

4
votes

Since you've done typedef GenericCallback = void Function<T>(T) you need to provide a generic method which matches the signature as a callback. The tricky part here is, you are doing that but not in the way you think.

In this line it looks like you're trying to specify the type for the closure you've created:

GenericCallback callback = <String>(String message) => printMessage(message);

However, Dart's rules for naming generic type parameters are strange in that you can use the names of existing types as the name of the type parameter. In other words, the following lines are all functionally identical and will provide a similar error:

GenericCallback callback = <String>(String message) => printMessage(message);
GenericCallback callback = <T>(T message) => printMessage(message);
GenericCallback callback = <int>(int message) => printMessage(message);

These generic closures are all completely valid and even built-in types like int and String will be treated as the names for type parameters in the scope of the closure.

In order to fix your error, you'll want to change the type parameter String to have a different name that doesn't collide with a core type, and do one of the following:

  • Update your invocation of printMessage to cast message to a String, although this will fail if T isn't of type String when the closure is called.
GenericCallback callback = <T>(T message) => printMessage(message as String);
  • Update your typedef to expect a String parameter
typedef GenericCallback = void Function<T extends String>(T);

GenericCallback callback = <T extends String>(T message) => printMessage(message);

This is an easy mistake to make if you've come from a language which allows for template/generic specialization like C++. Keep in mind that, at least currently, you can't specialize a generic method or object and the generic type isn't assigned until the method is actually called or object is created.