Ruby eval and Binding — Runtime Code Evaluation and Context Execution Explained
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