0.1 Eval

Code is data, data is code

It is important for one to think of code as data to understand what eval is all about. The idea is that the code itself can be manifested as a primitive data type which the language can then make sense of.

Example Code:
zen = eval("7 * 6")
zen = eval("7 * 6")
 
 

Output Window

The data here is the string that is being passed through, the code being the Ruby expression that evaluates to 42.

This is similar to doing a normal multiplication operation.

Example Code:
zen = 7 * 6
zen = 7 * 6
 
 

Output Window

One isn't restricted to just Ruby expressions, you can write absolutely any Ruby code using eval. In the example below, we re-define the method zen to return 42 instead of 41.
Example Code:
def zen
def zen
  41
end
 
eval("def zen; 42; end")
 
puts zen
 
 

Output Window

Now, write any code in eval that returns a value that is greater than that stored in answer. Make sure that the expression below becomes true.
answer < eval("#define your code here")
answer < eval("#define your code here")
 
 

Hint

Try incrementing the answer.

Output Window

Ruby’s eval takes in a string as the argument, which means that it is possible for one to accept user input or read code off of a file and get it evaluated from within your program - essentially re-programming the way your program behaves.
Example Code:
result = "Initial string"
result = "Initial string"
# note that we're mocking Ruby's File class
# as we don't allow reading from files.
contents = Document.new('zen')
result = eval("contents.get_contents")
 
 

Output Window

This of course means that any changes you need to make with the code you've read from the file have to be done by the use of Ruby's string manipulation methods.

This is unlike many functional programming languages where code itself is a first-class data type (known as homoiconicity) and you can eval code by just passing in code.

# assuming contents has the code 
# assuming contents has the code 
# def code
#   7 + 6
# end
 
contents = Document.new('zen').get_contents
result = eval("# we want to replace * with +")
 
 

Output Window

Evaluating code read out of a file or a string coming from a database greatly increases the risk of execution of malicious code and is one of the reasons why eval is considered evil. eval also reduces the effectiveness of static code analysis tools, because our code is just masquerading as code, and is essentially just data.
Example Code:
contents = Document.new('zen').get_contents
contents = Document.new('zen').get_contents
puts contents
puts eval(contents)
# Don't bother about getting the specs passing
 
 

Output Window

Thankfully, our system is pretty bullet-proof and this attempt at trying to rm -rf our root directory fails. eval should best be avoided in real scenarios. Ruby has saner tools (#define_method, #send) in its meta-programming repertoire that you can use to achieve eval-like cleverness.

Bindings

Now that we’ve gotten the idea through, we can look at how eval’s syntax works.

eval is a method on the Kernel module, which is included in the Object class, hence, available on all Ruby objects. eval takes in a second parameter along with the string where you can specify a binding.

A binding is an object that stores the context or the scope which it lies in. This allows us more granularity on where the code that we’ve passed is being eval’d in the context of the program.

Example Code:
def get_binding
def get_binding
 binding
end
 
class Monk
  def get_binding
    binding
  end
end
 
puts eval("self", get_binding)
puts eval("self", Monk.new.get_binding)
 
 

Output Window

Here we see how self has changed in different binding contexts. This is how you can change self while evaluating code through eval using bindings.

Ruby provides with a constant TOPLEVEL_BINDING, which is a Binding object that always represents the top-level scope of your program.

Here's an example that uses TOPLEVEL_BINDING with eval that creates methods in the main scope from inside a class.

Example Code:
# TOPLEVEL_BINDING
# TOPLEVEL_BINDING
class RubyMonk
  def self.create_book(book)
    eval("def #{book}; 'created'; end", TOPLEVEL_BINDING)
  end
end
 
RubyMonk.create_book :regular_expressions
 
puts regular_expressions
 
 

Output Window

It's a good practice to include the keywords __FILE__ and __LINE__ as the 3rd and 4th parameters to eval(). __FILE__ returns a string of the name of the file currently being executed. It returns "(eval)" if it's called from an eval() context.

Adding these parameters makes debugging code that uses eval much easier, so I would recommend that you use them every single time you use eval.

Note that we haven't necessarily used them in every example in this chapter to keep things simple.

Example Code:
puts __FILE__
puts __FILE__
puts __LINE__
eval("42", binding, __FILE__, __LINE__)
 
 

Output Window

To summarise: Bindings are regular objects that contain scope and the state within it, but no code.

But what about #define_method?

In this section, we'll benchmark the performance of methods created by define_method and those created through eval.
Example Code:
require 'benchmark'
require 'benchmark'
 
class Monk
  eval "def zen; end"
 
  define_method(:zen_block) {}
end
 
monk = Monk.new
 
Benchmark.bmbm do |x|
  x.report("eval zen: ") { 10_000.times { monk.zen } }
  x.report("define_method zen: ") { 10_000.times { monk.zen_block } }
end
 
 

Output Window

As we can see from the results, the methods defined using eval() performed better than those created using define_method.

I've re-worded this section based on feedback from one of our students, Alfonso Muñoz-Pomer Fuentes, who pointed out that eval itself performs poorly though methods created with it are quick. This is worth noting, because if you need to create a lot of new methods at runtime (a very rare scenario), define_method is the better option. Here's an example contributed by him that demonstrates the relative performance of creating lots of new methods using eval versus define_method.

Example Code:
require 'benchmark'
require 'benchmark'
 
# A major difference from the previous
# benchmark is that in order to measure
# the performance of method creation 
# (as opposed to method execution), we're 
# ensuring that we create a new method in 
# each iteration of the benchmark loop
 
class Monk
  def with_eval(unique_label)
    eval "def zen_#{unique_label}; end"
  end
 
  # define_method is a private class method.
  # We wrap it in our own public class method 
  # so we can use it from outside the class
  def self.with_define_method(unique_label)
    define_method("zen_block_#{unique_label}") {}
  end
end
 
monk = Monk.new 
 
Benchmark.bmbm do |x|
  x.report("eval zen: ") do 
    2000.times { |i| monk.with_eval(i) }
    end
    x.report("define_method zen: ") do 
    2000.times { |i| Monk.with_define_method(i) }
    end
end
 
 
 

Output Window

In general, I would caution against using eval despite any performance advantages because code that depends on eval tends to be harder to maintain for a variety of reasons that are beyond the scope of this course.

Congratulations, guest!


% of the book completed

or

This lesson is Copyright © 2011-2025 by Sidu Ponnappa and Jasim A Basheer