Error Handling

View errorHandling.chpl on GitHub

This primer is a short tutorial on Chapel error handling. A detailed description can be found in the Error Handling Modes and Prototype Modules technote.

Handling Errors

Several important standard library functions throw errors so that developers can handle the errors as they see fit. This is especially useful for recovering from errors in IO. The try and catch constructs enable handling of errors. Any errors caught by a catch block are owned errors and can be rethrown. try! is available to halt if an error is thrown but not handled.

use IO;

const f1 = "input.txt";
const f2 = "backup_input.txt";

var f: file;

  // halts on error
  // f = try! open(f1, ioMode.r);

try {
  f = open(f1, ioMode.r);
  // if open() raises an error, jump to the catch block
  writeln("everything is fine");
} catch {
  // catch block are used to handle errors
  writeln("an error occurred");
}

try {
  f = open(f1, ioMode.r);
} catch e: FileNotFoundError {
  // catch block can be directed to only handle certain errors
  // the error caught will be `owned FileNotFoundError` in this case
  writeln("Warning: ", f1, " does not exist");
  try! {
    f = open(f2, ioMode.r);
  } catch e: FileNotFoundError {
    writeln("Warning: ", f2, " does not exist");
  }
  // halts if a different error is returned
} catch {
  // catchall is needed because main() does not throw
  writeln("unknown error");
}

Throwing Errors

Developers can also write their own throwing functions, throwing and consuming errors. While there are built-in OS.SystemError types, developers can also create their own hierarchies.

Only owned instances that are subtypes of Error can be thrown.

class EmptyFilenameError : Error {
  proc init() {
  }
}

proc checkFilename(f_name: string) throws {
  if f_name.isEmpty() then
    throw new EmptyFilenameError();
}

proc openFilename(f_name: string) throws {
  var f: file;

  try {
    f = open(f, ioMode.r);
  } catch e: FileNotFoundError {
    writeln("Warning: ", f, " does not exist");
  } // throws all other errors

  return f;
}

Modules

Implicit modules, prototype modules, and explicit modules have different minimum requirements for error handling. This encourages rapid prototyping at the outset while enforcing safety in production.

Implicit modules will halt if an error is unhandled.

checkFilename(f1); // halts on error

proc doesNotThrow() {
  checkFilename(f2); // halts on error
}

proc throwOn() throws {
  checkFilename(f2); // throws on error
}

Prototype modules will also halt if an error is unhandled.

prototype module P {
  checkFilename(f1); // halts on error

  proc doesNotThrow() {
    checkFilename(f2); // halts on error
  }

  proc throwOn() throws {
    checkFilename(f2); // throws on error
  }
}

Explicit modules require errors to be completely handled, except when the calling function also throws.

module R {
  // not permitted, error must be handled completely
  // checkFilename(f1);
  try! checkFilename(f1);

  proc doesNotThrow() {
    // not permitted, error must be handled completely
    try {
      checkFilename(f2);
    } catch {
      writeln("handled completely");
    }
  }

  proc throwOnExplicit() throws {
    checkFilename(f2); // still works, throws on error
  }
}

Strict Mode may be enabled on any module, requiring that all throwing calls be marked with try.