Dart Functions

A function is the basic unit for organizing code; it encapsulates a piece of reusable logic that can be called whenever needed.

Dart's function system is very flexible, supporting features such as named parameters, optional parameters, arrow functions, and higher-order functions.


Function Definition and Return Value

A function consists of a return type, a function name, a parameter list, and a function body.

Example

// Define a simple function
// String is the return type, greet is the function name, (String name) is the parameter list
String greet(String name) {
  return 'Hello, $name! Welcome to EXAMPLE.';
}

// Functions with no return value use void
void printWelcome() {
  print('=== EXAMPLE Dart Tutorial ===');
}

void main() {
  printWelcome();

  // Call the function and receive the return value
  String message = greet('Xiao Ming');
  print(message);
}
=== EXAMPLE Dart 教程 ===
你好,小明!欢迎来到 EXAMPLE。

Return Value

Every function has a return value.

If there is no explicit return, the function implicitly returns null (but under null safety this would cause a type mismatch, so void is usually used to indicate no return value).

Example

// Function that returns an int
int add(int a, int b) {
  return a + b;
}

// Use arrow syntax as shorthand for single-expression functions
int multiply(int a, int b) => a * b;

// void indicates no meaningful return value
void log(String msg) {
  print('[EXAMPLE Log] $msg');
}

void main() {
  print('3 + 5 = ${add(3, 5)}');
  print('3 × 5 = ${multiply(3, 5)}');
  log('Function learning complete');
}
3 + 5 = 8
3 × 5 = 15
[EXAMPLE 日志] 函数学习完成

Named Parameters and Optional Parameters

Dart parameters are divided into two types: required parameters and optional parameters.

Optional parameters are further divided into named parameters and positional parameters.

Named Parameters

Named parameters are wrapped in curly braces {}, and when calling, they are passed by parameter name, so the order can be arbitrary.

Example

// Parameters in curly braces {} are named parameters and are optional by default
// The required keyword marks them as required
String createUser({
  required String name,    // Required named parameter
  int age = 0,             // Optional, defaults to 0
  String? email,           // Optional, can be null
  bool isVip = false,      // Optional, defaults to false
}) {
  var info = 'Username: $name, Age: $age';
  if (email != null) {
    info += ', Email: $email';
  }
  if (isVip) {
    info += ' [VIP User]';
  }
  return info;
}

void main() {
  // Named parameters: passed by parameter name, order doesn't matter
  print(createUser(name: 'example', age: 10));
  print(createUser(name: 'admin', email: 'admin@example.com', isVip: true));
  // Required parameters cannot be omitted
  // createUser(); // Error: missing required parameter name
}
用户名: example, 年龄: 10
用户名: admin, 邮箱: admin@example.com [VIP用户]

Positional Parameters

Positional parameters are wrapped in square brackets [], and when calling, they are passed in order.

Example

// The ones in square brackets [] are optional positional parameters
String buildUrl(String host, [String path = '/', int port = 80]) {
  return 'http://$host:$port$path';
}

void main() {
  // Only pass required parameters, optional parameters use default values
  print(buildUrl('www.example.com'));

  // Pass 2 parameters
  print(buildUrl('www.example.com', '/dart'));

  // Pass 3 parameters
  print(buildUrl('localhost', '/api', 8080));
}
http://www.example.com:80/
http://www.example.com:80/dart
http://localhost:8080/api

Named parameters vs positional parameters: how to choose

ScenariosRecommended approachReason
Many parameters (more than 3)Named parametersThe parameter names are self-documenting at the call site, making it hard to pass them in the wrong order.
The meaning of the parameter is unclearNamed parametersValues such as true/false do not explain their purpose; adding a name makes them clearer.
Few parameters and clear meaningPositional parametersSuch as add(1, 2), concise and clear.
Flutter Widget constructionNamed parametersFlutter standard style

A function cannot use both optional positional parameters and optional named parameters. You can only choose one.


Default parameter values

Optional parameters can be given default values, which are used when the caller does not pass that parameter.

Example

// All optional parameters have default values
String formatMessage(
  String content, {
  String prefix = '[EXAMPLE]',
  String suffix = '',
  bool uppercase = false,
}) {
  var result = '$prefix $content $suffix';
  return uppercase ? result.toUpperCase() : result;
}

void main() {
  // Use all default values
  print(formatMessage('Dart tutorial updated'));

  // Override some default values
  print(formatMessage('Important notice', prefix: '[Announcement]', suffix: '!!!'));

  // Override default values + enable uppercase
  print(formatMessage('error', prefix: [Error], uppercase: true));
}
[EXAMPLE] Dart 教程更新了
[公告] 重要通知 !!!
[错误] ERROR

Default values must be compile-time constants. That is, the default value cannot be DateTime.now() or the return value of a function (unless the function is const). If you need to determine the default value at runtime, you can handle it with ?? inside the function body.


Arrow Functions and Anonymous Functions

When the function body contains only a single expression, arrow syntax (=>) can be used as shorthand.

An anonymous function is a function without a name, usually passed as an argument to another function.

Example

void main() {
  var numbers = [1, 2, 3, 4, 5];

  // Anonymous function (full syntax)
  numbers.forEach((number) {
    print(EXAMPLE number: $number);
  });

  print('---');

  // Arrow function (single-expression shorthand)
  var doubled = numbers.map((n) => n * 2);
  print(Doubled: $doubled);

  // Using arrow functions in function definitions
  // When the function body has only one return statement, you can use => as shorthand
  int square(int x) => x * x;
  print(5 squared: ${square(5)});

  // Store an anonymous function in a variable
  var sayHello = (String name) => Hello, $name!;
  print(sayHello('example'));
}
EXAMPLE 数字: 1
EXAMPLE 数字: 2
EXAMPLE 数字: 3
EXAMPLE 数字: 4
EXAMPLE 数字: 5
---
翻倍: (2, 4, 6, 8, 10)
5 的平方: 25
你好,example!

Higher-order functions and closures

A higher-order function is a function that can receive a function as an argument, or return a function as a result.

A closure is a function that can access variables in its lexical scope, even when the function is called outside that scope.

Higher-order functions

Example

// Higher-order function: accepts a function as a parameter
List<int> filterList(
    List<int> items, bool Function(int) predicate) {
  return items.where(predicate).toList();
}

// Higher-order function: returns a function
Function makeMultiplier(int factor) {
  // Returns a closure: remembers the outer factor
  return (int n) => n * factor;
}

void main() {
  var numbers = [10, 15, 20, 25, 30];

  // Pass an anonymous function as a filter condition
  var bigNumbers = filterList(numbers, (n) => n > 18);
  print(EXAMPLE numbers greater than 18: $bigNumbers);

  // Get the returned function
  var doubleIt = makeMultiplier(2);
  var tripleIt = makeMultiplier(3);

  print('5 × 2 = ${doubleIt(5)}');
  print('5 × 3 = ${tripleIt(5)}');
}
EXAMPLE 大于18的数: [20, 25, 30]
5 × 2 = 10
5 × 3 = 15

Closure

A closure is a function object that can access variables in its lexical scope, even when the function is called outside the original scope.

Example

// Closure typical application: creating a counter
Function makeCounter() {
  int count = 0;  // This variable is "captured" by the returned function
  return () {
    count++;
    return count;
  };
}

void main() {
  // counterA and counterB each have their own independent count
  var counterA = makeCounter();
  var counterB = makeCounter();

  print(EXAMPLE Counter A: ${counterA()});  // 1
  print(EXAMPLE Counter A: ${counterA()});  // 2
  print(EXAMPLE Counter B: ${counterB()});  // 1 (independent count)
  print(EXAMPLE Counter A: ${counterA()});  // 3
}
EXAMPLE 计数器 A: 1
EXAMPLE 计数器 A: 2
EXAMPLE 计数器 B: 1
EXAMPLE 计数器 A: 3

The key to closures is that the inner function "remembers" the outer function's variables, even after the outer function has finished executing. Each call to makeCounter() creates a brand new count variable and closure, so multiple counters do not affect each other.

other extensions