CrackedRuby logo

CrackedRuby

String Interpolation

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}"