Exceptions
Prerequisites: Result and error handling
F# uses .NET exceptions for unexpected failures, and you'll meet them whenever you call .NET libraries.
try ... with
let safeDivide a b =
try
Some (a / b)
with
| :? System.DivideByZeroException -> None
printfn "%A" (safeDivide 10 2)
printfn "%A" (safeDivide 10 0)
let parse (s: string) =
try
int s
with ex ->
printfn "Error: %s" ex.Message
0
printfn "%d" (parse "12")
printfn "%d" (parse "twelve")
Some 5
None
12
Error: The input string 'twelve' was not in a correct format.
0
try ... with is an expression: both the try body and each handler
must return the same type. :? SomeException matches by exception type;
ex alone catches everything.
Raising exceptions
let checkAge age =
if age < 0 then invalidArg (nameof age) "must not be negative"
elif age > 150 then failwith $"unrealistic age: {age}"
else age
for age in [30; -1; 200] do
try
printfn "ok: %d" (checkAge age)
with
| :? System.ArgumentException as ex -> printfn "argument problem: %s" ex.Message
| Failure message -> printfn "failure: %s" message
ok: 30
argument problem: must not be negative (Parameter 'age')
failure: unrealistic age: 200
failwith "message"raises a general exception; theFailurepattern catches it and extracts the message.invalidArg,invalidOp, andnullArgraise the standard .NET argument exceptions.nameof agegives the string"age", which survives renaming.raise (SomeException(...))raises any exception object.
Your own exceptions
exception InsufficientFunds of balance: decimal * requested: decimal
let withdraw balance amount =
if amount > balance then raise (InsufficientFunds (balance, amount))
balance - amount
try
printfn "%M" (withdraw 100m 30m)
printfn "%M" (withdraw 100m 300m)
with
| InsufficientFunds (b, r) -> printfn "Can't withdraw %M from %M" r b
70
Can't withdraw 300 from 100
finally and use
try ... finally runs cleanup code whether or not an exception
happened. For objects that hold resources (files, connections), use
does this automatically — it calls Dispose when the value goes out of
scope:
type Resource(name: string) =
do printfn "open %s" name
interface System.IDisposable with
member _.Dispose() = printfn "close %s" name
let work () =
use r = new Resource("database")
printfn "working"
failwith "something broke"
try
try
work ()
finally
printfn "finally block ran"
with Failure msg ->
printfn "caught: %s" msg
open database
working
close database
finally block ran
caught: something broke
Notice the order: the resource closes as soon as the exception leaves
work, then the finally block runs, then the handler.
reraise
Inside a handler, reraise () rethrows the current exception with its
original stack trace — use it after logging:
try
try
failwith "disk full"
with ex ->
printfn "log: %s" ex.Message
reraise ()
with ex ->
printfn "outer handler: %s" ex.Message
log: disk full
outer handler: disk full
Common mistakes
- Catching every exception and returning a default value hides bugs.
Catch specific exceptions, or convert them to a
Resultat the edge of your program. - Using exceptions for normal control flow ("user not found") — that's
what
OptionandResultare for.