Errors in D Programming Language

import std.stdio;
import std.exception;

// By convention, errors are often returned as the last value
// and have type Exception or Error.
int f(int arg) {
    if (arg == 42) {
        // new Exception constructs a basic Exception
        // with the given error message.
        throw new Exception("can't work with 42");
    }
    
    // A function that doesn't throw an exception
    // indicates that there was no error.
    return arg + 3;
}

// A custom exception is similar to a sentinel error in Go
class OutOfTeaException : Exception {
    this(string msg, string file = __FILE__, size_t line = __LINE__) {
        super(msg, file, line);
    }
}

class PowerException : Exception {
    this(string msg, string file = __FILE__, size_t line = __LINE__) {
        super(msg, file, line);
    }
}

void makeTea(int arg) {
    if (arg == 2) {
        throw new OutOfTeaException("no more tea available");
    } else if (arg == 4) {
        // In D, we can't directly wrap exceptions like in Go,
        // but we can create a new exception with additional context
        throw new Exception("making tea: " ~ new PowerException("can't boil water").msg);
    }
}

void main() {
    import std.range : only;
    
    foreach (i; only(7, 42)) {
        try {
            int r = f(i);
            writeln("f worked: ", r);
        } catch (Exception e) {
            writeln("f failed: ", e.msg);
        }
    }

    foreach (i; 0 .. 5) {
        try {
            makeTea(i);
            writeln("Tea is ready!");
        } catch (OutOfTeaException e) {
            writeln("We should buy new tea!");
        } catch (PowerException e) {
            writeln("Now it is dark.");
        } catch (Exception e) {
            if (cast(PowerException) e) {
                writeln("Now it is dark.");
            } else {
                writefln("unknown error: %s", e.msg);
            }
        }
    }
}

This D code demonstrates error handling using exceptions, which is the idiomatic way to handle errors in D. Here are some key points about the translation:

  1. D uses exceptions for error handling, unlike Go’s explicit error return values.

  2. We define custom exceptions (OutOfTeaException and PowerException) to represent specific error conditions.

  3. Instead of returning errors, we throw exceptions when an error occurs.

  4. The try-catch blocks are used to handle exceptions, similar to how Go uses if-else blocks to check for errors.

  5. D doesn’t have a direct equivalent to Go’s error wrapping with fmt.Errorf and %w. Instead, we create a new exception with additional context.

  6. To check for specific types of exceptions (similar to errors.Is in Go), we use catch clauses for specific exception types or cast within a general catch clause.

To run this program, save it as errors.d and use the D compiler:

$ dmd errors.d
$ ./errors
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This example demonstrates how D handles errors using exceptions, providing similar functionality to Go’s error handling but in a way that’s idiomatic to D.