# Every string literal creates new object 1000.times do puts "Processing request" # 1000 objects! end # Memory usage grows unnecessarily # GC pressure increases # Performance degrades
# frozen_string_literal: true 1000.times do puts "Processing request" # 1 object! end # Massive memory savings # Reduced GC pressure # Better performance
Database URLs, API endpoints, and app constants benefit from memory sharing
Reusable email subjects and HTML templates get massive memory savings
HTTP headers, status messages, and route patterns used repeatedly
# Add this magic comment as the first line # frozen_string_literal: true # Now all string literals are automatically frozen! app_name = "My Ruby App" # frozen: true version = "1.0.0" # frozen: true # Create mutable copy when needed mutable_name = +app_name # frozen: false mutable_name << " v2" # ✅ Works!
That's it! Just one line at the top of your Ruby file enables massive performance benefits automatically.
# frozen_string_literal: true class WebApp # All these constants share memory! APP_NAME = "MyRubyApp" VERSION = "2.1.0" API_BASE = "https://api.example.com" # HTTP constants - perfect for frozen strings HTTP_OK = "200 OK" NOT_FOUND = "404 Not Found" SERVER_ERROR = "500 Internal Server Error" end # Memory usage: 1 object per unique string!
# frozen_string_literal: true def build_message(name, action) # ❌ This would fail - trying to modify frozen string # message = "Hello" # message << " #{name}" # FrozenError! # ✅ Use string interpolation instead message = "Hello #{name}, you just #{action}!" # ✅ Or create mutable copy when needed mutable = +"Processing: " # Unary + makes it mutable mutable << name mutable << " - " << action [message, mutable] end # String interpolation creates new objects (not frozen) greeting = "Welcome #{user.name}!" # frozen: false url = "#{API_BASE}/users/#{user.id}" # frozen: false
# frozen_string_literal: true
class PerformanceAnalysis
def self.memory_usage_demo
puts "=== Memory Usage Analysis ==="
# Simulate a web application with many constant strings
def self.without_frozen_strings
# Simulating code without frozen string literals
constants = []
1000.times do
constants << "application/json".dup
constants << "text/html".dup
constants << "image/png".dup
constants << "GET".dup
constants << "POST".dup
end
constants
end
def self.with_frozen_strings
# With frozen string literals
constants = []
1000.times do
constants << "application/json"
constants << "text/html"
constants << "image/png"
constants << "GET"
constants << "POST"
end
constants
end
# Analyze object creation
before_frozen = ObjectSpace.count_objects[:T_STRING]
frozen_constants = with_frozen_strings
after_frozen = ObjectSpace.count_objects[:T_STRING]
puts "Frozen strings - Objects created: #{after_frozen - before_frozen}"
before_mutable = ObjectSpace.count_objects[:T_STRING]
mutable_constants = without_frozen_strings
after_mutable = ObjectSpace.count_objects[:T_STRING]
puts "Mutable strings - Objects created: #{after_mutable - before_mutable}"
# Check unique object IDs
frozen_unique = frozen_constants.map(&:object_id).uniq.length
mutable_unique = mutable_constants.map(&:object_id).uniq.length
puts "Frozen - Unique objects: #{frozen_unique}"
puts "Mutable - Unique objects: #{mutable_unique}"
puts "Memory efficiency: #{((mutable_unique - frozen_unique).to_f / mutable_unique * 100).round(2)}% reduction"
end
def self.string_creation_benchmark
require 'benchmark'
puts "\n=== String Creation Benchmark ==="
n = 100_000
Benchmark.bm(25) do |x|
x.report("Frozen literals") do
n.times do
str = "Hello World"
str.length
end
end
x.report("Mutable copies") do
n.times do
str = "Hello World".dup
str.length
end
end
x.report("String interpolation") do
n.times do
str = "#{'Hello'} #{'World'}"
str.length
end
end
x.report("String concatenation") do
n.times do
str = "Hello" + " " + "World"
str.length
end
end
end
end
def self.hash_key_performance
puts "\n=== Hash Key Performance ==="
# Frozen strings make excellent hash keys
hash_with_frozen = {}
hash_with_mutable = {}
# Pre-populate with frozen keys
1000.times do |i|
hash_with_frozen["key_#{i}"] = "value_#{i}"
end
# Pre-populate with mutable keys
1000.times do |i|
hash_with_mutable["key_#{i}".dup] = "value_#{i}".dup
end
require 'benchmark'
n = 10_000
Benchmark.bm(20) do |x|
x.report("Frozen key lookup") do
n.times do
hash_with_frozen["key_500"]
end
end
x.report("Mutable key lookup") do
n.times do
hash_with_mutable["key_500".dup]
end
end
end
end
def self.gc_impact_analysis
puts "\n=== Garbage Collection Impact ==="
require 'benchmark'
def self.create_many_strings(frozen: true)
if frozen
1000.times.map { "This is a test string for GC analysis" }
else
1000.times.map { "This is a test string for GC analysis".dup }
end
end
GC.disable
# Measure GC with frozen strings
gc_before = GC.stat[:total_allocated_objects]
frozen_strings = create_many_strings(frozen: true)
gc_after_frozen = GC.stat[:total_allocated_objects]
# Measure GC with mutable strings
mutable_strings = create_many_strings(frozen: false)
gc_after_mutable = GC.stat[:total_allocated_objects]
GC.enable
puts "Objects allocated (frozen): #{gc_after_frozen - gc_before}"
puts "Objects allocated (mutable): #{gc_after_mutable - gc_after_frozen}"
puts "Difference: #{(gc_after_mutable - gc_after_frozen) - (gc_after_frozen - gc_before)}"
end
end
PerformanceAnalysis.memory_usage_demo
# PerformanceAnalysis.string_creation_benchmark # Uncomment to run benchmark
# PerformanceAnalysis.hash_key_performance # Uncomment to run benchmark
PerformanceAnalysis.gc_impact_analysis
# frozen_string_literal: true
class ConfigurationExample
# Application constants - all frozen by default
APP_NAME = "MyWebApp"
VERSION = "1.2.3"
DEFAULT_LOCALE = "en"
# HTTP status messages
HTTP_MESSAGES = {
200 => "OK",
201 => "Created",
400 => "Bad Request",
401 => "Unauthorized",
404 => "Not Found",
500 => "Internal Server Error"
}.freeze
# MIME types
MIME_TYPES = {
".html" => "text/html",
".css" => "text/css",
".js" => "application/javascript",
".json" => "application/json",
".png" => "image/png",
".jpg" => "image/jpeg"
}.freeze
# Database configuration
class DatabaseConfig
HOST = "localhost"
PORT = "5432"
DATABASE = "myapp_production"
def self.connection_string
# String interpolation creates new string
"postgresql://#{HOST}:#{PORT}/#{DATABASE}"
end
def self.connection_params
{
host: HOST,
port: PORT,
database: DATABASE
}
end
end
def self.demonstrate_configuration
puts "=== Configuration Usage ==="
puts "App: #{APP_NAME} v#{VERSION}"
puts "Locale: #{DEFAULT_LOCALE}"
puts "Connection: #{DatabaseConfig.connection_string}"
# All string literals are frozen
constants = [APP_NAME, VERSION, DEFAULT_LOCALE]
constants.each do |const|
puts "#{const} frozen? #{const.frozen?}"
end
# Hash values are also frozen
HTTP_MESSAGES.each do |code, message|
puts "#{code}: #{message} (frozen: #{message.frozen?})"
end
end
# Logger with frozen format strings
class Logger
LOG_LEVELS = {
debug: "DEBUG",
info: "INFO",
warn: "WARN",
error: "ERROR"
}.freeze
def self.log(level, message)
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
level_str = LOG_LEVELS[level] || "UNKNOWN"
# Format string is frozen, but result is mutable
formatted = "[#{timestamp}] #{level_str}: #{message}"
puts formatted
formatted
end
def self.demonstrate_logging
puts "\n=== Logging Example ==="
messages = [
[:info, "Application started"],
[:debug, "Processing request"],
[:warn, "High memory usage detected"],
[:error, "Database connection failed"]
]
messages.each do |level, msg|
result = log(level, msg)
puts " Log result frozen? #{result.frozen?}"
end
end
end
def self.demonstrate_template_system
puts "\n=== Template System ==="
# Email templates with frozen literals
class EmailTemplates
WELCOME_SUBJECT = "Welcome to %s!"
WELCOME_BODY = <<~EMAIL
Dear %s,
Welcome to %s! We're excited to have you on board.
Best regards,
The %s Team
EMAIL
RESET_SUBJECT = "Password Reset Request"
RESET_BODY = <<~EMAIL
Hello %s,
You requested a password reset. Click the link below:
%s
If you didn't request this, please ignore this email.
EMAIL
def self.welcome_email(name, app_name)
{
subject: WELCOME_SUBJECT % app_name,
body: WELCOME_BODY % [name, app_name, app_name]
}
end
def self.reset_email(name, reset_link)
{
subject: RESET_SUBJECT,
body: RESET_BODY % [name, reset_link]
}
end
end
# Demonstrate template usage
welcome = EmailTemplates.welcome_email("Alice", APP_NAME)
reset = EmailTemplates.reset_email("Bob", "https://example.com/reset/123")
puts "Welcome Subject: #{welcome[:subject]}"
puts "Welcome Subject frozen? #{welcome[:subject].frozen?}"
puts "\nReset Subject: #{reset[:subject]}"
puts "Reset Subject frozen? #{reset[:subject].frozen?}"
end
end
ConfigurationExample.demonstrate_configuration
ConfigurationExample::Logger.demonstrate_logging
ConfigurationExample.demonstrate_template_system
# frozen_string_literal: true
class WebDevelopmentExample
# HTTP-related constants
module HTTPConstants
METHODS = %w[GET POST PUT PATCH DELETE HEAD OPTIONS].freeze
HEADERS = {
content_type: "Content-Type",
authorization: "Authorization",
user_agent: "User-Agent",
accept: "Accept",
cache_control: "Cache-Control"
}.freeze
STATUS_CODES = {
ok: 200,
created: 201,
no_content: 204,
bad_request: 400,
unauthorized: 401,
forbidden: 403,
not_found: 404,
unprocessable_entity: 422,
internal_server_error: 500
}.freeze
end
# API response builder
class APIResponse
def self.success(data, message = "Success")
build_response(
status: "success",
data: data,
message: message
)
end
def self.error(message, code = "error")
build_response(
status: "error",
error: {
code: code,
message: message
}
)
end
def self.paginated(data, page, per_page, total)
build_response(
status: "success",
data: data,
pagination: {
page: page,
per_page: per_page,
total: total,
pages: (total.to_f / per_page).ceil
}
)
end
private
def self.build_response(response_data)
# All string keys are frozen, but the hash structure is new
{
timestamp: Time.now.iso8601,
**response_data
}
end
end
# Route handling
class Router
ROUTES = {
"GET /users" => :list_users,
"POST /users" => :create_user,
"GET /users/:id" => :show_user,
"PUT /users/:id" => :update_user,
"DELETE /users/:id" => :delete_user
}.freeze
def self.match_route(method, path)
route_key = "#{method} #{path}"
# Direct match first
return ROUTES[route_key] if ROUTES.key?(route_key)
# Pattern matching for parameterized routes
ROUTES.each do |pattern, action|
if pattern.include?(":") && match_pattern?(pattern, route_key)
return action
end
end
nil
end
def self.match_pattern?(pattern, route)
pattern_parts = pattern.split(" ", 2)[1].split("/")
route_parts = route.split(" ", 2)[1].split("/")
return false if pattern_parts.length != route_parts.length
pattern_parts.zip(route_parts).all? do |pattern_part, route_part|
pattern_part.start_with?(":") || pattern_part == route_part
end
end
def self.demonstrate_routing
puts "=== Route Matching ==="
test_routes = [
["GET", "/users"],
["POST", "/users"],
["GET", "/users/123"],
["PUT", "/users/456"],
["DELETE", "/users/789"],
["GET", "/unknown"]
]
test_routes.each do |method, path|
action = match_route(method, path)
puts "#{method} #{path} -> #{action || 'Not Found'}"
end
end
end
# JSON API serializer
class JSONSerializer
MIME_TYPE = "application/json"
CHARSET = "utf-8"
def self.serialize(data, options = {})
# Content-Type header is frozen
headers = {
HTTPConstants::HEADERS[:content_type] => "#{MIME_TYPE}; charset=#{CHARSET}"
}
serialized = case data
when Hash
serialize_hash(data)
when Array
serialize_array(data)
else
{ value: data }
end
{
headers: headers,
body: JSON.generate(serialized)
}
end
def self.serialize_hash(hash)
# Transform keys to camelCase if requested
hash.transform_keys { |key| key.to_s.frozen? ? key : key.to_s }
end
def self.serialize_array(array)
{
items: array,
count: array.length
}
end
def self.demonstrate_serialization
puts "\n=== JSON Serialization ==="
test_data = [
{ name: "Alice", age: 30, email: "alice@example.com" },
[1, 2, 3, 4, 5],
"Simple string value"
]
test_data.each do |data|
result = serialize(data)
puts "Data: #{data.inspect}"
puts "Headers: #{result[:headers]}"
puts "Body: #{result[:body]}"
puts "Body frozen? #{result[:body].frozen?}"
puts
end
end
end
def self.demonstrate_api_usage
puts "=== API Usage Example ==="
# Simulate API responses
user_data = { id: 1, name: "Alice", email: "alice@example.com" }
users_list = [user_data, { id: 2, name: "Bob", email: "bob@example.com" }]
responses = [
APIResponse.success(user_data, "User retrieved"),
APIResponse.error("User not found", "not_found"),
APIResponse.paginated(users_list, 1, 10, 25)
]
responses.each_with_index do |response, i|
puts "Response #{i + 1}:"
puts JSON.pretty_generate(response)
puts
end
end
end
WebDevelopmentExample::Router.demonstrate_routing
WebDevelopmentExample::JSONSerializer.demonstrate_serialization
WebDevelopmentExample.demonstrate_api_usage
# frozen_string_literal: true
class MigrationGuide
def self.common_migration_issues
puts "=== Common Migration Issues ==="
# Issue 1: String modification patterns
def self.old_pattern_demo
# This would fail with frozen strings
begin
message = "Hello"
message << " World" # FrozenError
message
rescue FrozenError => e
puts "Error: #{e.message}"
# Fixed version
message = "Hello"
message = message + " World" # Creates new string
puts "Fixed: #{message}"
end
end
old_pattern_demo
# Issue 2: String buffer patterns
def self.string_buffer_demo
puts "\nString Buffer Pattern:"
# Old (problematic) pattern
def self.old_build_string(items)
result = "" # This becomes frozen
items.each { |item| result << item } # Would fail
result
rescue FrozenError
# New pattern
result = +"" # Explicitly mutable
items.each { |item| result << item }
result
end
# Better pattern
def self.new_build_string(items)
items.join # More efficient anyway
end
items = ["a", "b", "c", "d"]
puts "Old pattern fixed: #{old_build_string(items)}"
puts "New pattern: #{new_build_string(items)}"
end
string_buffer_demo
# Issue 3: Default parameter modification
def self.default_parameter_demo
puts "\nDefault Parameter Pattern:"
# Problematic pattern
def problematic_method(options = {})
options[:default] = "value" # Modifies frozen hash
options
rescue FrozenError => e
puts "Error with options: #{e.message}"
nil
end
# Fixed pattern
def fixed_method(options = {})
options = options.dup # Create mutable copy
options[:default] = "value"
options
end
result = problematic_method
puts "Problematic result: #{result}"
result = fixed_method
puts "Fixed result: #{result}"
end
default_parameter_demo
end
def self.migration_tools
puts "\n=== Migration Tools and Techniques ==="
# Tool 1: Unary plus operator
def self.unary_plus_demo
frozen_string = "I am frozen"
mutable_string = +frozen_string # Unary plus creates mutable copy
puts "Original frozen? #{frozen_string.frozen?}"
puts "Mutable copy frozen? #{mutable_string.frozen?}"
mutable_string << " but now I'm not!"
puts "Modified: #{mutable_string}"
end
unary_plus_demo
# Tool 2: -@ operator for deduplication
def self.deduplication_demo
puts "\nString Deduplication:"
# Create multiple instances of the same string
strings = 5.times.map { "duplicate".dup }
puts "Before deduplication: #{strings.map(&:object_id).uniq.length} unique objects"
# Deduplicate using -@ operator
deduplicated = strings.map { |s| -s }
puts "After deduplication: #{deduplicated.map(&:object_id).uniq.length} unique objects"
puts "All point to same object? #{deduplicated.map(&:object_id).uniq.length == 1}"
end
deduplication_demo
# Tool 3: String modification helpers
module StringHelpers
def self.safe_append(string, suffix)
if string.frozen?
string + suffix
else
string << suffix
end
end
def self.safe_modify(string, &block)
working_copy = string.frozen? ? string.dup : string
block.call(working_copy)
working_copy
end
end
def self.helper_demo
puts "\nString Helper Methods:"
frozen_str = "Hello"
mutable_str = "Hello".dup
result1 = StringHelpers.safe_append(frozen_str, " World")
result2 = StringHelpers.safe_append(mutable_str, " World")
puts "Frozen result: #{result1}"
puts "Mutable result: #{result2}"
# Using safe_modify
result3 = StringHelpers.safe_modify("test string") do |s|
s.upcase!
s.reverse!
end
puts "Safe modify result: #{result3}"
end
helper_demo
end
def self.performance_best_practices
puts "\n=== Performance Best Practices ==="
# Practice 1: Use string interpolation for dynamic content
def self.interpolation_practice
name = "Alice"
age = 30
# Good: Creates one new string
message = "User #{name} is #{age} years old"
# Less efficient: Multiple concatenations
message2 = "User " + name + " is " + age.to_s + " years old"
puts "Interpolated: #{message}"
puts "Concatenated: #{message2}"
end
interpolation_practice
# Practice 2: Use constants for repeated strings
module Constants
ERROR_MESSAGES = {
not_found: "Resource not found",
unauthorized: "Access denied",
invalid_params: "Invalid parameters"
}.freeze
def self.get_error_message(type)
ERROR_MESSAGES[type] || "Unknown error"
end
end
def self.constants_practice
puts "\nConstants Practice:"
error_types = [:not_found, :unauthorized, :invalid_params, :unknown]
error_types.each do |type|
message = Constants.get_error_message(type)
puts "#{type}: #{message} (frozen: #{message.frozen?})"
end
end
constants_practice
# Practice 3: Efficient string building
def self.string_building_practice
puts "\nString Building Practice:"
items = %w[apple banana cherry date elderberry]
# Efficient: Join with pre-allocated separator
list1 = items.join(", ")
# Efficient: String interpolation
list2 = "Items: #{items.join(', ')}"
# Less efficient but sometimes necessary: Manual building
result = +"" # Explicitly mutable
items.each_with_index do |item, index|
result << item
result << ", " unless index == items.length - 1
end
puts "Joined: #{list1}"
puts "Interpolated: #{list2}"
puts "Manual: #{result}"
end
string_building_practice
end
end
MigrationGuide.common_migration_issues
MigrationGuide.migration_tools
MigrationGuide.performance_best_practices
# frozen_string_literal: true
class AdvancedUsage
def self.demonstrate_edge_cases
puts "=== Edge Cases ==="
# Edge case 1: Regexp literals are also affected
pattern = /hello/i
puts "Regexp source frozen? #{pattern.source.frozen?}"
# Edge case 2: Symbol to_s returns frozen strings
symbol = :test_symbol
symbol_string = symbol.to_s
puts "Symbol to_s frozen? #{symbol_string.frozen?}"
# Edge case 3: Exception messages
begin
raise StandardError, "This is an error message"
rescue StandardError => e
puts "Exception message frozen? #{e.message.frozen?}"
end
# Edge case 4: File operations
require 'tempfile'
Tempfile.create('test') do |file|
file.write("test content")
file.rewind
content = file.read
puts "File content frozen? #{content.frozen?}"
end
end
def self.demonstrate_encoding_behavior
puts "\n=== Encoding Behavior ==="
# Frozen strings with different encodings
utf8_string = "Hello 世界"
ascii_string = "Hello World".encode('ASCII')
puts "UTF-8 string: #{utf8_string} (encoding: #{utf8_string.encoding}, frozen: #{utf8_string.frozen?})"
puts "ASCII string: #{ascii_string} (encoding: #{ascii_string.encoding}, frozen: #{ascii_string.frozen?})"
# Encoding operations create new strings
converted = utf8_string.encode('ASCII', invalid: :replace, undef: :replace)
puts "Converted: #{converted} (frozen: #{converted.frozen?})"
end
def self.demonstrate_thread_safety
puts "\n=== Thread Safety Benefits ==="
shared_string = "I am shared between threads"
results = []
mutex = Mutex.new
threads = 5.times.map do |i|
Thread.new do
# Frozen strings are safe to share
local_result = "Thread #{i}: #{shared_string}"
mutex.synchronize do
results << local_result
end
end
end
threads.each(&:join)
puts "Shared string frozen? #{shared_string.frozen?}"
results.each { |result| puts result }
# Check if all results reference the same shared string parts
object_ids = results.map { |r| r.match(/: (.+)$/)[1].object_id }.uniq
puts "All threads used same string object? #{object_ids.length == 1}"
end
def self.demonstrate_memory_profiling
puts "\n=== Memory Profiling ==="
# Helper to track object allocations
def self.track_allocations
before = ObjectSpace.count_objects
yield
after = ObjectSpace.count_objects
{
strings: after[:T_STRING] - before[:T_STRING],
objects: after[:T_OBJECT] - before[:T_OBJECT],
arrays: after[:T_ARRAY] - before[:T_ARRAY],
hashes: after[:T_HASH] - before[:T_HASH]
}
end
# Test 1: Frozen string usage
allocations1 = track_allocations do
strings = 100.times.map { "constant string" }
end
# Test 2: Mutable string usage
allocations2 = track_allocations do
strings = 100.times.map { "constant string".dup }
end
puts "Frozen strings allocated: #{allocations1[:strings]} objects"
puts "Mutable strings allocated: #{allocations2[:strings]} objects"
puts "Memory savings: #{allocations2[:strings] - allocations1[:strings]} objects"
end
module StringPool
# String pool implementation using frozen strings
@pool = {}
def self.intern(string)
frozen = string.frozen? ? string : -string
@pool[frozen] ||= frozen
end
def self.size
@pool.size
end
def self.clear
@pool.clear
end
end
def self.demonstrate_string_pool
puts "\n=== String Pool Implementation ==="
# Add strings to pool
strings = ["hello", "world", "hello", "ruby", "world", "hello"]
strings.each { |s| StringPool.intern(s) }
puts "Original strings: #{strings.length}"
puts "Pooled strings: #{StringPool.size}"
# Demonstrate that pooled strings are the same object
str1 = StringPool.intern("hello")
str2 = StringPool.intern("hello")
puts "Same object? #{str1.equal?(str2)}"
puts "Object ID: #{str1.object_id}"
end
end
AdvancedUsage.demonstrate_edge_cases
AdvancedUsage.demonstrate_encoding_behavior
AdvancedUsage.demonstrate_thread_safety
AdvancedUsage.demonstrate_memory_profiling
AdvancedUsage.demonstrate_string_pool
Watch and learn frozen string literals & immutability