[Dart Language Tour] Control flow statements, Assert, Exception

Control flow statements, Assert, Exception

Control flow statements

You can control the flow of your Dart code using any of the following:

  • if and else

  • for loops

  • while and do-while loops

  • break and continue

  • switch and case

  • assert

You can also affect the control flow using try-catch and throw, as explained in Exceptions - https://dart.dev/guides/language/language-tour#exceptions.

If and else

Dart supports if statements with optional else statements, as the next sample shows. Also see conditional expressions - https://dart.dev/guides/language/language-tour#conditional-expressions.

1
2
3
4
5
6
7
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}

Unlike JavaScript, conditions must use boolean values, nothing else. See Booleans - https://dart.dev/guides/language/language-tour#booleans for more information.

For loops

You can iterate with the standard for loop. For example:

1
2
3
4
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}

Closures inside of Dart’s for loops capture the value of the index, avoiding a common pitfall found in JavaScript. For example, consider:

1
2
3
4
5
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

The output is 0 and then 1, as expected. In contrast, the example would print 2 and then 2 in JavaScript.

If the object that you are iterating over is an Iterable, you can use the forEach() - https://api.dart.dev/stable/dart-core/Iterable/forEach.html method. Using forEach() is a good option if you don’t need to know the current iteration counter:

1
candidates.forEach((candidate) => candidate.interview());

Iterable classes such as List and Set also support the for-in form of iteration - https://dart.dev/guides/libraries/library-tour#iteration:

1
2
3
4
var collection = [1, 2, 3];
for (var x in collection) {
print(x); // 1 2 3
}

While and do-while

A while loop evaluates the condition before the loop:

1
2
3
while (!isDone()) {
doSomething();
}

A do-while loop evaluates the condition after the loop:

1
2
3
do {
printLine();
} while (!atEndOfPage());

Break and continue

Use break to stop looping:

1
2
3
4
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}

Use continue to skip to the next loop iteration:

1
2
3
4
5
6
7
for (int i = 0; i < candidates.length; i++) {
var candidate = candidates[i];
if (candidate.yearsExperience < 5) {
continue;
}
candidate.interview();
}

You might write that example differently if you’re using an Iterable such as a list or set:

1
2
3
candidates
.where((c) => c.yearsExperience >= 5)
.forEach((c) => c.interview());

Switch and case

Switch statements in Dart compare integer, string, or compile-time constants using ==. The compared objects must all be instances of the same class (and not of any of its subtypes), and the class must not override ==. Enumerated types - https://dart.dev/guides/language/language-tour#enumerated-typeswork well in switch statements.

Each non-empty case clause ends with a break statement, as a rule. Other valid ways to end a non-empty case clause are a continue, throw, or return statement.

Use a default clause to execute code when no case clause matches:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}

The following example omits the break statement in a case clause, thus generating an error:

1
2
3
4
5
6
7
8
9
10
var command = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// ERROR: Missing break

case 'CLOSED':
executeClosed();
break;
}

However, Dart does support empty case clauses, allowing a form of fall-through:

1
2
3
4
5
6
7
8
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}

If you really want fall-through, you can use a continue statement and a label:

1
2
3
4
5
6
7
8
9
10
11
12
13
var command = 'CLOSED';
switch (command) {
case 'CLOSED':
executeClosed();
continue nowClosed;
// Continues executing at the nowClosed label.

nowClosed:
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}

A case clause can have local variables, which are visible only inside the scope of that clause.

Assert

During development, use an assert statement — assert(condition, optionalMessage); — to disrupt normal execution if a boolean condition is false. You can find examples of assert statements throughout this tour. Here are some more:

1
2
3
4
5
6
7
8
// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

To attach a message to an assertion, add a string as the second argument to assert (optionally with a trailing comma - https://dart.dev/guides/language/language-tour#trailing-comma):

1
2
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".');

The first argument to assert can be any expression that resolves to a boolean value. If the expression’s value is true, the assertion succeeds and execution continues. If it’s false, the assertion fails and an exception (an AssertionError - https://dart.dev/guides/language/language-tour#trailing-comma) is thrown.

When exactly do assertions work? That depends on the tools and framework you’re using:

In production code, assertions are ignored, and the arguments to assert aren’t evaluated.

Exceptions

Your Dart code can throw and catch exceptions. Exceptions are errors indicating that something unexpected happened. If the exception isn’t caught, the isolate - https://dart.dev/guides/language/language-tour#isolates that raised the exception is suspended, and typically the isolate and its program are terminated.


In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods do not declare which exceptions they might throw, and you are not required to catch any exceptions.


Dart provides Error - https://api.dart.dev/stable/dart-core/Error-class.html and Exception - https://api.dart.dev/stable/dart-core/Exception-class.html types, as well as numerous predefined subtypes. You can, of course, define your own exceptions. However, Dart programs can throw any non-null object—not just Exception and Error objects—as an exception.

Throw

Here’s an example of throwing, or raising, an exception:

1
throw FormatException('Expected at least 1 section');

You can also throw arbitrary objects:

1
throw 'Out of llamas!';

Note: Production-quality code usually throws types that implement Error - https://api.dart.dev/stable/dart-core/Error-class.html or Exception - https://api.dart.dev/stable/dart-core/Exception-class.html.


Because throwing an exception is an expression, you can throw exceptions in => statements, as well as anywhere else that allows expressions:

1
void distanceTo(Point other) => throw UnimplementedError();

Catch

Catching, or capturing, an exception stops the exception from propagating (unless you rethrow the exception). Catching an exception gives you a chance to handle it:

1
2
3
4
5
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}

To handle code that can throw more than one type of exception, you can specify multiple catch clauses. The first catch clause that matches the thrown object’s type handles the exception. If the catch clause does not specify a type, that clause can handle any type of thrown object:

1
2
3
4
5
6
7
8
9
10
11
12
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}

As the preceding code shows, you can use either on or catch or both. Use on when you need to specify the exception type. Use catch when your exception handler needs the exception object.

You can specify one or two parameters to catch(). The first is the exception that was thrown, and the second is the stack trace (a StackTrace - https://api.dart.dev/stable/dart-core/StackTrace-class.html object).

1
2
3
4
5
6
7
8
try {
// ···
} on Exception catch (e) {
print('Exception details:\n $e');
} catch (e, s) {
print('Exception details:\n $e');
print('Stack trace:\n $s');
}

To partially handle an exception, while allowing it to propagate, use the rethrow keyword.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void misbehave() {
try {
dynamic foo = true;
print(foo++); // Runtime error
} catch (e) {
print('misbehave() partially handled ${e.runtimeType}.');
rethrow; // Allow callers to see the exception.
}
}

void main() {
try {
misbehave();
} catch (e) {
print('main() finished handling ${e.runtimeType}.');
}
}

Finally

To ensure that some code runs whether or not an exception is thrown, use a finally clause. If no catch clause matches the exception, the exception is propagated after the finally clause runs:

1
2
3
4
5
6
try {
breedMoreLlamas();
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}

The finally clause runs after any matching catch clauses:

1
2
3
4
5
6
7
try {
breedMoreLlamas();
} catch (e) {
print('Error: $e'); // Handle the exception first.
} finally {
cleanLlamaStalls(); // Then clean up.
}

Learn more by reading the Exceptions - https://dart.dev/guides/libraries/library-tour#exceptions section of the library tour.