Manual contents

Error Handling

Feng uses throw to raise exceptions, try/catch to handle them, and defer to schedule scope cleanup.

Throw an Exception

func require_positive(value: int) {
  if value <= 0 {
    throw "value must be positive";
  }
}

You can throw scalars, bool, string, arrays, or concrete user-defined types. Functions, method values, and values viewed through a spec cannot be thrown.

Catch an Exception

try is followed by an expression, not a statement block:

try load_config() catch error: string {
  println("load failed: {0}", error);
} catch {
  println("unknown failure");
};

Multiple catch clauses are matched in source order. Put concrete type branches first. catch error: unknown and an anonymous catch are catch-all branches.

An unknown binding can only be rethrown; its fields and methods are not accessible:

try run_task() catch error: unknown {
  throw error;
};

try/catch Expressions

try/catch can produce a value:

let port = try parse_port(text) catch error: string {
  8080;
};

The normal path and every normally completing catch must produce the same type. Omitting catch creates only an exception propagation point; the exception continues to the caller.

defer

defer runs when execution leaves the current lexical scope and is useful for paired resource operations:

let file = File.create(path, FileMode.Read);
defer {
  file.close();
}

let content = file.readText();

Multiple defer blocks in one scope run in last-in, first-out order. Normal exit, return, break, continue, and exception propagation all trigger cleanup blocks that have already been registered.

C Boundaries

An exception cannot cross a C ABI boundary. An ABI function must internally handle every exception that could propagate to the boundary. A C function should report errors through return values, error codes, or callback conventions.