Here's a silly thing you can do with the Ruby parser:
begin
raise "omg"
rescue =>
(class Box
class << self
attr_accessor :contents
end
end; Box).contents
end
puts Box.contents
# => "omg"
You... what?
So let's break this down. In Ruby, the way one catches exceptions is with a
begin...rescue...end
expression. The rescue
part of that looks like this:
rescue <class_name> => <assignable_expression>
. Usually you'd see something
like:
begin
... whatever
rescue SomeClass => some_local_variable_name
do_something_with_local_variable(some_local_variable_name)
end
However, it doesn't have to be this way. In fact, the Ruby parser permits any
valid assignment expression in Ruby! So we can for example set a key in a hash
from a rescue:
h = {}
begin
raise "omg"
rescue => h[:the_key]
end
puts h.inspect
# => {:the_key=>#<RuntimeError: omg>}
So, to come back to our first example:
begin
raise "omg"
rescue =>
(class Box
class << self
attr_accessor :contents
end
end; Box).contents
end
puts Box.contents
# => "omg"
Here we, in the rescue clause, define a class called box, define a class
level attribute called contents, and then set it. You shouldn't ever do this in
you production code, but it is possible.
Why is this even possible?
As far as I can tell, the reason why this works as it does has to do with the
history of the implementation of Ruby. Before Ruby 1.9, Ruby used a "tree
walking" interpreter. This means that Ruby code was executed from the syntax
tree of the program, with no intermediate steps. Putting the rescued exception
"somewhere", conceptually, is the same thing as variable assignment, even though
it doesn't look like local_name =
, so the code was shared between rescue and
variable assignment.
Neat!
If you enjoyed this post, please consider sending me a follow on twitter:
@penelope_zone.
Top comments (1)
Thanks for sharing! This is awesome and Iād love to see more.