expected that err
match Exception
err = ArgumentError.new("error")
err_types = [Exception, RuntimeError]
if err_types.any? { |t| err.is_a?(t) } # Error: unexpected token: "t"
puts "type match"
end
expected that err
match Exception
err = ArgumentError.new("error")
err_types = [Exception, RuntimeError]
if err_types.any? { |t| err.is_a?(t) } # Error: unexpected token: "t"
puts "type match"
end
is_a?
is a compile primitive and only works with a constant value.
At runtime you can use Class#===(other)
to check if other
is an instance of that class: t === err
.
What is wrong here?
err = ArgumentError.new("error")
err_types = [IO::Error, Base64::Error]
p! IO::Error === err
p! Base64::Error === err
match = err_types.any? { |t|
p! t
t === err
}
p! match
result:
IO::Error === err # => false
Base64::Error === err # => false
t # => IO::Error
match # => true
This seems like the same issue as `Class#cast` doesn't work correctly on virtual receivers · Issue #13706 · crystal-lang/crystal · GitHub where Class#===
isn’t correctly instantiated for each metaclass
Seems like this is a bug, so is there any other way to solve my problem
What is the problem you’re trying to solve tho? Seeing if an exception type is one of set number of exception types? Technically in your example since you have Exception
, that would always be true, no matter what other exception types are in err_types
. In which case it would be sufficient to just do:
if err.is_a? Exception
pp "type match"
end
Or worst case, just manually ||
all the types together . Or maybe use
case
?
case err
when Exception, RuntimeError then pp "type match"
end
Look at the code of my first question, what to do if the match type is a variable
err_types = [Exception, RuntimeError]
A simple scenario: the user configures a set of Excepiton types (not hard coded) that need to be intercepted. If an Exception occurs and matches them during the running of the program, the program will retry
I think what @Blacksmoke16 means is why you have the types in an array to begin with. If you’re trying to define error types to rescue, receiving an actual type instead of an array of class objects would be more idiomatic.
For example, notice how I’m passing in a union of all the exception types I want to capture into the capture
method call here:
def capture(error_type : T.class) forall T
yield
rescue ex : T
puts "CAUGHT! #{ex}"
end
10.times do
capture(ArgumentError | TypeCastError) do
if rand < 0.5
# ArgumentError
"asdf".to_i
else
# TypeCastError
"asdf".as(String | Int32).as(Int32)
end
end
end
This is the reason you received the original compiler error. Types and variables containing references to classes are not parsed the same way in Crystal, so you can’t deal with them the same way as you would in Ruby. If you’re dealing with types, you probably want to handle them as types rather than as variables.
How to constrain T to be a subtype of Exception
That constraint is already enforced by the rescue
clause. For example, if I change the type to ArgumentError | String
, I get this error at compile time:
In dynamic_rescue.cr:3:13
3 | rescue ex : T
^
Error: (ArgumentError | String) is not a subclass of Exception