Skip to content
AgileLogics
6 min read

Error Handling

Prerequisites: Structs

When something goes wrong, Julia throws an exception. Catch it with try/catch:

function safe_divide(a, b)
    try
        return div(a, b)
    catch err
        println("caught: ", sprint(showerror, err))
        return nothing
    finally
        println("done dividing $a by $b")
    end
end

println(safe_divide(10, 2))
println(safe_divide(1, 0))
done dividing 10 by 2
5
caught: DivideError: integer division error
done dividing 1 by 0
nothing
  • catch err binds the exception to err.
  • finally always runs — use it for cleanup.
  • sprint(showerror, err) turns an exception into its message.

Throwing errors

function withdraw(balance, amount)
    amount > 0 || throw(ArgumentError("amount must be positive"))
    amount <= balance || error("insufficient funds: balance is $balance")
    return balance - amount
end

for amount in (30, -5, 500)
    try
        println("new balance: ", withdraw(100, amount))
    catch err
        println(typeof(err), ": ", err.msg)
    end
end
new balance: 70
ArgumentError: amount must be positive
ErrorException: insufficient funds: balance is 100
  • throw(SomeException(...)) throws a specific exception type.
  • error("message") throws a generic ErrorException.
  • Built-in types include ArgumentError, DomainError, BoundsError, KeyError, MethodError, DimensionMismatch, and InexactError.

Custom exceptions and catching by type

Julia's catch doesn't filter by type, so check the type yourself and rethrow() what you don't handle:

struct ValidationError <: Exception
    field::String
    reason::String
end
Base.showerror(io::IO, e::ValidationError) = print(io, "invalid ", e.field, ": ", e.reason)

function register(email)
    occursin('@', email) || throw(ValidationError("email", "missing @"))
    return "registered $email"
end

function try_register(email)
    try
        println(register(email))
    catch err
        err isa ValidationError || rethrow()
        println("Error: ", sprint(showerror, err))
    end
end

try_register("ada@example.com")
try_register("not-an-email")
registered ada@example.com
Error: invalid email: missing @

Errors versus nothing

Exceptions are for unexpected problems. For expected "no result" cases, return nothing and let the caller check — like tryparse, findfirst, and match do. Also, try/catch is relatively slow, so don't use it for normal control flow in hot loops.

@assert

@assert cond "message" throws an AssertionError if cond is false. Use it for internal sanity checks, not for validating user input (it may be disabled at higher optimization levels in the future).

Practice

Write parse_age(s) that returns an Int or throws an ArgumentError with a helpful message for non-numeric input or ages outside 0–150. Test it on "42", "abc", and "200".

Show solution
function parse_age(s)
    n = tryparse(Int, s)
    n === nothing && throw(ArgumentError("'$s' is not a number"))
    0 <= n <= 150 || throw(ArgumentError("$n is not a realistic age"))
    return n
end

for s in ["42", "abc", "200"]
    try
        println(parse_age(s))
    catch err
        println(err.msg)
    end
end
42
'abc' is not a number
200 is not a realistic age