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 throwing Initializers

Like typical routines, initializers and post-initializers (postinit() procedures) can be declared with the throws keyword. This enables errors encountered during object initialization to be thrown back to the calling context. When an error is thrown during either of these calls, the compiler ensures that the deinit() routine for each initialized field is called, and that the memory allocated to store the object is freed.

Note that, at present, this feature has the following limitations:

  • Initializers can only throw errors after the init this statement (see Limitations on Instance Usage in Initializers for more information about init this). One implication of this is that the initializer of a superclass may not throw since its invocation precedes the init this; statement.

  • Initializers can only throw errors by making calls to throwing routines, not by directly executing throws statements.

The following is an example of a throwing initializer that relies on a throwing helper procedure, validate(), called after its init this; statement:

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;
    init this;
    validate(this);
  }
}

As in typical procedures, if an initializer is not declared with the throws keyword, yet makes a call that throws an error, the program will halt if errors are encountered (see Error Handling).

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 init this (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;
        init this;
        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;
        init this;
      }
    }