Structured. Warnings. Now.

by Daniel Berger

The Problem

I find Ruby’s current warning system, if you can call it that, lacking. Warnings are controlled by the -W flag on the command line, and are generated via the Kernel#warn method within code. There are a host of problems with this approach to warnings.

First, warnings aren’t currently testable. With Test::Unit, for example, I can ensure that specific errors are raised in certain conditions via the assert_raise method. There is no analogue for warnings. It would be nice if there were so I could test them.

Second, there is no backtrace information provided with warnings. If I discover a warning I have to wade through the source and figure out where it was generated, because a Kernel#warn call does not provide a line number or method name that I can refer back to.1 For large code bases that can be problematic and generally annoys me.

Third, and most significantly, with warning flags it’s all or nothing. I cannot enable or disable specific kinds of warnings. Perl, on the other hand, implements warning control through pragmas. So, for example, I can specify “no warnings uninitialized” in a Perl program and warnings about uninitialized variables go away.2 With Ruby it’s off, on, or even-more-on (-W0, -W1 or -W2).

The Solution

One of the things I’ve pushed for in the past in Ruby is structured warnings.3 By ’structured warnings’ I mean a system analogous to the Error class, except that a warning would only emit text to STDERR, not cause the interpreter to exit. In our hypothetical Warning class you still have backtrace information available. And, like Exceptions, there would be a standard hierarchy, with Warning at the top, StandardWarning, UninitializedWarning, RedefinedMethodWarning, DeprecatedMethodWarning, etc. Whatever we can think of.

Such a system would allow you to raise specific warnings within your code:

   class Foo
      def old_method
         warn DeprecatedMethodWarning, 'This method is deprecated. Use new_method instead'
         # Do stuff

The ability to explicitly raise specific types of warnings then makes them testable:

   require 'test/unit'
   class TC_Foo_Tests < Test::Unit::TestCase
      def setup
         @foo =

      # Assume we've added an assert_warn method to Test::Unit
      def test_old_method
         assert_warn(DeprecatedMethodWarning){ @foo.old_method }

And, for sake of backwards compatibility and convenience, a call to Kernel#warn without an explicit warning type would simply raise a StandardWarning in the same way that raise without an explicit error type raises a StandardWarning.4

Unlike Exceptions you could permanately or temporarily disable warnings to suit your particular preferences in the system I have in mind. For example, in the win32-file library I'm well aware that I've gone and redefined some core File methods. When I run any code that uses win32-file with the -w flag, I get "method redefined" warnings. I don't want to see those because I neither need nor want to be reminded about them.

So, using our hypothetical RedefinedMethodWarning class, I could disable them like so:

RedefinedMethodWarning.disable # No more warnings about method redefinitions!

Or, with block syntax, we could disable a particular warning temporarily:

# Don't bug me about deprecated method warnings within this block, I know
# what I'm doing.
   [1,2,3,4,5].indexes(1,3) # Array#indexes is a deprecated method

# But here I would get a warning since it's outside the block:

Unlike the current warning system, this would allow users to still receive other types of warnings, instead of the on/off switch we have now.5And, in case you were wondering why I don’t just create a ‘warnings’ library that defines a bunch of warning classes and redefines Kernel#warn, the answer is that I still can’t hook into the existing warnings being raised in core Ruby via rb_warn(), like uninitialized variables or redefined methods.

With our warning system in place we could use it for other nefarious purposes down the road, like implementing an advisory typing system. But, I’ll save that for the next post. :)

See you next Wednesday!

1Curiously, a line number is provided by the rb_warn() function, but Kernel#warn itself does not.

2However, I do not remember if you can disable them temporarily in Perl, or if there’s a way to explicitly test them. Someone feel free to fill me in.


4At this point I’m sure one of you is wondering about rescue/retry semantics. My opinion on the matter is that warnings should not be rescuable. They are meant to be informational. They are not meant to control program flow. This also lets us avoid having to worry about retry semantics. Not that anyone would retry based on a warning in practice.

5Yes, there would have to be a Warning.enable method as well, for those times you want to trump some third party library that has them disabled, in a “last call wins” arrangement.


Dave Rolsky
2008-02-20 20:03:24
In Perl, warnings can be disabled lexically like this:

use warnings;


no warnings 'uninitialized';

print $may_be_undef + 0;

At the end of the block, all warnings are once again enabled. As far as testing them, of course you can, because you can "catch" warnings with $SIG{__WARN__}, and since this is Perl, there's a module on CPAN to help you, Test::Warn.

Gregor Schmidt
2008-02-21 04:00:06
I really like your proposal, so I started implementing it.

I just release a gem called "structured_warnings", that provides this functionality. I hope I did not step on your toe with this.

2008-02-21 19:01:23
I agree. This looks like a great idea. I never quite understood why warnings were fundamentally different from exceptions --which, btw, makes me wonder if this could actually be implemented piggy-back on top of the exception system somehow?