Tracing global variables in Ruby using trace_var
Recently, I came across this “interesting” approach to printing the “99 bottles of beer” song on Rosetta code.
Wait… WHAT!?
This is unlike any Ruby code I’ve seen before.
What’s trace_var
?
What are those global variables doing?
Let’s start by looking at this trace_var
thing.
The Kernel#trace_var
documentation says:
Controls tracing of assignments to global variables.
… the block is executed whenever the variable is assigned. The block or Proc object receives the variable’s new value as a parameter.
Let’s try something simple with this.
If you pass in a string instead of a block or Proc, it gets eval’ed.
If you need to unset tracing with trace_var
,
you can do untrace_var :$foo
.
Now that we’ve seen how trace_var
works,
let’s go back to the “99 bottles” program.
It’s starting to make sense now.
This assigns the string
"#{$bottle_num} bottles"
to the $bottles
variable,
except when $bottle_num
is zero,
in which case “No more bottles”
is assigned.
So each time $bottle_num
gets changed,
the $bottles
string gets updated automatically.
Debugging with trace_var
trace_var
can be a useful debugging tool.
Suppose you want to find out
which line of code is
changing a global variable.
You could do this:
This will show you assigned value and the execution stack at that point.
But if you ever find yourself debugging your code like this, maybe it’s a good time to get rid of those global variables. They are bad for your codebase.
trace_var
only works with global variables.
If you need a more flexible tool
you might want to look at Ruby 2.0’s new
TracePoint API.
More weird Ruby
If you found this post interesting, you might also enjoy my other articles about lesser known features of Ruby, such as the flip flop operator, tail call optimization, memoization using metaprogramming tricks, or using Ruby as an alternative to sed/awk.