Skip to content

Ruby eval and Binding — Runtime Code Evaluation and Context Execution Explained

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Ruby eval and Binding. We cover key concepts, practical examples, and best practices to help you master this topic.

Ruby eval executes arbitrary strings as Ruby code at runtime, with Binding capturing execution context for deferred evaluation in different scopes, enabling REPLs, dynamic configuration, and template rendering.

What You'll Learn

  • Using eval, instance_eval, and class_eval
  • Working with Binding objects
  • Security considerations
  • Building a simple REPL

Why It Matters

eval powers Rails console, ERB templates, and interactive tools. Doda Browser uses eval for extension scripts. Durga Antivirus Pro uses it for dynamic rule evaluation. Understanding when to use (and avoid) eval is critical.

Real-World Use

ERB template rendering, Rails console, Pry debugger, Rake tasks, configuration DSLs, and interactive coding environments.

flowchart LR
    A["eval"] --> B["instance_eval"]
    B --> C["class_eval"]
    C --> D["Binding"]
    D --> E["Security"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

The eval Family

Kernel#eval

# Basic eval — executes string in current context
x = 10
code = "x * 2"
result = eval(code)
puts result  # 20

# With binding
def make_greeter
  name = "Alice"
  binding  # Captures current context
end

b = make_greeter
puts eval("name", b)  # Alice

# With file and line info
eval("puts 'hello'", binding, "my_file.rb", 42)

instance_eval

obj = "hello"

# Change context to obj
obj.instance_eval do
  puts upcase   # HELLO
  puts length   # 5
end

# Define singleton methods
obj.instance_eval do
  def shout
    "#{upcase}!"
  end
end
puts obj.shout  # HELLO!

# With string
obj.instance_eval("upcase + '!'")  # HELLO!

class_eval (alias: module_eval)

class User; end

User.class_eval do
  attr_accessor :name, :email

  def initialize(name, email)
    @name = name
    @email = email
  end
end

user = User.new("Alice", "alice@test.com")
puts user.name  # Alice

# With string
User.class_eval("def greet; 'Hello!'; end")
puts user.greet  # Hello!

Using Binding

def capture_context(name, age)
  @name = name
  local_var = age
  binding  # Returns Binding with current context
end

b = capture_context("Alice", 30)

# Access variables through binding
puts eval("@name", b)    # Alice
puts eval("local_var", b) # 30

# Define methods in binding context
eval("def greet; 'Hi from #{@name}'; end", b)
puts eval("greet", b)             # Hi from Alice
puts eval("local_var * 2", b)    # 60

Binding from Proc

def create_binding
  x = 10
  y = 20
  Proc.new { x + y }
end

proc_obj = create_binding
b = proc_obj.binding

puts eval("x", b)  # 10
puts eval("y", b)  # 20
puts eval("x + y", b)  # 30

TOPLEVEL_BINDING

# Access main object's binding
main = TOPLEVEL_BINDING

# Define variables at the top level
eval("message = 'Hello from top level'", main)

# Define methods at the top level
eval("def top_method; 'I am at the top'; end", main)
puts eval("top_method", main)  # I am at the top

Building a Simple REPL

class SimpleREPL
  def initialize
    @history = []
    @binding = binding
  end

  def run
    puts "SimpleREPL (type 'exit' to quit)"
    loop do
      print ">> "
      input = gets.chomp
      break if input == "exit"

      begin
        result = eval(input, @binding)
        puts "=> #{result.inspect}"
        @history << input
      rescue StandardError, SyntaxError => e
        puts "Error: #{e.message}"
      end
    end
  end

  def history
    @history
  end
end

# repl = SimpleREPL.new
# repl.run

Safe Evaluation with $SAFE (Legacy)

# Ruby 2.7+ — $SAFE is deprecated
# Instead use sandboxing techniques

# Restricted eval with allowed methods
def safe_eval(code, allowed_methods:)
  whitelist = allowed_methods
  sandbox = Object.new

  sandbox.define_singleton_method(:method_missing) do |name, *args|
    if whitelist.include?(name)
      puts "Allowed: #{name}"
    else
      raise "Forbidden: #{name}"
    end
  end

  sandbox.instance_eval(code)
end

# safe_eval("system('rm -rf /')", allowed_methods: [:puts])  # Raises error

DSL with instance_eval

class ConfigBuilder
  def initialize
    @settings = {}
  end

  def setting(name, value = nil, &block)
    if block
      @settings[name] = block
    else
      @settings[name] = value
    end
  end

  def build
    @settings
  end
end

class Application
  def self.configure(&block)
    builder = ConfigBuilder.new
    builder.instance_eval(&block)
    @config = builder.build
  end

  def self.config
    @config || {}
  end
end

Application.configure do
  setting :name, "MyApp"
  setting :port, 8080
  setting :on_start do
    puts "Application starting..."
  end
end

puts Application.config[:name]  # MyApp

ERB Template Engine (Simplified)

class SimpleTemplate
  def initialize(template)
    @template = template
  end

  def render(context = {})
    b = binding
    context.each { |k, v| b.local_variable_set(k, v) }

    compiled = @template.gsub(/<%= (.+?) %>/) { eval($1, b) }
    compiled = compiled.gsub(/<% (.+?) %>/) {
      eval($1, b)
      ""
    }
    compiled
  end
end

template = SimpleTemplate.new(
  "Hello <%= name %>! You are <%= age %> years old."
)

puts template.render(name: "Alice", age: 30)
# Hello Alice! You are 30 years old.

Common Mistakes

1. eval with Untrusted Input

# DANGEROUS — code injection
def run_user_code(code)
  eval(code)  # User can run: system("rm -rf /")
end

# NEVER pass user input to eval, instance_eval, or class_eval

2. Memory Leaks with Binding

# Binding captures entire scope, preventing GC
def leaky_method
  large_data = "x" * 10_000_000
  @binding = binding  # Prevents GC on large_data
end

3. Performance Penalty

# eval is ~100x slower than direct code
# Never use eval in performance-critical paths

# Bad
10_000.times { |i| eval("x = #{i}") }

# Good
10_000.times { |i| x = i }

4. Syntax Errors in eval Strings

begin
  eval("def foo;")  # Syntax error
rescue SyntaxError => e
  puts "Syntax error: #{e.message}"
end

5. Variable Scope Confusion

x = 10
eval("x = 20")  # Creates local x? Modifies outer x?
puts x  # 20 — modifies local variable!

# class_eval and instance_eval have different scope rules

Practice Questions

1. What's the difference between eval, instance_eval, and class_eval?

eval runs in the current binding (scope). instance_eval changes self to the receiver. class_eval runs in the context of a class/module, defining instance methods.

2. What is a Binding object?

An object that captures the execution context (variables, methods, self) at a specific point. Use it to defer evaluation with the captured context.

3. Why is eval dangerous with user input?

It can execute arbitrary code, including system commands and file operations. An attacker can run system("rm -rf /") or steal data.

4. When is eval acceptable?

In controlled environments (development tools, REPLs), with trusted code (user-written but not attacker-controlled), or for Code Generation in build tools.

Challenge: Create a Sandbox class that allows evaluating Ruby expressions with a restricted set of allowed methods and a timeout.

Solution
require "timeout"

class Sandbox
  SAFE_METHODS = %i[+ - * / to_s to_i to_f inspect puts print
                    upcase downcase capitalize reverse length
                    map select reduce each join split].freeze

  def initialize
    @sandbox = BasicObject.new
  end

  def evaluate(code, timeout_sec: 1)
    Timeout.timeout(timeout_sec) do
      result = @sandbox.instance_eval(build_safe_code(code))
      sanitize_result(result)
    end
  rescue Timeout::Error
    "Error: Execution timed out"
  rescue => e
    "Error: #{e.message}"
  end

  private

  def build_safe_code(code)
    # Only allow certain patterns
    sanitized = code.gsub(/[^a-z0-9\s\.\(\)\+\-\*\/\,\:\"\']/i, "")
    "begin; #{sanitized}; rescue => e; e.message; end"
  end

  def sanitize_result(result)
    case result
    when String, Integer, Float, true, false, nil
      result
    when Array
      result.map { |e| sanitize_result(e) }
    else
      result.to_s
    end
  end
end

sandbox = Sandbox.new
puts sandbox.evaluate("2 + 3")           # 5
puts sandbox.evaluate("'hello'.upcase")   # HELLO

begin
  Timeout.timeout(1) do
    sandbox.evaluate("loop { 1 + 1 }", timeout_sec: 1)
  end
rescue Timeout::Error
  puts "Timeout works!"
end

FAQ

{{< faq question="What's the difference between eval and instance_eval?" >}} eval runs code in the current lexical scope. instance_eval changes self to the receiver. eval can access local variables; instance_eval cannot (unless they're captured in a closure). {{< /faq >}}

{{< faq question="Is there a safe alternative to eval?" >}} For configuration, use YAML/JSON. For expressions, write a parser. For math, use a gem like dentaku. For templates, use ERB with safe mode. Only use eval with trusted code. {{< /faq >}}

{{< faq question="How does Rails console use eval?" >}} Rails console reads input, wraps it in a rescue block, and calls eval(input, binding). It captures the console's binding to maintain state between evaluations. {{< /faq >}}

{{< faq question="Can eval return multiple values?" >}} Yes. eval("1; 2; 3") returns 3 (last expression). Use arrays: eval("[1, 2, 3]") or multiple statements. {{< /faq >}}

{{< faq question="What is TOPLEVEL_BINDING?" >}} A Binding object representing the main Ruby execution context (the top-level self). Use it to evaluate code at the top-level scope from anywhere: eval("def top_method; end", TOPLEVEL_BINDING). {{< /faq >}}

Try It Yourself

# eval_demo.rb

def create_counter(start = 0)
  count = start
  binding
end

counter = create_counter(10)

# Use the binding to access and modify the counter
puts eval("count", counter)  # 10
eval("count += 1", counter)
puts eval("count", counter)  # 11

# Dynamic method definition with class_eval
class MathHelper
end

%w[add subtract multiply].each_with_index do |name, idx|
  MathHelper.class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def #{name}(a, b)
      result = #{[:+, :-, :*][idx]}.call(a, b)
      "[#{name}] #{a} #{:#{[:+, :-, :*][idx]}} #{b} = #{result}"]
    end
  RUBY
end

m = MathHelper.new
puts m.add(5, 3)       # [add] 5 + 3 = 8
puts m.subtract(10, 4) # [subtract] 10 - 4 = 6
puts m.multiply(3, 7)  # [multiply] 3 * 7 = 21

What's Next

Now that you understand eval and binding, explore performance optimization techniques in Ruby.

Topic Description Link
Ruby Performance Optimization Profiling, Caching, YJIT {{< ref "39-performance-optimization" >}}
Ruby Metaprogramming define_method, send, method_missing {{< ref "33-metaprogramming-basics" >}}
Python exec/eval Compare Python's dynamic execution Python

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro