Dart typedef and function types

typedef is the type alias mechanism in Dart, which allows you to define short, readable names for complex types.

This chapter introduces two uses of typedef: the traditional function type alias, and the generic type alias introduced in Dart 3.0.


typedef function type alias

When a function type is used frequently, typedef can give it a clear name.

This is particularly useful for scenarios such as callback functions and event handlers.

Example

// Define a function type alias
// Represents: a function that takes two int parameters and returns int
typedef IntOperation = int Function(int a, int b);

// Define a callback type
// Represents: a function that receives a String message and returns no value
typedef MessageCallback = void Function(String message);

// Use typedef as parameter type
int performOperation(int x, int y, IntOperation operation) {
  return operation(x, y);
}

void logMessage(String msg, MessageCallback callback) {
  print('Preparing to output message...');
  callback('[EXAMPLE] $msg');
}

void main() {
  // Pass in a function that matches the IntOperation signature
  int add(int a, int b) => a + b;
  int multiply(int a, int b) => a * b;

  print('10 + 5 = ${performOperation(10, 5, add)}');
  print('10 × 5 = ${performOperation(10, 5, multiply)}');

  // You can also directly pass in an anonymous function
  int result = performOperation(20, 4, (a, b) => a ~/ b);
  print('20 ÷ 4 = $result');

  // Use MessageCallback
  logMessage('Operation completed', (msg) {
    print('Received message: $msg');
  });
}
10 + 5 = 15
10 × 5 = 50
20 ÷ 4 = 5
准备输出消息...
收到消息: [EXAMPLE] 操作完成

Without typedef, you need to repeatedly write verbose function type signatures at every parameter position.

With typedef, type declarations become clear and unified, and modifications only need to be made in one place.

typedef is just an alias for a type; it does not create a new type. IntOperation and int Function(int, int) are completely equivalent in the type system.


Function type as parameter

In Dart, functions are first-class citizens and can be passed around like ordinary values.

Example

// Use function types directly to declare parameters (without typedef)
void processNumbers(
  List<int> numbers,
  bool Function(int) filter,
  String Function(int) formatter,
) {
  var filtered = numbers.where(filter);
  for (var n in filtered) {
    print(formatter(n));
  }
}

// A higher-order function that returns a function
int Function(int) makeMultiplier(int factor) {
  // The returned closure captures factor
  return (int n) => n * factor;
}

void main() {
  var scores = [55, 78, 92, 60, 45, 88];

  print('--- Passing Score ---');
  processNumbers(
    scores,
    (n) => n >= 60,                    // Filter condition
    (n) => 'EXAMPLE Score: $n points',       // Formatting
  );

  print('--- High Scores (> 80) ---');
  processNumbers(
    scores,
    (n) => n > 80,
    (n) => 'High Score: $n points',
  );

  // Function as return value
  var doubler = makeMultiplier(2);
  var tripler = makeMultiplier(3);
  print('5 × 2 = ${doubler(5)}');
  print('5 × 3 = ${tripler(5)}');
}
--- 及格分数 ---
EXAMPLE 分数: 78 分
EXAMPLE 分数: 92 分
EXAMPLE 分数: 60 分
EXAMPLE 分数: 88 分
--- 高分(> 80)---
高分: 92 分
高分: 88 分
5 × 2 = 10
5 × 3 = 15

Callback Pattern

The callback pattern is the most common application scenario for function types.

It delegates the control of "what to do" to the caller, making code more flexible and reusable.

Example

// Define callback type
typedef ResultCallback<T> = void Function(T result);
typedef ErrorCallback = void Function(String error);

// Simulate asynchronous operation
void fetchUserData(
  String userId, {
  required ResultCallback<Map<String, dynamic>> onSuccess,
  required ErrorCallback onError,
}) {
  // Simulate network request
  print('Fetching user data...');

  // Simulate success/failure
  if (userId == 'example') {
    var data = {
      'id': 'example',
      'name': 'EXAMPLE user',
      'level': 'VIP',
    };
    onSuccess(data);
  } else {
    onError('User $userId does not exist');
  }
}

void main() {
  // Use callback to handle result
  fetchUserData(
    'example',
    onSuccess: (data) {
      print('Fetch successful!');
      print('Username: ${data['name']}');
      print('Level: ${data['level']}');
    },
    onError: (error) {
      print('Fetch failed: $error');
    },
  );

  print('---');

  // Test failure scenario
  fetchUserData(
    'unknown',
    onSuccess: (data) {
      print('Fetch successful');
    },
    onError: (error) {
      print('Fetch failed: $error');
    },
  );
}
正在获取用户数据...
获取成功!
用户名: EXAMPLE 用户
等级: VIP
---
正在获取用户数据...
获取失败: 用户 unknown 不存在

Common application scenarios for the callback pattern:

ScenariosCallback Type Example
Network requestonSuccess(data) / onError(error)
UI eventonTap() / onLongPress()
Data conversionCallbacks in map(), where(), reduce()
TimerTimer(callback, duration)
Animation completedonComplete()

Although the callback pattern is flexible, it leads to "callback hell" when handling multi-layered asynchronous operations. Dart provides async/await to handle asynchronous flows more elegantly, which we will cover in detail in Chapter 17.


Dart 3.0 Generic Type Alias

Dart 3.0 extends the capabilities of typedef; now you can create aliases for any type, not just function types.

Example

// Dart 3.0: typedef can create aliases for any type

// Aliases for complex collection types
typedef JsonMap = Map<String, dynamic>;
typedef UserList = List<Map<String, dynamic>>;

// Generic aliases
typedef Result<T> = ({T data, String? error});

// Function type aliases (traditional usage)
typedef Validator<T> = String? Function(T value);

// Using type aliases
void processJson(JsonMap json) {
  print('Processing JSON: $json');
  print('Number of keys: ${json.length}');
}

void validateAndPrint<T>(T value, Validator<T> validator) {
  var error = validator(value);
  if (error != null) {
    print('Validation failed: $error');
  } else {
    print('Validation passed: $value');
  }
}

void main() {
  // Using the JsonMap alias
  JsonMap userData = {
    'name': 'example',
    'age': 10,
    'isVip': true,
  };
  processJson(userData);

  // Using the Result alias
  Result<String> successResult = (data: 'Operation successful', error: null);
  Result<String> errorResult = (data: '', error: 'Network connection timed out');
  print('Success: ${successResult.data}');
  print('Failure: ${errorResult.error}');

  // Using the generic Validator
  Validator<String> nameValidator = (value) {
    if (value.isEmpty) return 'Name cannot be empty';
    if (value.length < 3) return 'Name must be at least 3 characters';
    return null;  // null means validation passed
  };

  validateAndPrint('EXAMPLE', nameValidator);
  validateAndPrint('AB', nameValidator);
}
处理 JSON: {name: example, age: 10, isVip: true}
键的数量: 3
Success: 操作成功
失败: 网络连接超时
验证通过: EXAMPLE
验证失败: 名称至少 3 个字符

Dart 3.0's type aliases greatly reduce verbose type declarations, making code more concise and readable.

Type aliases (typedef) and the types themselves are completely equivalent at runtime; they are only compile-time "nicknames." This means you cannot use typedef to distinguish between two types that are structurally identical but semantically different—they are treated as the same type.

other extensions