Throwing Initializers

Overview

Initializers (see Class Initializers) and Error Handling are currently in the process of being integrated together. Support for the overlap of these features is currently limited, but key functionality has been enabled.

Using Catch-less try!

try! can be used anywhere in the body of any initializer, so long as it does not have an associated catch clause. When used, any error encountered will cause the program to halt.

Example (try-bang-init.chpl).

proc throwingFunc(x: int) throws {
  if (x > 10) {
    throw new Error("x too large");
  } else {
    return x;
  }
}

record Foo {
  var x: int;

  proc init(xVal: int) {
    x = try! throwingFunc(xVal); // try! here is legal
    /* The following code is not legal in an initializer yet, due to
       having a catch clause:

      try! {
        x = throwingFunc(xVal);
      } catch e: Error {
        x = 9;
      }
    */
  }
}

Declaring Initializers as throws

Initializers can be declared with the throws keyword. This enables uncaught errors in the initializer body to be propagated outside of the initializer (see Calling Throwing Functions). When an error is thrown, the memory that would have been used for the result of the initializer call will be cleaned up before the error is thrown back to the caller.

Example (init-declared-throws.chpl).

proc validate(r: R) throws {
  if (r.x > 10) {
    throw new Error("x too large");
  }
}

record R {
  var x: int;

  proc init(xVal: int) throws {
    x = xVal;
    this.complete();
    validate(this);
  }
}

Calling Throwing Functions

When an initializer is declared with the throws keyword, calls to throwing functions may be made in the body of the initializer after this.complete() (see Limitations on Instance Usage in Initializers for information on this.complete() and the example in Declaring Initializers as throws). As a result, thrown errors will be propagated outside of the initializer. The memory that would have been used to store the instance created by the initializer will be cleaned up prior to propagating the error.

Note

Calls to throwing functions are not currently allowed prior to this.complete().

When an initializer is not declared with the throws keyword, calls to throwing functions may be made anywhere in the body of the initializer. Such calls will cause the program to halt (see Error Handling) if errors are encountered.

Future Work

We intend to fully support throwing initializers in the future. This will include:

  • being able to throw from anywhere in the body of an initializer

  • being able to write try / try! with catch blocks anywhere in the body of an initializer

  • being able to call functions that throw prior to this.complete() (see Limitations on Instance Usage in Initializers for a description) - including super.init calls when the parent initializer throws, e.g.,

    class A {
      var x: int;
    
      proc init(xVal: int) throws {
        x = xVal;
        this.complete();
        someThrowingFunc(this);
      }
    }
    
    class B : A {
      var y: bool;
    
      proc init(xVal: int, yVal: bool) throws {
        super.init(xVal); // This call is not valid today
        y = yVal;
        this.complete();
      }
    }