OCaml for the Skeptical
U of C Library DLDC Informal OCaml Class

Exception Handling

OCaml has exception handling to deal with exceptions, application-level errors, out-of-band data and the like. Exceptions are a very dynamic construct, but there is a third-party application that performs a static analysis of your use of exceptions and can find inconsistencies and errors at compile time!

Exceptions can (optionally) take a parameter, so that they can carry a value with them that can be extracted and examined if the exception is caught. Exception names must begin with an initial uppercase letter. They are constructors that return a given exception value.

Exceptions are first-class values in OCaml, and can be stored and manipulated like any other value.

There are a number of exceptions defined in the Core Library, and many modules in the Standard Library and from third-parties define their own exceptions. You can do so as well. Three of the exceptions in the Core Library are particularly useful for you to raise in your own functions:

Raising Exceptions

Exceptions are raised with the raise function, which takes an exception as an argument and raises or throws it: as a result, raise never returns, so it doesn't make sense to ask what its value is. Note that you frequently need to parenthesize an exception constructor when applying it to a parameter. This is not special syntax but merely falls out of the precedence rules; for example:

    # raise Not_found;;
    Exception: Not_found.
    # raise Failure "foo";;
    The constructor Failure expects 1 argument(s),
    but is here applied to 0 argument(s)
    # raise (Failure "foo");;
    Exception: Failure "foo".
    # let ex1 = Failure "foo"
      and ex2 = Not_found
      and ex3 = Invalid_argument "bar";;
    val ex1 : exn = Failure "foo"
    val ex2 : exn = Not_found
    val ex3 : exn = Invalid_argument "bar"
    # [ex1;ex2;ex3];;
    - : exn list = [Failure "foo"; Not_found; Invalid_argument "bar"]
    # raise ex1;;
    Exception: Failure "foo".
    # raise (List.hd [ex1;ex2;ex3]);;
    Exception: Failure "foo".
    #

Handling Exceptions

Exceptions can be useful even if not caught, but frequently you will want to catch them. This is done with a try expr with pattern-matching expression. The expr is an expression that you want to protect, such that if an exception occurs during its evaluation, it is caught and examined in the pattern-matching; if the particular exception is matched, its handler is evaluated and the result is returned as the value of the entire try expression. If the exception isn't matched, it is re-raised and propagated upward. If there was no exception raised in the evaluation of expr, then the value of expr is returned as the value of the entire try expression. Of course you can nest try's (directly or indirectly through intervening functions) and the innermost matching handler is the one that "wins".

Pattern matching is a topic in itself, but here's enough to show how exception handling works. The pattern-matching part of the try is a sequence of handlers, each one of the form: pattern -> expr, separated by vertical bars (|). Here the simplest case of one handler:

    # try 100 / 0 with Division_by_zero -> 69;;
    - : int = 69
    # let f n = try 100 / n with Division_by_zero -> 69;;
    val f : int -> int = <fun>
    # [f 1; f 2; f 3; f 0];;
    - : int list = [100; 50; 33; 69]
    #

Here's an example of handling several different exceptions at once; note that input_line raises the exception End_of_file to indicate, well, end of file:

    # try print_endline (input_line (open_in "/dev/null")) with
	Not_found -> print_endline "Shouldn't happen" 
      | End_of_file -> print_endline "EOF"
      ;;
    EOF
    - : unit = ()
    #

This example is somewhat bogus because, as far as I know, the input_line function can't raise Not_found; still, it's legal for me to test for it. It's also bogus because I open a file and don't save the opened channel; hence I can never close it, and in a real program this would be a file descriptor leak! Note that the expression prints "EOF" because I try to read a line from /dev/null, which will always get an immediate end of file. As usual, formatting of the try is completely free-form: the above is just my style.

When handling exceptions that take parameters, like Failure, you may want to be able to examine the parameter. OCaml's pattern matching construct can bind that parameter to a name that is bound within the handler's expr (only):

    # try raise (Failure "this is why") with 
	Failure explanation -> print_endline explanation
      ;;
    this is why
    - : unit = ()
    #

Here explanation is the variable name.

In some cases you're uninterested in the parameter. In this case the best thing to do is use the magic name _ as the name of the parameter (or even as the names of several); this tells the compiler that it doesn't actually need to perform the binding and may save you memory or time (or both).

    # try raise (Failure "this is why") with 
	Failure _ -> print_endline "blowed up real good"
      ;;
    blowed up real good
    - : unit = ()
    #

Defining Exceptions

You can define your own exceptions with the exception declaration. Defining a parameterless exception is this easy:

    exception Foo

Just don't forget the initial uppercase letter in your constructor name. Defining an exception that takes a parameter has this syntax:

    exception name of type

As you can see, an exception's parameter can be of any type, including compound types to let you return lots of information. For example:

    # exception Foo of string;;
    exception Foo of string
    # exception Bar of int;;
    exception Bar of int
    # exception Foobar of int * string;;
    exception Foobar of int * string
    # raise (Foo "yikes");;
    Exception: Foo "yikes".
    # raise (Bar 12);;
    Exception: Bar 12.
    # raise (Foobar (12,"yikes"));;
    Exception: Foobar (12, "yikes").
    #

As a realistic example of an exception with a compound parameter, the Unix module defines this exception for Posix errors:

    exception Unix.Unix_error of (Unix.error * string * string)

The first component is the error code, a special type defined in the Unix module; the second component is the name of system call in which the error occurred; the third component is the string parameter to the function, if it has one (it might be a filename for example), or the empty string otherwise.