Ruby Tutorials

Tutorials on Ruby specific concepts.

5

SEP
2009

eval() Isn't Quite Pure Evil

I was explaining method lookup to the Lone Star Ruby Conference attendees and needed to show some trivial code like this:

module B
  def call
    puts "B"
    super
  end
end

module C
  def call
    puts "C"
    super
  end
end

module D
  def call
    puts "D"
    super
  end
end

Unfortunately, my space was limited due to the code being on slides and me needing to show more than just what you see above. I cracked under the pressure and committed two major programming sins. First, I collapsed the code with eval():

%w[B C D].each do |name|
  eval <<-END_RUBY
  module #{name}
    def call
      puts "#{name}"
      super
    end
  end
  END_RUBY
end

Then I really blew it when I jokingly apologized to the audience for using eval().

I got the first email with corrected code before I even finished the speech. OK, the email was from a friend and he wasn't mean, but he still instantly needed to set me straight. I had clearly turned from the light.

Only, his corrected code didn't quite work. It got close, but it had bugs. Still the approach was sound and it could be made to work. Let me fix the bugs and show you what was recommended:

%w[B C D].each do |name|
  mod = Module.new do
    define_method :call do
      puts name
      super()
    end
  end
  Object.const_set(name, mod)
end

Is that better? It is a line shorter and it avoids the use of eval(), which we all know to be pure evil. I found it a bit harder to follow the closure logic here though and remember that the point was to show clear code on a slide. I also wouldn't want to have to explain to anyone why the parentheses are required on the super() in that example, if only because it has nothing to do with the real point of the code.

That leads us to the natural question: why is eval() pure evil?

I hear two complaints most often when this talk comes up. The first is that eval() is dangerous. That usually means that we could take in some malicious code from a user and pass it into eval() where it would destroy our hard drive, or worse. Only, I'm not doing that. I made the variables here and user input is not involved. I think we're safe this time.

The other reason people tend to complain is that it's too much tool for the job. What does that mean? Is it slower because it includes extra busy work for the computer? Nope:

$ cat time_eval.rb
#!/usr/bin/env ruby -wKU

require "benchmark"

TESTS = 10_000
Benchmark.bmbm do |results|
  results.report("eval():") { TESTS.times {
    %w[B C D].each do |name|
      eval <<-END_RUBY
      module #{name}
        def call
          puts "#{name}"
          super
        end
      end
      END_RUBY
    end
  } }
  results.report("closures:") { TESTS.times {
    %w[B C D].each do |name|
      mod = Module.new do
        define_method :call do
          puts name
          super()
        end
      end
      Object.const_set(name, mod)
    end
  } }
end
$ ruby time_eval.rb 2> /dev/null
Rehearsal ---------------------------------------------
eval():     0.940000   0.080000   1.020000 (  1.027164)
closures:   1.070000   0.150000   1.220000 (  1.216268)
------------------------------------ total: 2.240000sec

                user     system      total        real
eval():     0.950000   0.080000   1.030000 (  1.034849)
closures:   1.070000   0.150000   1.220000 (  1.219367)

Every time I tried that test eval() came out the same or faster. It always seems to be faster on the calling side:

$ cat time_eval_calls.rb 
#!/usr/bin/env ruby -wKU

require "benchmark"

class A
  def call
    warn "A"
  end
end
%w[B C D].each do |name|
  eval <<-END_RUBY
  module #{name}
    def call
      warn "#{name}"
      super
    end
  end
  END_RUBY
end
class E < A
  include B
  include C
  include D
  def call
    warn "E"
    super
  end
end

class F
  def call
    warn "F"
  end
end
%w[G H I].each do |name|
  mod = Module.new do
    define_method :call do
      warn name
      super()
    end
  end
  Object.const_set(name, mod)
end
class J < F
  include G
  include H
  include I
  def call
    warn "J"
    super
  end
end

TESTS = 10_000
Benchmark.bmbm do |results|
  results.report("eval():")   { TESTS.times { E.new.call } }
  results.report("closures:") { TESTS.times { J.new.call } }
end
$ ruby time_eval_calls.rb 2> /dev/null
Rehearsal ---------------------------------------------
eval():     0.210000   0.100000   0.310000 (  0.315426)
closures:   0.270000   0.120000   0.390000 (  0.389828)
------------------------------------ total: 0.700000sec

                user     system      total        real
eval():     0.210000   0.100000   0.310000 (  0.314385)
closures:   0.270000   0.120000   0.390000 (  0.390397)

That makes sense if you think about it. The closure version will need to hunt through the scopes and resolve those variables with each call. The eval() call builds a normal Ruby method though, so once it executes it's just a normal method call.

I guess I'm all out of objections.

I agree it's sometimes worth avoiding eval(). However, I think we need to admit that it's sometimes OK to use eval(). The trick is to ditch the fear and rationally evaluate which of the two you are facing.

Comments (24)
  1. Darrick Wiebe
    Darrick Wiebe September 5th, 2009 Reply Link

    I absolutely agree that eval() should have a place in any Ruby metaprogrammer's toolbox. In most cases eval() is the simplest solution that works, and should be chosen on that basis alone.

    I think that evaling a String should be considered the preferred technique for defining methods on the fly or performing instance_evals or class_evals unless certain conditions are met which tilt the balance in favor of using a block. If I need to pull variables out of the scope that is defining my meta code, and if they are not simply basic Strings, Symbols or Numerics, then it makes sense to spring for the conceptual overhead of understanding the block scoping of the eval method you need. Otherwise—and this is most cases—it's probably best to interpolate what you need and into the String and eval it.

    In most cases, the small difference in performance between the two options is not worth considering and in order to avoid the trap of premature optimization it should not be.

    None of what I'm saying here takes into account the possibility of evaling user input, and if anyone is considering doing any kind of metaprogramming based on user input, their most important decision is probably not which kind of gun to shoot themselves in the foot with anyway.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  2. Benjamin Kudria
    Benjamin Kudria September 7th, 2009 Reply Link

    eval() is evil because it turns your code into a String.

    Strings are for data, and putting your code into data destroys the ability of all sorts of useful tools, like code coverage, cyclomatic complexity calculators, and all sorts of other static analyzers—I'm not even going to mention editor syntax highlighting.

    Now, the proper solution is a real homoiconic language, but Ruby isn't going to go there any time soon. So, while eval is evil, it can't be helped in Ruby. But, please, use it as sparingly as often!

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  3. James H
    James H September 7th, 2009 Reply Link

    I know you didn't want to explain the parenthesis on super() on the slide, but would you be so kind as to explain it here?

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. James Edward Gray II
      James Edward Gray II September 7th, 2009 Reply Link

      I'm happy to explain the required parentheses, yes.

      super is one of those magical keywords in Ruby. It often looks like a normal method call, but it has a little magic involved. When you call super without any parentheses, it invokes a method of the same name higher in the call chain passing the same arguments that the method which invoked super received. That's the magic. You can choose to specify the arguments yourself, with or without parentheses, overriding the magical behavior. The default for a bare call though is the magic.

      eval() can define normal Ruby methods, which is how I used it in my example. Because of that, you get all the normal magic and super works the same. In my example, the super's forward all arguments up the call chain. There just don't happen to be any arguments to forward.

      As I hinted at in the article though, define_method() is kind of a second class citizen. It builds a method for you, but you don't quite get all of the normal method magic. super is one of the cases where the magic doesn't work. To protect you from bugs, Ruby tosses an error when it sees a bare super inside of a define_method() call. The reason is that it assumes you wanted the magic and it can't give it in this case, so the error serves as a warning that your expectations may be off.

      In my example though, I didn't need the magic. I have nothing to pass up. Even though I can usually do that with a bare method call, this is one of the exceptions. A bare super call would summon the no-magic-here warning error. Thus, I have to explicitly pass zero arguments with the empty parentheses.

      See why I didn't want to have to explain that to the audience? Yuck!

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
  4. Matt Todd
    Matt Todd September 8th, 2009 Reply Link

    Hey James, great article. It's great that we're challenging notions of proper style and practice all the time (yet another reason why Why the Lucky Stiff was my hero, RIP).

    Anyways, wanted to chime in that I think there's also a difference in execution position as well in that evaling dynamic methods and modules at load time is absolutely fine but that runtime might be less ideal. I like to have most of my dynamic evaluation happen at once in the beginning before the "live" state of the application (for long-running apps mostly).

    Thanks for sharing!

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  5. Pykler
    Pykler September 9th, 2009 Reply Link

    eval is evil, they even sound the same.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  6. Bob Aman
    Bob Aman September 9th, 2009 Reply Link

    I'm curious. If Ruby didn't have eval() at all, what (rational) things couldn't you do with eval-less Ruby that could be done with Ruby as it exists today? I mean, there have been a few times where I thought I had a legitimate use case for eval(), but thus far, I've almost always ended up being proven wrong once I get far enough along in the development process. Template languages are the only significant legitimate use I can think of, that couldn't easily be handled by some other facility of the language.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. Glenn Vanderburg
      Glenn Vanderburg September 9th, 2009 Reply Link

      That's an interesting thought experiment, and you're right that there are very few things that you can do with eval that you couldn't do without.

      But if you're suggesting that as an argument against having eval in the language ... well, if we had the rule that we only wanted one way to do something in the language, half of Ruby would have to go, and we'd just have Python. ;-)

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
    3. Mat Brown
      Mat Brown October 2nd, 2009 Reply Link

      If Ruby didn't have eval() at all, what (rational) things couldn't you do with eval-less Ruby that could be done with Ruby as it exists today?

      The answer is metaprogramming without closures (the latter being potentially costly both in terms of namespace resolution and efficient garbage collection, as well as semantically undesirable and occasionally productive of hard-to-track-down bugs).

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
  7. Bradly Feeley
    Bradly Feeley September 9th, 2009 Reply Link

    Whenever I come across eval in Rails code it is usually a method call where either the class, method, or arguments are some variable. I don't like eval in that use case and prefer using send. Personally, I find String programming harder to read, which makes it harder to maintain.

    Also, while it isn't a wise to write code based on limitations of your editor, there may be an argument to be made on the cost of losing out on syntax highlighting on a chunk of code.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  8. Wyatt
    Wyatt September 9th, 2009 Reply Link

    I'm usually not one to go all Eval Knieval on my code, but there was one case where eval seemed to be my only viable option. I was doing some metaprogramming and needed to choose a class on the fly. Object.const_get does not work with namespaced classes, so the quickest way was to use eval("Namespace::ClassName").

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. James Edward Gray II
      James Edward Gray II September 9th, 2009 Reply Link

      Wyatt: It may have been possible to use code like the following in your case:

      "Namespace::ClassName".split("::").inject(Object) { |a, c| a.const_get(c) }
      
      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
  9. Nathan Powell
    Nathan Powell September 10th, 2009 Reply Link

    I think you are correct. I think that often times as technologists we want shortcuts since there are so many things to remember day to day. However as technical people we ought to find out why we repeat a mantra, not simply repeat it.

    Thanks for the reminder.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  10. Matthew Bennett
    Matthew Bennett September 10th, 2009 Reply Link

    It reminds me of a story a friend once told. A girl is watching her mother cook leg of (something), and noticed that before she put it in the oven, the mother always cut off both the ends. Asked why, the mother replies "That's what my mother always did". So she goes to the grandmother, and she gets the same response. So she goes to her great grandmother, and she explains "Well, it was during the war and money was tight. We didn't have an oven, and I could never fit it into the saucepan, so I always had to chop the ends off"

    The moral of the story: Always ask why. Otherwise you're wasting useful parts of the (meat|language) based on effectively superstition. In this case, if eval does what you need and you are aware of the possible dangers, never feel pressured into doing the same thing in a more convoluted way based on someone else's superstitions.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. Colin Bartlett
      Colin Bartlett October 14th, 2009 Reply Link

      I can't resist adding that in Primo Levi's wonderful book "The Periodic Table" there is a section called "Chrome" which is essentially a more literary (and true—it actually happened to Primo Levi when he was an industrial chemist working for a paint factory) version of the story in Matthew Bennett's post.

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
  11. Shane
    Shane September 14th, 2009 Reply Link

    If you feel your audience wouldn't understand that super() call then you probably shouldn't use eval in front of them. They may not understand when to ignore the eval mantra.

    I'd argue this is the beauty of a mantra. By the time you question it you know enough about the pros/cons to safely make your own judgment calls.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  12. Sarah Vessels
    Sarah Vessels September 16th, 2009 Reply Link

    I recently asked a question on StackOverflow about why using goto would be preferable to runtime code evaluation with eval, in the context of a programming assignment in a graduate class. In amongst everyone trying to tell me to use recursion, there were some good points made about why eval can be bad:

    • Syntax errors in Ruby strings to be evaled will not be found by your IDE.
    • Code in strings to be evaled can be harder to read than more complicated code that is not in strings.
    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  13. David Masover
    David Masover October 11th, 2009 Reply Link

    There are two main reasons I dislike eval in that situation, and prefer the closure solution.

    First, eval breaks syntax highlighting in my editor. For this reason alone, I find the closure logic easier to follow.

    And second, it's not only dangerous for security reasons, it's dangerous in any case the developer (or a user) might pass something into those variables other than what you intended, including a typo. In an example like that, suppose the name variable somehow had a double quote or a #{ in it… Contrived, sure, but when things like that do happen, eval makes them that much more difficult to debug, whereas it might have not even been an issue with define_method.

    I agree it's not always evil—for example, I'd already have guessed that appropriate use of eval can improve performance, just as inappropriate use can destroy performance. But premature optimization is the root of all evil—once I've narrowed it down to a define_method being a bottleneck, I'll consider eval, but not before.

    And there are at least two things which could not be done in an eval-less ruby, last I checked:

    Access to local variables through bindings. (I'm not sure if 1.9 fixed this...) This is one example of what I think is an appropriate use of eval, even disregarding performance—find some feature the language doesn't provide, implement it, tuck the eval deep inside some library where no one has to see it again, and be done with it.

    And development tools—irb, debuggers, IDEs, etc. This goes back to Lisp's read-eval-print-loop, and whenever I'm forced to use a language without an interactive prompt of some kind, I always feel crippled.

    They do make templates more convenient, but technically aren't required—you could write a "compiler" which translates a template into Ruby, but I'd argue this is a legitimate use for eval, at least if you want to allow Ruby code in the template.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. Rob Gleeson
      Rob Gleeson August 23rd, 2011 Reply Link

      eval isn't always evil though, I think it's mis-used and misunderstood.

      Binding#eval is extremely useful for prying inside the scope of a Proc to gain access to the value of a variable, and obviously, for a REPL it is essential too ;-)

      eval is beat on a lot but only because people mistakenly use it for something you shouldn't use it for, it does have some valid use cases, though.

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
  14. Douglas G. Allen
    Douglas G. Allen December 31st, 2016 Reply Link

    Your code James Edward Gray II

    is evil

        ruby_lang/programs/see-no-eval.rb:34: warning: already initialized constant B
        (eval):1: warning: previous definition of B was here
        ruby_lang/programs/see-no-eval.rb:34: warning: already initialized constant C
        (eval):1: warning: previous definition of C was here
        ruby_lang/programs/see-no-eval.rb:34: warning: already initialized constant D
        (eval):1: warning: previous definition of D was here
    

    But oh no don't show us things like that. Blog on brother.
    But if you could just stick to ERB you'll be fine.

    require 'erb'
    
    # Create template.
    template = %q(
      From:  James Edward Gray II <james@grayproductions.net>
      To:  <%= to %>
      Subject:  Addressing Needs
    
      <%= to[/\w+/] %>:
    
      Just wanted to send a quick note assuring that your needs are being
      addressed.
    
      I want you to know that my team will keep working on the issues,
      especially:
    
      <%# ignore numerous minor requests -- focus on priorities %>
      % priorities.each do |priority|
        * <%= priority %>
      % end
    
      Thanks for your patience.
    
      James Edward Gray II
    ).gsub(/^  /, '')
    
    message = ERB.new(template, 0, '%<>')
    
    # Set up template data.
    to = 'Community Spokesman <spokesman@ruby_community.org>'
    priorities = ['Run Ruby Quiz',
                  'Document Modules',
                  'Answer Questions on Ruby Talk']
    
    # Produce result.
    email = message.result
    puts email
    

    I guess that you were just trying to document your M0dules?
    Thanks! But what was your talk all about?
    Practicing Ruby? There's good articles there about method lookup and Modules. I think he even mentions you James.
    P.S. HEREDOCS are evil

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. James Edward Gray II
      James Edward Gray II December 31st, 2016 Reply Link

      But oh no don't show us things like that.

      Sorry, but I don't really understand the complaint or how you got these warnings.

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
  15. Douglas G. Allen
    Douglas G. Allen December 31st, 2016 Reply Link

    This is all you would need in the mean time.

    #
    module B
      def call
        puts 'B'
        super
      end
    end
    #
    module C
      def call
        puts 'C'
        super
      end
    end
    #
    module D
      def call
        puts 'D'
        super
      end
    end
    

    According to Rubocop.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  16. Douglas G. Allen
    Douglas G. Allen December 31st, 2016 Reply Link

    Now back to the topic. Doesn't IRB use eval() extensively?
    Have you considered integrating something like that into you testing?
    Perhaps even Pry would work too.
    I just don't know enough to go about doing so myself.
    Perhaps you do James.
    Any way it would be an interesting article and I know that Rack has a middleware
    that I've seen some rails apps use.

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. James Edward Gray II
      James Edward Gray II December 31st, 2016 Reply Link

      Now back to the topic. Doesn't IRB use eval() extensively?

      Yes.

      Have you considered integrating something like that into you testing?

      Some languages do this, like Python and Elixir.

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
Leave a Comment (using GitHub Flavored Markdown)

Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

Ajax loader