In Flix, all error handling should be done using the Result[e, t] type. However, for interoperability with Java, Flix also has a classic try-catch mechanism.

For example, we can write:

/// Returns `true` if the given file `f` exists.
pub def exists(f: String): Result[String, Bool] \ IO =
    try {
        import java_new java.io.File(String): ##java.io.File \ IO as newFile;
        import java.io.File.exists(): Bool \ IO;
    } catch {
        case ex: ##java.io.IOException =>
            import java.lang.Throwable.getMessage(): String \ IO;

Here we import the File constructor as newFile and the File.exists method as exists. We then call the methods and catch the IOException.

Note: Flix programs should not rely on the exception mechanism. Instead, we should guard all call to Java code that might throw exceptions close to their call site and turn these exceptions into Results.

Structured Concurrency and Exceptions

Flix supports structured concurrency. This means that (1) threads cannot outlive the lifetime of their region and (2) that exceptions thrown in sub-threads are propagated to the thread of the region.

For example, given the program:

def main(): Unit \ IO =
    region rc {
        spawn f() @ rc;
        spawn g() @ rc

where f and g are some functions. If f or g were to throw an unhandled exception then that exception would be caught and rethrown inside the main thread. This means that we cannot successfully leave the scope of rc unless f and g terminated and did not throw any unhandled exceptions.