Function

In Dart, functions are also objects, which differs slightly from other programming languages.

Functions are first-class objects

Dart is a fully object-oriented language, and functions themselves are objects with the type Function.

They can be assigned to variables and passed as arguments to other functions;

Class instances can be invoked directly like functions

1 Standard Function Definition

// Full declaration with return type and parameter type annotations
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
Code language: Dart (dart)

Full Example

// Mapping table of noble gas atomic numbers
final Map<int, String> _nobleGases = {
  2: 'He',
  10: 'Ne',
  18: 'Ar',
  36: 'Kr',
  54: 'Xe',
  86: 'Rn',
};

// Full declaration with return type and parameter type annotations
// Type int is non-nullable; use int? to accept null values
bool isNoble(int? atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void main() {
  print(isNoble(null));       // null is not a map key → false
  print(isNoble(2));          // Matches a noble gas → true
  print(isNoble(9));          // No match → false
  print(isNoble(86));         // Matches a noble gas → true
}
Code language: Dart (dart)
false
true
false
trueCode language: JavaScript (javascript)

2 Omit Type Annotations

The Effective Dart style guide requires complete explicit type annotations for public external APIs.

You may omit type annotations for internal private functions

Example


final Map<int, String> _nobleGases = {
  2: 'He',
  10: 'Ne',
  18: 'Ar',
  36: 'Kr',
  54: 'Xe',
  86: 'Rn',
};


isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

void main() {
  print(isNoble(null));       // null is not a map key → false
  print(isNoble(2));          // Matches a noble gas → true
  print(isNoble(9));          // No match → false
  print(isNoble(86));         // Matches a noble gas → true
}

Code language: Dart (dart)

In the example above, the isNoble function omits the bool return type and int? parameter type, yet it works normally.

When you skip type annotations, Dart performs automatic type inference:

  • Parameter atomicNumber: No explicit type → inferred as dynamic (dynamic type, accepts any value: numbers, strings, null)
  • Return value: No leading type annotation → Dart infers the return type as bool from the expression after return, without writing it explicitly in code
Why Must Public APIs Include Explicit Types?

Readability: Developers calling your function can immediately see input and return types without reading internal implementation code;

Type Safety: The dynamic type disables static type checking; passing incorrect types will not trigger compile errors and often causes runtime crashes;

IDE Intellisense: Annotated types enable autocompletion and parameter hints in editors; IDEs cannot provide accurate hints without type annotations.

When Can You Omit Type Annotations?

Only recommended for local private code — functions used exclusively within the current file and never invoked externally.

Arrow Shorthand Syntax (=>)

You can shorten a function to arrow syntax when its body (code inside {}) contains only a single return expression.

Example

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
Code language: Dart (dart)
  • => expr is equivalent to { return expr; };
  • Only expressions can appear between the arrow and semicolon; statements such as if are forbidden (use ternary operators as alternatives).
// Mapping table of noble gas atomic numbers
final Map<int, String> _nobleGases = {
  2: 'Helium',
  10: 'Neon',
  18: 'Argon',
  36: 'Krypton',
  54: 'Xenon',
  86: 'Radon',
};

// Arrow shorthand function: single-line expression returns directly, equivalent to block functions with return
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

void main() {
  // Test existing noble gases
  print(isNoble(2));   // true
  print(isNoble(18));  // true

  // Test regular elements
  print(isNoble(6));   // false (Carbon)
  print(isNoble(99));  // false (No matching element)
}
Code language: Dart (dart)

Parameters

Functions support required positional parameters, followed by exactly one of two options: named parameters or optional positional parameters — they cannot be mixed.

Trailing commas , are allowed when defining or calling parameters to improve code readability.

1. Named parameters

Wrapped in {}, optional by default; add required to enforce mandatory passing.

1) Basic nullable named parameters (no default value, defaults to null, type must be nullable with ?)

void enableFlags({bool? bold, bool? hidden}) {}
// Explicitly specify parameter names during invocation
enableFlags(bold: true, hidden: false);
Code language: Dart (dart)

Full Example

/// Enable text style flags: bold, hidden
void enableFlags({bool? bold, bool? hidden}) {
  print('Bold: $bold, Hidden: $hidden');
}

void main() {
  // Pass all named parameters
  enableFlags(bold: true, hidden: false);

  // Pass only one named parameter; the other becomes null
  enableFlags(bold: true);

  // Pass only hidden
  enableFlags(hidden: true);

  // Pass no parameters; both values are null
  enableFlags();
}
Code language: Dart (dart)

Output Result

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Bold: true, Hidden: false
Bold: true, Hidden: null
Bold: null, Hidden: true
Bold: null, Hidden: nullCode language: JavaScript (javascript)

2) Set compile-time constant default values (nullable type mark ? is unnecessary, as a default value exists)

void enableFlags({bool bold = false, bool hidden = false}) {}
enableFlags(bold: true); // hidden automatically uses false

Code language: Dart (dart)

Example

/// Enable text style flags: bold, hidden
void enableFlags({bool bold = false, bool hidden = false}) {
  print('Bold: $bold, Hidden: $hidden');
}

void main() {
  enableFlags();//No parameters passed, both use default false → Bold: false, Hidden: false
}
Code language: Dart (dart)

3) Mandatory named parameters with required (Note: Frequently used in Flutter widget constructors). The example below is Flutter code; learn more in Flutter tutorials.

const Scrollbar({super.key, required Widget child});
// required parameters also accept nullable types
const Scrollbar({super.key, required Widget? child});

Code language: Dart (dart)

Dart does not force named parameters to be placed at the end; they can be inserted anywhere in the parameter list

repeat(times: 2, () {})Code language: CSS (css)
2. Optional positional parameters

Wrapped in [], defaults to null when omitted; constant default values can be defined.

// No default value, type marked with ?
String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) result = '$result with a $device';
  return result;
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');

Code language: Dart (dart)

Full Example

// [String? device] optional positional parameter with no default value; null if omitted
String say(String from, String msg, [String? device]) {
  var result = '$from says $msg';
  if (device != null) {
    result = '$result with a $device'; //Append device info to the message if device is not null
  }
  return result;
}

void main() {
  // Omit the third optional parameter
  String res1 = say('Bob', 'Howdy');
  print(res1);

  // Pass the third optional positional parameter
  String res2 = say('Bob', 'Howdy', 'smoke signal');
  print(res2);

  // Manually pass null to the optional parameter
  String res3 = say('Alice', 'Hi', null);
  print(res3);
}
Code language: Dart (dart)

Set default values:

String say(String from, String msg, [String device = 'carrier pigeon']) {
  return '$from says $msg with a $device';
}
assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');

Code language: Dart (dart)

Full Example

// Optional positional parameter with default value carrier pigeon
String say(String from, String msg, [String device = 'carrier pigeon']) {
  return '$from says $msg with a $device';
}

void main() {
  // Omit third optional parameter; default carrier pigeon is used automatically
  String res1 = say('Bob', 'Howdy');
  print(res1); //Bob says Howdy with a carrier pigeon

  // Pass custom device to override default value
  String res2 = say('Bob', 'Howdy', 'smoke signal');
  print(res2); //Bob says Howdy with a smoke signal

  String res3 = say('Alice', 'Hello', 'mobile phone');
  print(res3); // Alice says Hello with a mobile phone
  
  String res4 = say('Jack', 'Tom', 'website foxdevelop.com');
  print(res4); // Jack says Tom with a website foxdevelop.com
}
Code language: Dart (dart)

Output Result

Built firstdart:firstdart.
Bob says Howdy with a carrier pigeon
Bob says Howdy with a smoke signal
Alice says Hello with a mobile phone
Jack says Tom with a website foxdevelop.comCode language: JavaScript (javascript)


main () Entry Point Function

Every Dart program requires a top-level main() function, which acts as the sole program entry point.

  1. Basic no-argument version
void main() {
  print('Hello, welcome to foxdevelop.com!');
}

Code language: Dart (dart)
Hello, welcome to foxdevelop.com!Code language: CSS (css)
  1. Command-line program receiving launch arguments
// Execution command: dart run firstdart.dart 1 test
void main(List<String> arguments) {
  print(arguments); // [1, test]
}

Code language: Dart (dart)

Use the official args library for complex command-line argument parsing.

Replace firstdart.dart above with your own Dart filename


Functions as First-class Objects

1. Pass Functions as Parameters

void printElement(int element) => print(element);
var list = [1, 2, 3];
list.forEach(printElement); // Directly pass the function

Code language: Dart (dart)

Full Example

void main() {
    void printElement(int element) => print(element);
	var list = [1, 2, 3];
	list.forEach(printElement); // Directly pass the function
}
Code language: Dart (dart)

Output

1
2
3

2. Assign Anonymous Functions to Variables

void main() {
    var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
	print(loudify('hello')); //!!! HELLO !!! 
}
Code language: Dart (dart)

Function Types

You may explicitly declare types for function variables

Format: ReturnType Function(ParameterList, {NamedParameters})

void greet(String name, {String greeting = 'Hello'}) => print('$greeting $name!');
// Declare function-type variable
void Function(String, {String greeting}) g = greet;
g('Dash', greeting: 'Howdy');

Code language: Dart (dart)

Use typedef to create aliases for function types for reusability and readability.

{String greeting}) → One named parameter greeting of type String

void main() {
    void greet(String name, {String greeting = 'Hello'}) => print('$greeting $name!');
	// Declare function-type variable
	void Function(String, {String greeting}) g = greet;
	g('Jack', greeting: 'Howdy');
}
Code language: Dart (dart)

Output

Howdy Jack!

Anonymous Functions (Lambda / Closures)

Functions without a defined name, two writing styles:

  1. Block syntax (multi-line)
void main() {
    const list = ['apples', 'bananas'];
	var uppercaseList = list.map((item) {
	  return item.toUpperCase();
	}).toList();
	print(uppercaseList);
}
Code language: Dart (dart)

(item) { return item.toUpperCase(); } in the code above is an anonymous function (lambda)

Output: [APPLES, BANANAS]

  1. Arrow shorthand (single-line expression)
void main() {
    const list = ['fox', 'develop','foxdevelop.com'];
    var uppercaseList = list.map((item) => item.toUpperCase()).toList();
    uppercaseList.forEach((item) => print('$item: ${item.length}'));
}

Code language: Dart (dart)

Output

FOX: 3
DEVELOP: 7
FOXDEVELOP.COM: 14Code language: HTTP (http)

Lexical Scope

Dart uses static lexical scoping: variable scope is determined by brace hierarchy, and inner scopes can access all variables from outer scopes.

bool topLevel = true;
void main() {
  var insideMain = true;
  void myFunction() {
    var insideFunction = true;
    void nestedFunction() {
      var insideNestedFunction = true;
      // All variables are accessible
      assert(topLevel && insideMain && insideFunction && insideNestedFunction);
    }
  }
}

Code language: Dart (dart)

Lexical Closures

Closures: Function objects can capture and retain variables from outer scopes, and access them even after leaving the original scope.

Function makeAdder(int addBy) {
  // Capture outer variable addBy
  return (int i) => addBy + i;
}

void main() {
  var add2 = makeAdder(2);
  var add4 = makeAdder(4);
  assert(add2(3) == 5);
  assert(add4(3) == 7);
}

Code language: Dart (dart)

Step-by-step breakdown of execution flow above

Step 1: var add2 = makeAdder(2);

  1. Execute makeAdder, pass addBy = 2
  2. Create anonymous function: (int i) => 2 + i
  3. Assign this function to variable add2. At this point add2 is a function object whose body is (int i) => 2 + i; it accepts an int i and returns i+2
  4. The makeAdder function finishes execution, yet the anonymous function permanently remembers addBy=2

Step 2: var add4 = makeAdder(4);

  1. Execute makeAdder again, pass addBy = 4
  2. Create a completely separate new anonymous function: (int i) => 4 + i
  3. Assign to add4, which stores its own independent copy of addBy=4 without interfering with add2. The value addBy is stored separately inside the two function objects add2 and add4, one holding 2 and the other 4
  • Calling makeAdder(2) generates an independent closure with a unique bound value of addBy = 2, assigned to add2;
  • Calling makeAdder(4) generates a brand-new separate closure with a unique bound value of addBy = 4, assigned to add4;
  • add2 and add4 are fully isolated function objects, each privately storing its captured addBy without mutual interference.

Step 3: Invoke add2(3)

Execute inner function: 2 + 3 = 5, assertion passes

Step 4: Invoke add4(3)

Execute inner function: 4 + 3 = 7, assertion passes

Function Tear-offs

Omit parentheses when invoking functions/methods to create tear-offs (lightweight closures), replacing manually written anonymous lambdas for cleaner code.

This is the recommended syntax; simply pass the function directly.

var charCodes = [68, 97, 114, 116];
var buffer = StringBuffer();
charCodes.forEach(print); // Function tear-off
charCodes.forEach(buffer.write); // Method tear-off

Code language: Dart (dart)

Verbose equivalent syntax

charCodes.forEach((code) => print(code));
charCodes.forEach((code) => buffer.write(code));

Code language: Dart (dart)

Function Equality Checks

  • Top-level functions and static methods: == returns true if function names match exactly;
  • Instance methods: Only tear-offs bound to the same object instance evaluate as equal.
void foo() {}
class A {
  static void bar() {}
  void baz() {}
}
void main() {
  var x = foo;
  assert(foo == x);

  x = A.bar;
  assert(A.bar == x);

  var v = A(), w = A(), y = w;
  x = w.baz;
  assert(y.baz == x); // Same instance → equal
  assert(v.baz != w.baz); // Different instances → not equal
}

Code language: Dart (dart)

Return Values

  1. All functions have a return value: If no explicit return exists, return null; is implicitly appended at the end
foo() {}
assert(foo() == null);

Code language: Dart (dart)
  1. Return multiple values: Use Record tuples
(String, int) foo() {
  return ('something', 42);
}

Code language: Dart (dart)

Getter & Setter Accessors

Reading or assigning any property fundamentally calls its getter or setter. Regular variables generate implicit accessors automatically; you may also define explicit ones manually.

Advantage: External code uses uniform .property access, while internal logic can be modified anytime without updating external calling code.

String _secret = 'Hello';

// Getter for read operations
String get secret {
  print('getter invoked');
  return _secret.toUpperCase();
}

// Setter for assignment operations
set secret(String newMsg) {
  print('setter invoked');
  if (newMsg.isNotEmpty) _secret = newMsg;
}

void main() {
  print(secret); // Read, triggers getter → HELLO
  secret = 'Dart is fun'; // Assign, triggers setter
  print(secret); // DART IS FUN
}

Code language: Dart (dart)

* Generators

Lazily generate sequences on demand; two variants: synchronous and asynchronous, using yield to emit values.

  1. Synchronous generator sync*, returns Iterable
Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

Code language: Dart (dart)
  1. Asynchronous generator async*, returns Stream
Stream<int> asyncNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

Code language: Dart (dart)
  1. Recursive generator optimization with yield* (delegates to another generator for better performance)
Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

Code language: Dart (dart)

Leave a Reply

Your email address will not be published. Required fields are marked *