Exceptions - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# Exceptions

Unexpected situations are parts of programs: user mistakes, programming errors, changes in the program environment, etc. Programs must be written in ways to avoid producing incorrect results when faced with such exceptional conditions.

Some of these conditions may be severe enough to stop the execution of the program. For example, a required piece of information may be missing or invalid, or a device may not be functioning correctly. The exception handling mechanism of D helps with stopping program execution when necessary, and to recover from the unexpected situations when possible.

As an example of a severe condition, we can think of passing an unknown operator to a function that knows only the four arithmetic operators, as we have seen in the exercises of the previous chapter:

        switch (operator) {

        case "+":
            writeln(first + second);
            break;

        case "-":
            writeln(first - second);
            break;

        case "x":
            writeln(first * second);
            break;

        case "/":
            writeln(first / second);
            break;

        default:
            throw new Exception(format("Invalid operator: %s", operator));
        }

The `switch` statement above does not know what to do with operators that are not listed on the `case` statements; so throws an exception.

There are many examples of thrown exceptions in Phobos. For example, `to!int`, which can be used to convert a string representation of an integer to an `int` value throws an exception when that representation is not valid:

    import std.conv;

    void main() {
        const int value = to!int("hello");
    }

The program terminates with an exception that is thrown by `to!int`:

    std.conv.ConvException@std/conv.d(38): std.conv(1157): Can't
    convert value `hello' of type const(char)[] to type int

`std.conv.ConvException` at the beginning of the message is the type of the thrown exception object. We can tell from the name that the type is `ConvException` that is defined in the `std.conv` module.

##### The `throw` statement to throw exceptions

We've seen the `throw` statement both in the examples above and in the previous chapters.

`throw` throws an exception object and this terminates the current operation of the program. The expressions and statements that are written after the `throw` statement are not executed. This behavior is according to the nature of exceptions: they must be thrown when the program cannot continue with its current task.

Conversely, if the program could continue then the situation would not warrant throwing an exception. In such cases the function would find a way and continue.

###### The exception types `Exception` and `Error`

Only the types that are inherited from the `Throwable` class can be thrown. `Throwable` is almost never used directly in programs. The types that are actually thrown are types that are inherited from `Exception` or `Error`, which themselves are the types that are inherited from `Throwable`. For example, all of the exceptions that Phobos throws are inherited from either `Exception` or `Error`.

`Error` represents unrecoverable conditions and is not recommended to be caught. For that reason, most of the exceptions that a program throws are the types that are inherited from `Exception`. (Note: Inheritance is a topic related to classes. We will see classes in a later chapter.)

`Exception` objects are constructed with a `string` value that represents an error message. You may find it easy to create this message with the `format()` function from the `std.string` module:

    import std.stdio;
    import std.random;
    import std.string;

    int[] randomDiceValues(int count) {
        if (count < 0) {
            throw new Exception(
                format("Invalid dice count: %s", count));
        }

        int[] values;

        foreach (i; 0 .. count) {
            values ~= uniform(1, 7);
        }

        return values;
    }

    void main() {
        writeln(randomDiceValues(-5));
    }

    object.Exception...: Invalid dice count: -5

In most cases, instead of creating an exception object explicitly by `new` and throwing it explicitly by `throw`, the `enforce()` function is called. For example, the equivalent of the error check above is the following `enforce()` call:

        enforce(count >= 0, format("Invalid dice count: %s", count));

We will see the differences between `enforce()` and `assert()` in a later chapter.

###### Thrown exception terminates all scopes

We have seen that the program execution starts from the `main` function and branches into other functions from there. This layered execution of going deeper into functions and eventually returning from them can be seen as the branches of a tree.

For example, `main()` may call a function named `makeOmelet`, which in turn may call another function named `prepareAll`, which in turn may call another function named `prepareEggs`, etc. Assuming that the arrows indicate function calls, the branching of such a program can be shown as in the following function call tree:

    main
      │
      ├──▶ makeOmelet
      │      │
      │      ├──▶ prepareAll
      │      │          │
      │      │          ├─▶ prepareEggs
      │      │          ├─▶ prepareButter
      │      │          └─▶ preparePan
      │      │
      │      ├──▶ cookEggs
      │      └──▶ cleanAll
      │
      └──▶ eatOmelet

The following program demonstrates the branching above by using different levels of indentation in its output. The program doesn't do anything useful other than producing an output suitable to our purposes:

    import std.stdio;

    void indent(int level) {
        foreach (i; 0 .. level * 2) {
            write(' ');
        }
    }

    void entering(string functionName, int level) {
        indent(level);
        writeln("▶ ", functionName, "'s first line");
    }

    void exiting(string functionName, int level) {
        indent(level);
        writeln("◁ ", functionName, "'s last line");
    }

    void main() {
        entering("main", 0);
        makeOmelet();
        eatOmelet();
        exiting("main", 0);
    }

    void makeOmelet() {
        entering("makeOmelet", 1);
        prepareAll();
        cookEggs();
        cleanAll();
        exiting("makeOmelet", 1);
    }

    void eatOmelet() {
        entering("eatOmelet", 1);
        exiting("eatOmelet", 1);
    }

    void prepareAll() {
        entering("prepareAll", 2);
        prepareEggs();
        prepareButter();
        preparePan();
        exiting("prepareAll", 2);
    }

    void cookEggs() {
        entering("cookEggs", 2);
        exiting("cookEggs", 2);
    }

    void cleanAll() {
        entering("cleanAll", 2);
        exiting("cleanAll", 2);
    }

    void prepareEggs() {
        entering("prepareEggs", 3);
        exiting("prepareEggs", 3);
    }

    void prepareButter() {
        entering("prepareButter", 3);
        exiting("prepareButter", 3);
    }

    void preparePan() {
        entering("preparePan", 3);
        exiting("preparePan", 3);
    }

The program produces the following output:

    ▶ main, first line
      ▶ makeOmelet, first line
        ▶ prepareAll, first line
          ▶ prepareEggs, first line
          ◁ prepareEggs, last line
          ▶ prepareButter, first line
          ◁ prepareButter, last line
          ▶ preparePan, first line
          ◁ preparePan, last line
        ◁ prepareAll, last line
        ▶ cookEggs, first line
        ◁ cookEggs, last line
        ▶ cleanAll, first line
        ◁ cleanAll, last line
      ◁ makeOmelet, last line
      ▶ eatOmelet, first line
      ◁ eatOmelet, last line
    ◁ main, last line

The functions `entering` and `exiting` are used to indicate the first and last lines of functions with the help of the `▶` and `◁` characters. The program starts with the first line of `main()`, branches down to other functions, and finally ends with the last line of `main`.

Let's modify the `prepareEggs` function to take the number of eggs as a parameter. Since certain values of this parameter would be an error, let's have this function throw an exception when the number of eggs is less than one:

    import std.string;

    // ...

    void prepareEggs(int count) {
        entering("prepareEggs", 3);

        if (count < 1) {
            throw new Exception(
                format("Cannot take %s eggs from the fridge", count));
        }

        exiting("prepareEggs", 3);
    }

In order to be able to compile the program, we must modify other lines of the program to be compatible with this change. The number of eggs to take out of the fridge can be handed down from function to function, starting with `main()`. The parts of the program that need to change are the following. The invalid value of -8 is intentional to show how the output of the program will be different from the previous output when an exception is thrown:

    // ...

    void main() {
        entering("main", 0);
        makeOmelet(-8);
        eatOmelet();
        exiting("main", 0);
    }

    void makeOmelet(int eggCount) {
        entering("makeOmelet", 1);
        prepareAll(eggCount);
        cookEggs();
        cleanAll();
        exiting("makeOmelet", 1);
    }

    // ...

    void prepareAll(int eggCount) {
        entering("prepareAll", 2);
        prepareEggs(eggCount);
        prepareButter();
        preparePan();
        exiting("prepareAll", 2);
    }

    // ...

When we start the program now, we see that the lines that used to be printed after the point where the exception is thrown are missing:

    ▶ main, first line
      ▶ makeOmelet, first line
        ▶ prepareAll, first line
          ▶ prepareEggs, first line
    object.Exception: Cannot take -8 eggs from the fridge

When the exception is thrown, the program execution exits the `prepareEggs`, `prepareAll`, `makeOmelet` and `main()` functions in that order, from the bottom level to the top level. No additional steps are executed as the program exits these functions.

The rationale for such a drastic termination is that a failure in a lower level function would mean that the higher level functions that needed its successful completion should also be considered as failed.

The exception object that is thrown from a lower level function is transferred to the higher level functions one level at a time and causes the program to finally exit the `main()` function. The path that the exception takes can be shown as the highlighted path in the following tree:

         ▲
         │
         │
    main  ◀───────────┐
      │               │
      │               │
      ├──▶ makeOmelet  ◀─────┐
      │      │               │
      │      │               │
      │      ├──▶ prepareAll  ◀──────────┐
      │      │          │                │
      │      │          │                │
      │      │          ├─▶ prepareEggs  X thrown exception
      │      │          ├─▶ prepareButter
      │      │          └─▶ preparePan
      │      │
      │      ├──▶ cookEggs
      │      └──▶ cleanAll
      │
      └──▶ eatOmelet

The point of the exception mechanism is precisely this behavior of exiting all of the layers of function calls right away.

Sometimes it makes sense to catch the thrown exception to find a way to continue the execution of the program. I will introduce the `catch` keyword below.

###### When to use `throw`

Use `throw` in situations when it is not possible to continue. For example, a function that reads the number of students from a file may throw an exception if this information is not available or incorrect.

On the other hand, if the problem is caused by some user action like entering invalid data, it may make more sense to validate the data instead of throwing an exception. Displaying an error message and asking the user to re-enter the data is more appropriate in many cases.

##### The `try-catch` statement to catch exceptions

As we've seen above, a thrown exception causes the program execution to exit all functions and this finally terminates the whole program.

The exception object can be caught by a `try-catch` statement at any point on its path as it exits the functions. The `try-catch` statement models the phrase "try to do something, and catch exceptions that may be thrown." Here is the syntax of `try-catch`:

        try {
            // the code block that is being executed, where an
            // exception may be thrown

        } catch (an_exception_type) {
            // expressions to execute if an exception of this
            // type is caught

        } catch (another_exception_type) {
            // expressions to execute if an exception of this
            // other type is caught

        // ... more catch blocks as appropriate ...

        } finally {
            // expressions to execute regardless of whether an
            // exception is thrown
        }

Let's start with the following program that does not use a `try-catch` statement at this state. The program reads the value of a die from a file and prints it to the standard output:

    import std.stdio;

    int readDieFromFile() {
        auto file = File("the_file_that_contains_the_value", "r");

        int die;
        file.readf(" %s", &die);

        return die;
    }

    void main() {
        const int die = readDieFromFile();

        writeln("Die value: ", die);
    }

Note that the `readDieFromFile` function is written in a way that ignores error conditions, expecting that the file and the value that it contains are available. In other words, the function is dealing only with its own task instead of paying attention to error conditions. This is a benefit of exceptions: many functions can be written in ways that focus on their actual tasks, rather than focusing on error conditions.

Let's start the program when `the_file_that_contains_the_value` is missing:

    std.exception.ErrnoException@std/stdio.d(286): Cannot open
    file `the_file_that_contains_the_value' in mode `r' (No such
    file or directory)

An exception of type `ErrnoException` is thrown and the program terminates without printing "Die value: ".

Let's add an intermediate function to the program that calls `readDieFromFile` from within a `try` block and let's have `main()` call this new function:

    import std.stdio;

    int readDieFromFile() {
        auto file = File("the_file_that_contains_the_value", "r");

        int die;
        file.readf(" %s", &die);

        return die;
    }

    int tryReadingFromFile() {
        int die;

        try {
            die = readDieFromFile();

        } catch (std.exception.ErrnoException exc) {
            writeln("(Could not read from file; assuming 1)");
            die = 1;
        }

        return die;
    }

    void main() {
        const int die = tryReadingFromFile();

        writeln("Die value: ", die);
    }

When we start the program again when `the_file_that_contains_the_value` is still missing, this time the program does not terminate with an exception:

    (Could not read from file; assuming 1)
    Die value: 1

The new program tries executing `readDieFromFile` in a `try` block. If that block executes successfully, the function ends normally with the `return die;` statement. If the execution of the `try` block ends with the specified `std.exception.ErrnoException`, then the program execution enters the `catch` block.

The following is a summary of events when the program is started when the file is missing:

- like in the previous program, a `std.exception.ErrnoException` object is thrown (by `File()`, not by our code),
- this exception is caught by `catch`,
- the value of 1 is assumed during the normal execution of the `catch` block,
- and the program continues its normal operations.

`catch` is to catch thrown exceptions to find a way to continue executing the program.

As another example, let's go back to the omelet program and add a `try-catch` statement to its `main()` function:

    void main() {
        entering("main", 0);

        try {
            makeOmelet(-8);
            eatOmelet();

        } catch (Exception exc) {
            write("Failed to eat omelet: ");
            writeln('"', exc.msg, '"');
            writeln("Will eat at neighbor's...");
        }

        exiting("main", 0);
    }

(Note: The `.msg` property will be explained below.)

That `try` block contains two lines of code. Any exception thrown from either of those lines would be caught by the `catch` block.

    ▶ main, first line
      ▶ makeOmelet, first line
        ▶ prepareAll, first line
          ▶ prepareEggs, first line
    Failed to eat omelet: "Cannot take -8 eggs from the fridge"
    Will eat at neighbor's...
    ◁ main, last line

As can be seen from the output, the program doesn't terminate because of the thrown exception anymore. It recovers from the error condition and continues executing normally till the end of the `main()` function.

###### `catch` blocks are considered sequentially

The type `Exception`, which we have used so far in the examples is a general exception type. This type merely specifies that an error occurred in the program. It also contains a message that can explain the error further, but it does not contain information about the type of the error.

`ConvException` and `ErrnoException` that we have seen earlier in this chapter are more specific exception types: the former is about a conversion error, and the latter is about a system error. Like many other exception types in Phobos and as their names suggest, `ConvException` and `ErrnoException` are both inherited from the `Exception` class.

`Exception` and its sibling `Error` are further inherited from `Throwable`, the most general exception type.

Although possible, it is not recommended to catch objects of type `Error` and objects of types that are inherited from `Error`. Since it is more general than `Error`, it is not recommended to catch `Throwable` either. What should normally be caught are the types that are under the `Exception` hierarchy, including `Exception` itself.

               Throwable (not recommended to catch)
                 ↗   ↖
        Exception     Error (not recommended to catch)
         ↗    ↖        ↗    ↖
       ...    ...    ...    ...

Note: I will explain the hierarchy representations later in the Inheritance chapter. The tree above indicates that `Throwable` is the most general and `Exception` and `Error` are more specific.

It is possible to catch exception objects of a particular type. For example, it is possible to catch an `ErrnoException` object specifically to detect and handle a system error.

Exceptions are caught only if they match a type that is specified in a `catch` block. For example, a catch block that is trying to catch a `SpecialExceptionType` would not catch an `ErrnoException`.

The type of the exception object that is thrown during the execution of a `try` block is matched to the types that are specified by the `catch` blocks, in the order in which the `catch` blocks are written. If the type of the object matches the type of the `catch` block, then the exception is considered to be caught by that `catch` block, and the code that is within that block is executed. Once a match is found, the remaining `catch` blocks are ignored.

Because the `catch` blocks are matched in order from the first to the last, the `catch` blocks must be ordered from the most specific exception types to the most general exception types. Accordingly, and if it makes sense to catch that type of exceptions, the `Exception` type must be specified at the last `catch` block.

For example, a `try-catch` statement that is trying to catch several specific types of exceptions about student records must order the `catch` blocks from the most specific to the most general as in the following code:

        try {
            // operations about student records that may throw ...

        } catch (StudentIdDigitException exc) {

            // an exception that is specifically about errors with
            // the digits of student ids

        } catch (StudentIdException exc) {

            // a more general exception about student ids but not
            // necessarily about their digits

        } catch (StudentRecordException exc) {

            // even more general exception about student records

        } catch (Exception exc) {

            // the most general exception that may not be related
            // to student records

        }

###### The `finally` block

`finally` is an optional block of the `try-catch` statement. It includes expressions that should be executed regardless of whether an exception is thrown or not.

To see how `finally` works, let's look at a program that throws an exception 50% of the time:

    import std.stdio;
    import std.random;

    void throwsHalfTheTime() {
        if (uniform(0, 2) == 1) {
            throw new Exception("the error message");
        }
    }

    void foo() {
        writeln("the first line of foo()");

        try {
            writeln("the first line of the try block");
            throwsHalfTheTime();
            writeln("the last line of the try block");

        // ... there may be one or more catch blocks here ...

        } finally {
            writeln("the body of the finally block");
        }

        writeln("the last line of foo()");
    }

    void main() {
        foo();
    }

The output of the program is the following when the function does not throw:

    the first line of foo()
    the first line of the try block
    the last line of the try block
    the body of the finally block
    the last line of foo()

The output of the program is the following when the function does throw:

    the first line of foo()
    the first line of the try block
    the body of the finally block
    object.Exception@deneme.d: the error message

As can be seen, although "the last line of the try block" and "the last line of foo()" are not printed, the content of the `finally` block is still executed when an exception is thrown.

###### When to use the `try-catch` statement

The `try-catch` statement is useful to catch exceptions to do something special about them.

For that reason, the `try-catch` statement should be used only when there is something special to be done. Do not catch exceptions otherwise and leave them to higher level functions that may want to catch them.

##### Exception properties

The information that is automatically printed on the output when the program terminates due to an exception is available as properties of exception objects as well. These properties are provided by the `Throwable` interface:

- `.file`: The source file where the exception was thrown from
- `.line`: The line number where the exception was thrown from
- `.msg`: The error message
- `.info`: The state of the program stack when the exception was thrown
- `.next`: The next collateral exception

We saw that `finally` blocks are executed when leaving scopes due to exceptions as well. (As we will see in later chapters, the same is true for `scope` statements and destructors as well.)

Naturally, such code blocks can throw exceptions as well. Exceptions that are thrown when leaving scopes due to an already thrown exception are called collateral exceptions. Both the main exception and the collateral exceptions are elements of a linked list data structure, where every exception object is accessible through the `.next` property of the previous exception object. The value of the `.next` property of the last exception is `null`. (We will see `null` in a later chapter.)

There are three exceptions that are thrown in the example below: The main exception that is thrown in `foo()` and the two collateral exceptions that are thrown in the `finally` blocks of `foo()` and `bar()`. The program accesses the collateral exceptions through the `.next` properties.

Some of the concepts that are used in this program will be explained in later chapters. For example, the continuation condition of the `for` loop that consists solely of `exc` means as long as `exc` is not `null`.

    import std.stdio;

    void foo() {
        try {
            throw new Exception("Exception thrown in foo");

        } finally {
            throw new Exception(
                "Exception thrown in foo's finally block");
        }
    }

    void bar() {
        try {
            foo();

        } finally {
            throw new Exception(
                "Exception thrown in bar's finally block");
        }
    }

    void main() {
        try {
            bar();

        } catch (Exception caughtException) {

            for (Throwable exc = caughtException;
                 exc;    // ← Meaning: as long as exc is not 'null'
                 exc = exc.next) {

                writefln("error message: %s", exc.msg);
                writefln("source file  : %s", exc.file);
                writefln("source line  : %s", exc.line);
                writeln();
            }
        }
    }

The output:

    error message: Exception thrown in foo
    source file  : deneme.d
    source line  : 6

    error message: Exception thrown in foo's finally block
    source file  : deneme.d
    source line  : 9

    error message: Exception thrown in bar's finally block
    source file  : deneme.d
    source line  : 20

##### Kinds of errors

We have seen how useful the exception mechanism is. It enables both the lower and higher level operations to be aborted right away, instead of the program continuing with incorrect or missing data, or behaving in any other incorrect way.

This does not mean that every error condition warrants throwing an exception. There may be better things to do depending on the kinds of errors.

###### User errors

Some of the errors are caused by the user. As we have seen above, the user may have entered a string like "hello" even though the program has been expecting a number. It may be more appropriate to display an error message and ask the user to enter appropriate data again.

Even so, it may be fine to accept and use the data directly without validating the data up front; as long as the code that uses the data would throw anyway. What is important is to be able to notify the user why the data is not suitable.

For example, let's look at a program that takes a file name from the user. There are at least two ways of dealing with potentially invalid file names:

- Validating the data before use: We can determine whether the file with the given name exists by calling `exists()` of the `std.file` module:

          if (exists(fileName)) {
              // yes, the file exists

          } else {
              // no, the file doesn't exist
          }

  This gives us the chance to be able to open the data only if it exists. Unfortunately, it is still possible that the file cannot be opened even if `exists()` returns `true`, if for example another process on the system deletes or renames the file before this program actually opens it.

  For that reason, the following method may be more useful.

- Using the data without first validating it: We can assume that the data is valid and start using it right away, because `File` would throw an exception if the file cannot be opened anyway.

      import std.stdio;
      import std.string;

      void useTheFile(string fileName) {
          auto file = File(fileName, "r");
          // ...
      }

      string read_string(string prompt) {
          write(prompt, ": ");
          return strip(readln());
      }

      void main() {
          bool is_fileUsed = false;

          while (!is_fileUsed) {
              try {
                  useTheFile(
                      read_string("Please enter a file name"));

                  /* If we are at this line, it means that
                   * useTheFile() function has been completed
                   * successfully. This indicates that the file
                   * name was valid.
                   *
                   * We can now set the value of the loop flag to
                   * terminate the while loop. */
                  is_fileUsed = true;
                  writeln("The file has been used successfully");

              } catch (std.exception.ErrnoException exc) {
                  stderr.writeln("This file could not be opened");
              }
          }
      }

###### Programmer errors

Some errors are caused by programmer mistakes. For example, the programmer may think that a function that has just been written will always be called with a value greater than or equal to zero, and this may be true according to the design of the program. The function having still been called with a value less than zero would indicate either a mistake in the design of the program or in the implementation of that design. Both of these can be thought of as programming errors.

It is more appropriate to use `assert` instead of the exception mechanism for errors that are caused by programmer mistakes. (Note: We will cover `assert` in a later chapter.)

    void processMenuSelection(int selection) {
        assert(selection >= 0);
        // ...
    }

    void main() {
        processMenuSelection(-1);
    }

The program terminates with an `assert` failure:

    core.exception.AssertError@deneme.d(2): Assertion failure

`assert` validates program state and prints the file name and line number of the validation if it fails. The message above indicates that the assertion at line 2 of `deneme.d` has failed.

###### Unexpected situations

For unexpected situations that are outside of the two general cases above, it is still appropriate to throw exceptions. If the program cannot continue its execution, there is nothing else to do but to throw.

It is up to the higher layer functions that call this function to decide what to do with thrown exceptions. They may catch the exceptions that we throw to remedy the situation.

##### Summary

- When faced with a user error either warn the user right away or ensure that an exception is thrown; the exception may be thrown anyway by another function when using incorrect data, or you may throw directly.
- Use `assert` to validate program logic and implementation. (Note: `assert` will be explained in a later chapter.)
- When in doubt, throw an exception with `throw` or `enforce()`. (Note: `enforce()` will be explained in a later chapter.)
- Catch exceptions if and only if you can do something useful about that exception. Otherwise, do not encapsulate code with a `try-catch` statement; instead, leave the exceptions to higher layers of the code that may do something about them.
- Order the `catch` blocks from the most specific to the most general.
- Put the expressions that must always be executed when leaving a scope, in `finally` blocks.

Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Wed Aug 5 11:24:19 2026