Exception Handling
In Crystal, the keyword raise is used to raise exceptions. When raised, the exception
will unwind the call stack and it'll hopefully be caught by the programmer somewhere
higher up in the method chain. If it's not caught, the program will exit with an unhandled
exception error.
If you bubble up an exception with raise and catch it, the regular execution
of the program will continue.
Handling exceptions with begin - rescue blocks:
arr = [] of Int8
puts "Enter an Int8 value:"
while number = gets
number = number.strip
if number == "" || number == "stop"
break
end
# Catching exceptions in the begin block:
begin
arr << number.to_i8
rescue ex
# Reacting to the exception:
p ex.message
puts "The integer you've entered is bigger than Int8 capacity"
exit
else
# In case where everything is good:
p "Int8 added to the array."
p arr
ensure
# Ensure block executes no matter what:
p "Clean up with ensure block..."
end
end
It's also possible to use a shorter syntax alternative:
def add_i8(arr, number)
arr << number.to_i8
rescue
p ex.message
puts "The integer you've entered is bigger than Int8 capacity"
end
Shorter variant with the addition of ensure:
def add_i8(arr, number)
arr << number.to_i8
rescue
p ex.message
puts "The integer you've entered is bigger than Int8 capacity"
ensure
p "clean up happened."
end
A note on performance:
Using predicates like
to_i?orto_f?with if blocks is much faster and less resource intensive compared to exception handling because exception handling allocates more memory. If you choose to usebegin-rescueblocks, try to keep the amount of code betweenbeginandrescueminimal.
Idiomatic Patterns
An idiomatic pattern is to return nil from an inner function, and catch an outer
function which raises an exception when it detects the nil return. This exception
then can be caught by the caller site.
Here's an example:
# Private implementation
def say_hi(name : String) : String | Nil
name.size > 2 ? "Hi, #{name}" : nil
end
# Public implementation
def say_hi_public(name : String)
msg = say_hi name
unless msg
raise "say_hi error: Name too short"
else
msg
end
end
# Caller site
begin
p say_hi_public "Jo"
rescue error
p error
end
Let's rewrite the previous example using shorter syntax where possible:
# Private implementation
def say_hi(name : String) : String?
name.size > 2 ? "Hi, #{name}" : nil
end
# Public implementation:
def say_hi_public(name : String)
raise "say_hi error: Name too short" unless say_hi name
end
# Caller site:
def caller_site
say_hi_public("Jo") # <Exception:say_hi error: Name too short>
rescue ex
p ex
end
caller_site