Skip to content
Exceptions and try/with
step 1/7

Reading — step 1 of 7

Learn

~3 min readHigher-Order, Exceptions, Imperative

OCaml has exceptions for genuinely exceptional conditions. Idiomatic OCaml prefers option / result for expected failures, exceptions for the unexpected.

Defining exceptions

exception Not_found_in_db
exception Invalid_input of string
exception Bad_age of int

exception Name of payload-type — like a variant constructor, but for the exception type.

Throwing — raise

let validate s =
  if s = "" then raise (Invalid_input "empty")
  else s

let head = function
  | [] -> raise (Failure "empty list")
  | x :: _ -> x

raise exn throws. There's also failwith "msg" (raises Failure) and invalid_arg "msg" (raises Invalid_argument) — short forms for common cases.

Catching — try/with

try
    let n = int_of_string (read_line ()) in
    Printf.printf "got %d\n" n
with
| Failure msg -> Printf.printf "parse failed: %s\n" msg
| End_of_file -> print_endline "no input"
  • try EXPR with PATTERN -> HANDLER | PATTERN -> HANDLER
  • Pattern-matches on the exception type
  • Re-raise with raise: with e -> log e; raise e

Common stdlib exceptions

  • Failure msg — generic; raised by failwith
  • Invalid_argument msg — bad input; raised by invalid_arg
  • Not_found — search failed (List.find, Map.find, etc.)
  • Division_by_zero — int division by zero (note: float div by zero gives infinity)
  • End_of_file — input stream ended
  • Match_failure — non-exhaustive match on input it doesn't cover
  • Stack_overflow — recursion too deep

Exceptions vs option/result

(* throwing — caller MUST handle: *)
let div a b =
  if b = 0 then raise Division_by_zero
  else a / b

(* option — caller pattern-matches: *)
let div_opt a b =
  if b = 0 then None
  else Some (a / b)

(* result — caller pattern-matches with reason: *)
let div_res a b =
  if b = 0 then Error "div-by-zero"
  else Ok (a / b)

Modern style: prefer option / result for expected failures. Exceptions for cases where the program shouldn't continue (genuinely exceptional, unrecoverable, or programmer-error situations).

try-as-expression

let n =
    try int_of_string s
    with Failure _ -> 0

try is an expression — evaluates to the body's value or the matching handler's. Useful for inline default-on-error.

Performance — exceptions are slow

Throwing in OCaml allocates and walks the stack. For control flow on hot paths, prefer option / result.

There's a special form raise_notrace that skips the stack trace (slightly faster):

raise_notrace Not_found

Use for performance-critical exception throwing where you don't need debug info.

Common mistakes

  • Using exceptions for expected failures — slower and obscures intent. Use option/result.
  • Catching all exceptionswith _ -> ... swallows everything including programmer errors. Catch specific types.
  • Forgetting to re-raisewith e -> log_error () silences the exception. Add raise e if you want it to propagate.
  • Not handling Match_failure / Not_found from library calls — most stdlib functions document what they throw; check.
  • Throwing stringsraise "oops" is a type error. Use a defined exception or Failure / Invalid_argument.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…