Overview
String interpolation in Ruby provides a mechanism for embedding Ruby expressions within string literals. Ruby evaluates expressions enclosed in #{}
delimiters and converts the results to strings using the to_s
method. This feature works exclusively with double-quoted strings, heredocs, and certain string-like constructs.
Ruby processes interpolated strings at parse time, creating bytecode that concatenates string segments with evaluated expressions. The interpolation mechanism calls to_s
on each expression result, handling type conversion automatically.
name = "Alice"
age = 30
message = "Hello, #{name}! You are #{age} years old."
# => "Hello, Alice! You are #{age} years old."
String interpolation supports arbitrary Ruby expressions, including method calls, mathematical operations, and complex object interactions:
users = ["Alice", "Bob", "Charlie"]
status = "Found #{users.length} users: #{users.join(', ')}"
# => "Found 3 users: Alice, Bob, Charlie"
The feature integrates with Ruby's encoding system, maintaining string encoding consistency across interpolated segments. Ruby handles encoding conversion when interpolating strings with different encodings, following specific precedence rules.
Basic Usage
Double-quoted strings accept interpolation syntax anywhere within the string body. Ruby evaluates each #{}
expression in sequence, converting results to strings before concatenation:
product = "laptop"
price = 999.99
description = "The #{product} costs $#{price}"
# => "The laptop costs $999.99"
Method calls within interpolation execute normally, including methods with side effects:
counter = 0
def increment
@counter = (@counter || 0) + 1
end
message = "Call #{increment}, then #{increment}"
# => "Call 1, then 2"
Heredocs support interpolation with the same syntax as double-quoted strings:
name = "John"
email_template = <<~EMAIL
Dear #{name},
Your account balance is $#{calculate_balance(name)}.
Best regards,
Finance Team
EMAIL
Ruby evaluates complex expressions within interpolation blocks, including conditional logic and variable assignments:
temperature = 75
weather_report = "Today is #{temperature > 80 ? 'hot' : 'pleasant'}"
# => "Today is pleasant"
Nested interpolation works through recursive evaluation, though it requires careful escaping:
template = "Hello #{name}"
greeting = "Message: #{eval("\"#{template}\"")}"
Advanced Usage
String interpolation accepts any Ruby expression, including blocks, lambda calls, and metaprogramming constructs. Complex expressions can span multiple lines using proper syntax:
class Calculator
def self.compute(operation, a, b)
case operation
when :add then a + b
when :multiply then a * b
else 0
end
end
end
result = "Calculation: #{Calculator.compute(:multiply, 6, 7)} units"
# => "Calculation: 42 units"
Interpolation works with custom objects by calling their to_s
methods. Objects can customize their string representation for interpolation contexts:
class Person
def initialize(name, title)
@name, @title = name, title
end
def to_s
"#{@title} #{@name}"
end
end
person = Person.new("Smith", "Dr.")
introduction = "Meet #{person} from our team"
# => "Meet Dr. Smith from our team"
Ruby's interpolation mechanism handles complex data structures through recursive to_s
calls:
data = {
users: ["Alice", "Bob"],
timestamp: Time.now,
metadata: { version: "1.2.3", environment: "production" }
}
log_entry = <<~LOG
Processing #{data[:users].length} users at #{data[:timestamp]}
Version: #{data[:metadata][:version]}
Environment: #{data[:metadata][:environment]}
LOG
Interpolation supports method chaining and complex object interactions:
class DataProcessor
def initialize(data)
@data = data
end
def filter(&block)
DataProcessor.new(@data.select(&block))
end
def count
@data.length
end
def average
return 0 if @data.empty?
@data.sum.to_f / @data.length
end
end
numbers = DataProcessor.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
report = <<~REPORT
Total numbers: #{numbers.count}
Even numbers: #{numbers.filter(&:even?).count}
Average of evens: #{numbers.filter(&:even?).average.round(2)}
Sum of odds: #{numbers.filter(&:odd?).count * numbers.filter(&:odd?).average}
REPORT
Common Pitfalls
String interpolation evaluates expressions immediately during string creation, not when the string gets used. This timing difference creates subtle bugs in certain scenarios:
counter = 0
template = "Current count: #{counter += 1}"
puts template # => "Current count: 1"
puts template # => "Current count: 1" (not 2)
Ruby converts interpolation results using to_s
, which may not produce expected output for certain objects:
hash = { name: "Alice", age: 30 }
message = "User data: #{hash}"
# => "User data: {:name=>\"Alice\", :age=>30}"
# Better approach:
message = "User: #{hash[:name]}, Age: #{hash[:age]}"
Interpolation with nil
values produces empty strings rather than the literal text "nil":
value = nil
result = "Value is: #{value}"
# => "Value is: "
# Explicit nil handling:
result = "Value is: #{value || 'not set'}"
# => "Value is: not set"
Security vulnerabilities emerge when interpolating untrusted input, particularly in contexts where the result gets evaluated:
user_input = 'Alice"; system("rm -rf /"); puts "'
# Dangerous - don't do this:
eval("puts \"Hello, #{user_input}\"")
# Safe approach:
puts "Hello, #{user_input.gsub(/[";\\]/, '')}"
Performance degradation occurs with complex expressions in frequently executed interpolations:
# Inefficient in loops:
1000.times do |i|
log_entry = "Processing item #{expensive_calculation(i)} at #{Time.now}"
puts log_entry
end
# Better approach:
timestamp = Time.now
1000.times do |i|
result = expensive_calculation(i)
log_entry = "Processing item #{result} at #{timestamp}"
puts log_entry
end
Encoding issues arise when interpolating strings with different encodings:
# This can raise Encoding::CompatibilityError
ascii_string = "Hello".force_encoding("ASCII")
utf8_string = "世界".force_encoding("UTF-8")
result = "#{ascii_string} #{utf8_string}"
Reference
Syntax Forms
Form | Example | Interpolation Support |
---|---|---|
Double-quoted strings | "Hello #{name}" |
Yes |
Single-quoted strings | 'Hello #{name}' |
No |
Heredocs (<<) | <<EOF\nHello #{name}\nEOF |
Yes |
Heredocs (<<-) | <<-EOF\n Hello #{name}\nEOF |
Yes |
Heredocs (<<~) | <<~EOF\n Hello #{name}\nEOF |
Yes |
%Q strings |
%Q{Hello #{name}} |
Yes |
%q strings |
%q{Hello #{name}} |
No |
Regular expressions | /pattern #{variable}/ |
Yes |
Command strings | `echo #{message}` |
Yes |
%x commands |
%x{echo #{message}} |
Yes |
Conversion Methods
Expression Result | Conversion Method | Example Output |
---|---|---|
String | Direct use | "hello" → "hello" |
Integer | #to_s |
42 → "42" |
Float | #to_s |
3.14 → "3.14" |
Symbol | #to_s |
:symbol → "symbol" |
Array | #to_s |
[1, 2] → "[1, 2]" |
Hash | #to_s |
{a: 1} → "{:a=>1}" |
nil | #to_s |
nil → "" |
true | #to_s |
true → "true" |
false | #to_s |
false → "false" |
Performance Characteristics
Scenario | Performance Impact | Recommendation |
---|---|---|
Simple variable | Minimal overhead | Use freely |
Method calls | Depends on method | Cache expensive results |
Object creation | High overhead | Avoid in hot loops |
Complex expressions | Variable overhead | Extract to variables |
Large string building | Memory allocation | Consider StringIO |
Encoding Behavior
String 1 Encoding | String 2 Encoding | Result Encoding | Compatibility |
---|---|---|---|
ASCII-8BIT | ASCII-8BIT | ASCII-8BIT | Always compatible |
UTF-8 | UTF-8 | UTF-8 | Always compatible |
ASCII-8BIT | UTF-8 | UTF-8 | Compatible if ASCII-8BIT contains only ASCII |
UTF-8 | ASCII-8BIT | UTF-8 | Compatible if ASCII-8BIT contains only ASCII |
Locale | UTF-8 | UTF-8 | May raise CompatibilityError |
Common Expression Patterns
# Conditional interpolation
"Status: #{active ? 'enabled' : 'disabled'}"
# Method chaining
"Result: #{data.select(&:valid?).map(&:name).join(', ')}"
# Arithmetic operations
"Total: $#{(price * quantity * (1 + tax_rate)).round(2)}"
# String formatting
"Progress: #{(completed.to_f / total * 100).round(1)}%"
# Object attribute access
"User: #{user.name} (#{user.email})"
# Array operations
"Items: #{items.length} total, #{items.count(&:available)} available"
# Exception handling within interpolation
"Value: #{begin; risky_operation; rescue; 'N/A'; end}"