Choosing Error Handling Strategies in OCaml: Result vs. Exceptions
Learn when to use OCaml's Result type versus exceptions for error handling. This guide compares performance, type safety, and composability to help you design robust library APIs.
08 Apr 2026, 04:07 UTC

The Error Handling Dilemma
When designing an OCaml library API, the primary challenge is deciding how to signal failure without compromising type safety or performance. The core problem is a trade-off between explicitness (knowing exactly where a function can fail) and conciseness (avoiding repetitive error checking).
The takeaway: Use the Result type for expected, recoverable failures that the caller must handle. Reserve Exceptions for truly exceptional, unrecoverable system failures (e.g., out of memory or corrupted configuration files) where the only sane response is to crash or restart the process.
Comparison of Error Strategies
OCaml provides three primary ways to handle non-ideal execution paths. The choice depends on whether you need to communicate why a failure happened and how much control flow overhead you can tolerate.
| Feature | 'a option |
('a, 'b) result |
Exceptions (exn) |
|---|---|---|---|
| Error Detail | None (only None) |
Custom (via Error 'b) |
Detailed (via exn) |
| Type Visibility | Explicit in signature | Explicit in signature | Implicit/Hidden |
| Control Flow | Manual matching | Monadic (let*) |
Automatic propagation |
| Performance | Very High | High | Lower (Stack unwinding) |
Trade-offs and Engineering Constraints
The Case for Result
The Result type (introduced in OCaml 4.03) forces the developer to acknowledge the failure case at compile time. By returning Ok v or Error e, the function signature becomes a contract. This prevents the "hidden crash" scenario common in languages that rely heavily on unchecked exceptions.
Using Result enables functional composition. With the let* syntax (available via the Base library or by defining a binding operator), you can chain multiple fallible operations. If any step returns Error, the entire chain short-circuits and returns that error immediately.
The Case for Exceptions
Exceptions are useful when a failure is so catastrophic that passing an Error value back up ten levels of the call stack would create unnecessary boilerplate. They provide an "escape hatch" that bypasses the normal return path. However, they break purity and can inhibit tail-call optimization, potentially leading to stack overflows in deep recursions if not handled carefully.
The Case for Option
Option is the most lightweight approach. It is ideal for lookups (e.g., finding a key in a map) where the absence of a value is a normal business case rather than an "error." If the reason for the failure is irrelevant to the caller, Option is the correct choice.
Implementation: Recoverable Parsing
The following example demonstrates how to wrap a potentially crashing standard library function (int_of_string) into a safe Result type to ensure the caller handles the failure.
(* Required for let* syntax in modern OCaml *)
let ( let* ) = Option.bind (* Simplified for Result context below *)
(* A wrapper that converts an exception into a Result value *)
let parse_int s =
try Ok (int_of_string s)
with Failure _ -> Error ("cannot parse integer: " ^ s)
(* Composing two fallible operations *)
let add_parsed a b =
let result =
match parse_int a with
| Error e -> Error e
| Ok x ->t
match parse_int b with
| Error e -> Error e
| Ok y -> Ok (x + y)
in result
Validation and Testing
To verify this implementation, create a file named test.ml and add a small harness:
let () =
match add_parsed "10" "20" with
| Ok res -> Printf.printf "Success: %d\n" res
| Error e -> Printf.printf "Error: %s\n" e;
match add_parsed "10" "abc" with
| Ok res -> Printf.printf "Success: %d\n" res
| Error e -> Printf.printf "Error: %s\n" e
Execution: Run the following command in your terminal (assuming OCaml 4.03+):
ocamlc -o test test.ml && .\/test
Expected Result:
- First call:
Success: 30 - Second call:
Error: cannot parse integer: abc
Limitations and Risks
- Mixing Paradigms: Avoid mixing
Resultand exceptions in a single logical flow. If a function returns aResultbut internally calls a function that raises an unhandled exception, theResulttype becomes a lie, as the program will crash before theErrorvalue can be returned. - Version Compatibility: If targeting OCaml versions older than 4.03, the
Resulttype is not in the standard library. You must define your own variant:type 'a result = Ok of 'a | Error of string. - Boilerplate: Without a monadic library (like
BaseorCore), manually matching onOk/Errorcan become verbose.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.