I guess we could almost implement this entirely in Crystal. For example:
require "benchmark"
class ThrowError(T) < Exception
getter value : T
def initialize(@value : T)
end
end
def catch(value : T) forall T
begin
yield
rescue error : ThrowError(T)
if error.value == value
return
else
raise error
end
end
end
def throw(value)
raise ThrowError.new(value)
end
# This is similar to `raise(Exception)` except that it doesn't compute a callstack.
def raise(exception : ThrowError(T)) : NoReturn forall T
unwind_ex = Pointer(LibUnwind::Exception).malloc
unwind_ex.value.exception_class = LibC::SizeT.zero
unwind_ex.value.exception_cleanup = LibC::SizeT.zero
unwind_ex.value.exception_object = exception.as(Void*)
unwind_ex.value.exception_type_id = exception.crystal_type_id
__crystal_raise(unwind_ex)
end
Benchmark.ips do |x|
x.report("raise/rescue") do
begin
raise "OH NO!"
rescue
end
end
x.report("throw/catch") do
catch(:foo) do
throw :foo
end
end
end
Results are:
raise/rescue 680.32k ( 1.47µs) (± 2.33%) 256B/op 1.66× slower
throw/catch 1.13M (887.09ns) (± 3.23%) 128B/op fastest
The only thing else we’d need to do is to avoid rescuing ThrowError
when you do rescue e
or rescue e : Exception
. Maybe try to allocate even less memory, but I’m not sure it’s possible.
But then, I’m not sure this is all worth it. If it’s only going to be twice as fast, maybe it doesn’t make much difference.