Overview
Ruby provides two distinct data types for representing text-like data: String and Symbol. Strings represent mutable sequences of characters, while symbols represent immutable identifiers. Ruby stores strings as separate objects in memory, creating new instances each time identical string literals appear. Symbols operate differently - Ruby maintains a global symbol table where each unique symbol appears exactly once, making them immutable and memory-efficient for repeated use.
# String objects - each creates a new instance
str1 = "hello"
str2 = "hello"
str1.object_id == str2.object_id # => false
# Symbol objects - same symbol reuses the same object
sym1 = :hello
sym2 = :hello
sym1.object_id == sym2.object_id # => true
String objects inherit from the String class and provide extensive methods for manipulation, searching, and modification. The String class offers over 100 instance methods including #gsub
, #split
, #upcase
, and #concat
. Strings support encoding specifications and can represent any sequence of bytes in various character encodings.
text = "Ruby Programming"
text.downcase! # Modifies the original string
text.gsub!(/ruby/i, "Go") # In-place substitution
puts text # => "go programming"
Symbol objects inherit from the Symbol class, which provides a limited set of methods focused on comparison and conversion. Symbols cannot be modified after creation - operations that would change a symbol instead return new objects. Ruby automatically converts string literals to symbols when used in contexts requiring symbols, such as hash keys or method names in metaprogramming.
The fundamental distinction affects memory usage, performance, and appropriate use cases. Strings excel at representing user data that requires manipulation, while symbols excel at representing fixed identifiers that appear repeatedly throughout an application.
Basic Usage
String Creation and Manipulation
Ruby creates strings using double quotes, single quotes, or heredoc syntax. String literals support interpolation with double quotes and escape sequences. Ruby stores each string literal as a separate object, even when content matches existing strings.
# String creation methods
name = "Alice"
greeting = "Hello, #{name}!" # Interpolation
path = 'C:\Users\Alice' # Single quotes prevent interpolation
description = <<~TEXT
This is a multi-line
string with indentation
TEXT
# String modification
greeting.upcase! # => "HELLO, ALICE!"
greeting << " Welcome!" # => "HELLO, ALICE! Welcome!"
greeting[0] = "h" # => "hELLO, ALICE! Welcome!"
String methods enable extensive text processing. The String class provides methods for searching (#include?
, #match
), extraction (#slice
, #partition
), transformation (#reverse
, #strip
), and comparison (#casecmp
, #start_with?
).
text = " Ruby Programming Language "
clean_text = text.strip.downcase # => "ruby programming language"
words = clean_text.split(' ') # => ["ruby", "programming", "language"]
first_word = clean_text[0, 4] # => "ruby"
contains_prog = clean_text.include?("prog") # => true
Symbol Creation and Usage
Ruby creates symbols using colon syntax or by converting strings. Symbol creation through String#to_sym
or String#intern
adds the symbol to Ruby's global symbol table. Once created, symbols persist in memory for the entire program duration.
# Symbol creation methods
language = :ruby
framework = :"Ruby on Rails" # Symbols with spaces
method_name = "calculate".to_sym # String to symbol conversion
another_sym = "calculate".intern # Alternative conversion method
# Symbol usage in hashes
config = {
host: "localhost",
port: 3000,
ssl: false
}
# Method definition and calling
define_method(:greet) do |name|
"Hello, #{name}!"
end
Symbols commonly serve as hash keys, method names, and constant identifiers. Ruby's hash implementation optimizes for symbol keys, providing faster access compared to string keys. Symbols also appear in method definitions, attribute accessors, and metaprogramming contexts.
# Symbol keys vs string keys
symbol_hash = { name: "Alice", age: 30 }
string_hash = { "name" => "Alice", "age" => 30 }
# Accessing hash values
puts symbol_hash[:name] # => "Alice"
puts string_hash["name"] # => "Alice"
# Metaprogramming with symbols
class Person
attr_accessor :name, :age # Symbols define method names
def initialize(attributes = {})
attributes.each do |key, value|
send("#{key}=", value) if respond_to?("#{key}=")
end
end
end
Conversion Between Types
Ruby provides methods for converting between strings and symbols. The conversion process creates new objects when necessary, following each type's characteristics. String to symbol conversion adds entries to the symbol table, while symbol to string conversion creates new string objects.
# String to symbol
text = "method_name"
symbol = text.to_sym # => :method_name
symbol2 = text.intern # => :method_name (alternative)
# Symbol to string
identifier = :user_id
string = identifier.to_s # => "user_id"
string2 = identifier.id2name # => "user_id" (alternative)
# Verification
puts symbol.class # => Symbol
puts string.class # => String
Performance & Memory
Memory Allocation Patterns
Strings allocate new memory for each instance, even with identical content. Ruby's garbage collector manages string memory, deallocating unused string objects during collection cycles. String objects consume memory proportional to their content length plus object overhead.
# Memory allocation demonstration
1000.times do
str = "repeated_string" # Creates 1000 separate String objects
end
# Memory-efficient alternative with symbols
1000.times do
sym = :repeated_symbol # Reuses the same Symbol object
end
Symbols allocate memory once per unique symbol in the global symbol table. Ruby never garbage collects symbols, making them permanent fixtures in memory. Symbol memory usage includes the symbol name, hash calculation, and symbol table entry overhead.
# Symbol table persistence
def create_symbols
1000.times do |i|
"symbol_#{i}".to_sym # Creates 1000 permanent symbols
end
end
create_symbols
# All 1000 symbols remain in memory indefinitely
puts Symbol.all_symbols.count # Includes the 1000 new symbols
Performance Benchmarks
Symbol comparison operates through object identity checks, providing O(1) performance. String comparison requires character-by-character evaluation, resulting in O(n) performance where n represents string length. Hash operations with symbol keys outperform string keys due to pre-computed hash values.
# Comparison performance difference
symbol1 = :performance_test
symbol2 = :performance_test
string1 = "performance_test"
string2 = "performance_test"
# Symbol comparison: object identity check
result1 = symbol1 == symbol2 # Fast - compares object_id
# String comparison: character evaluation
result2 = string1 == string2 # Slower - compares each character
Hash access patterns demonstrate significant performance differences. Symbol keys enable faster hash operations because Ruby pre-calculates and caches symbol hash values. String keys require hash recalculation on each access.
# Hash performance comparison
symbol_hash = {}
string_hash = {}
# Population
1000.times do |i|
symbol_hash[:"key_#{i}"] = i
string_hash["key_#{i}"] = i
end
# Access patterns
# Symbol key access: pre-computed hash lookup
value1 = symbol_hash[:key_500]
# String key access: hash calculation required
value2 = string_hash["key_500"]
Memory Optimization Strategies
String freeze optimization prevents object duplication for frozen strings. Ruby 2.1+ automatically optimizes frozen string literals, reusing identical frozen strings across the program. This optimization bridges the gap between string flexibility and symbol memory efficiency.
# Frozen string optimization
frozen_string = "constant_value".freeze
# Ruby 2.3+ magic comment enables automatic freezing
# frozen_string_literal: true
# Optimization verification
str1 = "optimized"
str2 = "optimized"
puts str1.object_id == str2.object_id # => true (if frozen_string_literal: true)
Symbol table management requires careful consideration in dynamic scenarios. Applications that dynamically generate symbols risk memory leaks since Ruby never releases symbol table entries. Use strings for dynamic content and reserve symbols for static identifiers.
# Problematic: dynamic symbol creation
def bad_example(user_input)
user_input.to_sym # Creates permanent symbols from user data
end
# Better: string usage for dynamic content
def good_example(user_input)
user_input.to_s # Creates garbage-collectable strings
end
Advanced Usage
Metaprogramming with Symbols
Symbols enable sophisticated metaprogramming patterns through their role as method and attribute identifiers. Ruby's metaprogramming methods accept symbols for method names, allowing dynamic method definition and invocation. Symbol-based metaprogramming provides cleaner syntax compared to string alternatives.
class DynamicMethods
# Define methods dynamically using symbols
[:create, :update, :delete].each do |action|
define_method(:"#{action}_record") do |data|
puts "#{action.capitalize}ing record with #{data}"
end
end
# Dynamic attribute creation
def self.attr_status(*statuses)
statuses.each do |status|
define_method(:"#{status}?") do
@status == status
end
define_method(:"set_#{status}") do
@status = status
end
end
end
attr_status :active, :inactive, :pending
end
# Usage demonstrates symbol-based method creation
obj = DynamicMethods.new
obj.create_record({ name: "Alice" }) # => "Creating record with {:name=>\"Alice\"}"
obj.set_active
puts obj.active? # => true
Symbol table introspection provides insights into application structure. Ruby exposes the symbol table through Symbol.all_symbols
, enabling analysis of method names, constants, and identifiers throughout the application.
# Symbol table analysis
class SymbolAnalyzer
def self.analyze_symbols(pattern = nil)
symbols = Symbol.all_symbols
if pattern
symbols = symbols.select { |sym| sym.to_s.match(pattern) }
end
{
count: symbols.count,
samples: symbols.first(10),
memory_estimate: symbols.sum { |sym| sym.to_s.bytesize + 40 } # Rough estimate
}
end
def self.find_method_symbols
symbols = Symbol.all_symbols.select do |sym|
sym.to_s.match(/\A[a-z_][a-z0-9_]*[!?=]?\z/)
end
grouped = symbols.group_by do |sym|
case sym.to_s
when /\?\z/ then :predicate
when /!\z/ then :mutating
when /=\z/ then :setter
else :regular
end
end
grouped.transform_values(&:count)
end
end
# Analysis results
puts SymbolAnalyzer.analyze_symbols(/\Auser_/)
# => {:count=>15, :samples=>[:user_id, :user_name, ...], :memory_estimate=>1240}
puts SymbolAnalyzer.find_method_symbols
# => {:regular=>2847, :predicate=>156, :mutating=>89, :setter=>234}
String Interning and Symbol Creation
String interning converts strings to symbols while managing memory implications. Applications can implement controlled interning strategies that balance memory efficiency with dynamic content requirements. Custom interning approaches provide middle ground between string flexibility and symbol performance.
class ManagedInternPool
def initialize(max_size = 1000)
@pool = {}
@max_size = max_size
@access_count = Hash.new(0)
end
def intern_string(string)
return string.to_sym if @pool.size < @max_size
# Track access patterns for eviction decisions
@access_count[string] += 1
if @access_count[string] > 5 # Threshold for interning
symbol = string.to_sym
@pool[string] = symbol
symbol
else
string # Keep as string if rarely accessed
end
end
def stats
{
interned_count: @pool.size,
access_patterns: @access_count.sort_by { |_, count| -count }.first(10)
}
end
end
# Managed interning usage
intern_pool = ManagedInternPool.new
1000.times do |i|
key = "dynamic_key_#{i % 100}" # Repeated keys
result = intern_pool.intern_string(key)
end
puts intern_pool.stats
# Shows which strings were interned based on access frequency
Advanced String Processing
String processing with encoding considerations requires careful handling of byte sequences and character boundaries. Ruby's string methods respect encoding settings, but mixing encodings or processing binary data needs explicit encoding management.
class EncodingAwareProcessor
def self.safe_process(input_string, target_encoding = Encoding::UTF_8)
# Handle potential encoding issues
string = input_string.dup
unless string.valid_encoding?
# Attempt to clean invalid byte sequences
string = string.encode(target_encoding,
invalid: :replace,
undef: :replace,
replace: '?')
end
# Convert to target encoding if needed
if string.encoding != target_encoding
string = string.encode(target_encoding)
end
# Process with encoding safety
{
original_encoding: input_string.encoding,
processed_encoding: string.encoding,
byte_size: string.bytesize,
char_count: string.length,
processed_content: string
}
rescue Encoding::ConverterNotFoundError => e
{ error: "Encoding conversion failed: #{e.message}" }
end
def self.analyze_string_composition(string)
{
ascii_chars: string.count("\x00-\x7F"),
multibyte_chars: string.length - string.count("\x00-\x7F"),
byte_size: string.bytesize,
encoding: string.encoding.name,
valid_encoding: string.valid_encoding?
}
end
end
# Advanced processing examples
mixed_content = "Hello 世界! Café naïve résumé"
result = EncodingAwareProcessor.safe_process(mixed_content)
analysis = EncodingAwareProcessor.analyze_string_composition(mixed_content)
puts result
# => {:original_encoding=>#<Encoding:UTF-8>, :processed_encoding=>#<Encoding:UTF-8>, ...}
puts analysis
# => {:ascii_chars=>13, :multibyte_chars=>11, :byte_size=>27, ...}
Common Pitfalls
Symbol Memory Leaks
Dynamic symbol creation causes irreversible memory consumption because Ruby never garbage collects symbols. Applications that convert user input or dynamically generated strings to symbols accumulate permanent memory allocations. This pattern particularly affects web applications processing arbitrary user data.
# Problematic pattern: user input to symbols
class BadUserPreferences
def initialize
@preferences = {}
end
def set_preference(key, value)
# DANGER: Creates permanent symbols from user input
@preferences[key.to_sym] = value
end
def get_preference(key)
@preferences[key.to_sym]
end
end
# Memory leak demonstration
bad_prefs = BadUserPreferences.new
10_000.times do |i|
bad_prefs.set_preference("user_setting_#{i}", "value")
# Creates 10,000 permanent symbols
end
puts Symbol.all_symbols.count # Count has increased by 10,000
Safe alternatives use string keys or controlled symbol creation. Validate and limit symbol creation to prevent unbounded memory growth. Consider frozen strings for repeated access patterns that need memory efficiency without permanence.
# Safe pattern: controlled symbol usage
class SafeUserPreferences
ALLOWED_PREFERENCE_KEYS = %i[
theme color_scheme language timezone notification_enabled
].freeze
def initialize
@preferences = {}
end
def set_preference(key, value)
symbol_key = key.to_s.to_sym
if ALLOWED_PREFERENCE_KEYS.include?(symbol_key)
@preferences[symbol_key] = value
else
# Use string keys for arbitrary preferences
@preferences[key.to_s] = value
end
end
def get_preference(key)
symbol_key = key.to_s.to_sym
if ALLOWED_PREFERENCE_KEYS.include?(symbol_key)
@preferences[symbol_key]
else
@preferences[key.to_s]
end
end
end
String Mutability Surprises
String mutability creates unexpected behavior when strings are shared between variables or passed to methods. Modifications to one reference affect all references to the same string object. This sharing can cause subtle bugs when strings are modified in place.
# Shared string mutation problem
original = "shared content"
reference = original
# Mutation affects both variables
reference.upcase!
puts original # => "SHARED CONTENT" (unexpected!)
puts reference # => "SHARED CONTENT"
# Method parameter mutation
def process_text(text)
text.gsub!(/content/, "data") # Modifies original string
text.strip!
end
input = " file content "
process_text(input)
puts input # => "file data" (caller's string modified)
Defensive copying prevents unintended mutations. Create new string objects when modifications are needed, or use non-mutating methods that return new strings instead of modifying originals.
# Safe string handling patterns
class SafeStringProcessor
def self.process_defensively(input)
# Create a copy to avoid mutating the original
working_copy = input.dup
working_copy.strip!
working_copy.downcase!
working_copy
end
def self.process_immutably(input)
# Use non-mutating methods
input.strip.downcase
end
def self.process_with_freeze(input)
# Freeze to prevent mutations
frozen_input = input.freeze
frozen_input.strip.downcase # Returns new string
rescue FrozenError => e
puts "Cannot modify frozen string: #{e.message}"
nil
end
end
# Safe usage examples
text = " PROCESSING TEXT "
result1 = SafeStringProcessor.process_defensively(text)
result2 = SafeStringProcessor.process_immutably(text)
puts text # => " PROCESSING TEXT " (unchanged)
puts result1 # => "processing text"
puts result2 # => "processing text"
Performance Misconceptions
Developers often assume symbols provide universal performance benefits, but inappropriate symbol usage can decrease performance. Symbol comparison advantages only apply when comparing identical symbols. String-to-symbol conversion costs can outweigh comparison benefits in certain scenarios.
# Misleading symbol usage
class PerformanceTrap
def slow_symbol_comparison(input_array)
# Converting strings to symbols for each comparison
# eliminates performance benefits
input_array.select do |item|
item.to_sym == :target_value # Conversion cost > comparison benefit
end
end
def better_string_comparison(input_array)
# Direct string comparison without conversion
input_array.select do |item|
item == "target_value"
end
end
def optimal_symbol_usage(symbol_array)
# Symbols compared directly without conversion
symbol_array.select do |item|
item == :target_value
end
end
end
# Performance test setup
string_data = ["target_value", "other", "target_value"] * 1000
symbol_data = [:target_value, :other, :target_value] * 1000
trap = PerformanceTrap.new
# The slow_symbol_comparison actually performs worse than string comparison
# due to conversion overhead
Hash key performance differences depend on usage patterns. Symbol keys provide advantages for static keys accessed repeatedly, but string keys may perform better when keys are generated dynamically or accessed infrequently.
class HashKeyPerformance
def self.benchmark_static_access
symbol_hash = { name: "Alice", age: 30, city: "Boston" }
string_hash = { "name" => "Alice", "age" => 30, "city" => "Boston" }
# Static access pattern favors symbols
1000.times do
symbol_hash[:name] # Fast: pre-computed hash
string_hash["name"] # Slower: hash computation
end
end
def self.benchmark_dynamic_access(keys)
symbol_hash = {}
string_hash = {}
keys.each_with_index do |key, index|
symbol_hash[key.to_sym] = index
string_hash[key] = index
end
# Dynamic access with conversion eliminates symbol benefits
keys.each do |key|
symbol_hash[key.to_sym] # Slow: conversion + lookup
string_hash[key] # Fast: direct lookup
end
end
end
Encoding and Symbol Interactions
Symbol creation from strings with different encodings can produce unexpected results. Ruby creates distinct symbols for strings that appear identical but have different byte representations due to encoding differences. This behavior affects symbol table management and comparison operations.
# Encoding-based symbol differences
utf8_string = "café"
latin1_string = "café".encode("ISO-8859-1")
utf8_symbol = utf8_string.to_sym
latin1_symbol = latin1_string.to_sym
puts utf8_symbol == latin1_symbol # => false (different symbols!)
puts utf8_symbol.object_id == latin1_symbol.object_id # => false
# Symbol table contains both versions
puts Symbol.all_symbols.include?(utf8_symbol) # => true
puts Symbol.all_symbols.include?(latin1_symbol) # => true
# Demonstration of the problem in practice
class EncodingSymbolIssue
def initialize
@cache = {}
end
def store_value(key, value)
symbol_key = key.to_sym # Problematic with mixed encodings
@cache[symbol_key] = value
end
def retrieve_value(key)
symbol_key = key.to_sym
@cache[symbol_key]
end
end
# Problem scenario
cache = EncodingSymbolIssue.new
cache.store_value("café", "French coffee") # UTF-8 symbol
result = cache.retrieve_value("café".encode("ISO-8859-1")) # Latin-1 symbol
puts result # => nil (different symbols, cache miss)
Reference
String Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#+(other) |
other (String) | String | Concatenates strings, returns new string |
#<<(other) |
other (String) | String | Appends to string, modifies original |
#[](index) |
index (Integer/Range) | String/nil | Extracts characters at index or range |
#[]=(index, replacement) |
index (Integer/Range), replacement (String) | String | Replaces characters at index |
#bytesize |
none | Integer | Returns byte count in current encoding |
#capitalize |
none | String | Returns string with first character uppercased |
#capitalize! |
none | String/nil | Modifies string, uppercases first character |
#casecmp(other) |
other (String) | Integer/nil | Case-insensitive comparison |
#center(width, padstr) |
width (Integer), padstr (String) | String | Centers string in field of given width |
#chomp(separator) |
separator (String) | String | Removes trailing separator |
#chomp!(separator) |
separator (String) | String/nil | Modifies string, removes trailing separator |
#downcase |
none | String | Returns lowercase copy |
#downcase! |
none | String/nil | Modifies string to lowercase |
#each_line(**opts) |
separator (String), chomp (Boolean) | Enumerator/String | Iterates over lines |
#empty? |
none | Boolean | Returns true if string length is zero |
#encoding |
none | Encoding | Returns string's encoding object |
#end_with?(*suffixes) |
suffixes (String) | Boolean | Tests if string ends with any suffix |
#gsub(pattern, replacement) |
pattern (Regexp/String), replacement (String/Hash) | String | Replaces pattern matches |
#gsub!(pattern, replacement) |
pattern (Regexp/String), replacement (String/Hash) | String/nil | Modifies string, replaces matches |
#include?(substring) |
substring (String) | Boolean | Tests if string contains substring |
#index(substring, offset) |
substring (String/Regexp), offset (Integer) | Integer/nil | Returns index of first match |
#length |
none | Integer | Returns character count |
#ljust(width, padstr) |
width (Integer), padstr (String) | String | Left-justifies string in field |
#match(pattern, pos) |
pattern (Regexp), pos (Integer) | MatchData/nil | Matches pattern against string |
#partition(separator) |
separator (String/Regexp) | Array | Splits string into three parts |
#reverse |
none | String | Returns string with characters reversed |
#reverse! |
none | String | Modifies string, reverses characters |
#rindex(substring, offset) |
substring (String/Regexp), offset (Integer) | Integer/nil | Returns index of last match |
#rjust(width, padstr) |
width (Integer), padstr (String) | String | Right-justifies string in field |
#scan(pattern) |
pattern (Regexp) | Array | Returns array of pattern matches |
#size |
none | Integer | Alias for length |
#slice(index) |
index (Integer/Range) | String/nil | Extracts substring (alias for []) |
#split(pattern, limit) |
pattern (String/Regexp), limit (Integer) | Array | Splits string into array |
#start_with?(*prefixes) |
prefixes (String) | Boolean | Tests if string starts with any prefix |
#strip |
none | String | Returns string with whitespace removed |
#strip! |
none | String/nil | Modifies string, removes whitespace |
#sub(pattern, replacement) |
pattern (Regexp/String), replacement (String/Hash) | String | Replaces first pattern match |
#sub!(pattern, replacement) |
pattern (Regexp/String), replacement (String/Hash) | String/nil | Modifies string, replaces first match |
#to_i(base) |
base (Integer) | Integer | Converts string to integer |
#to_f |
none | Float | Converts string to float |
#to_s |
none | String | Returns self |
#to_sym |
none | Symbol | Converts string to symbol |
#upcase |
none | String | Returns uppercase copy |
#upcase! |
none | String/nil | Modifies string to uppercase |
Symbol Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#<=> |
other (Symbol) | Integer/nil | Comparison operator for sorting |
#== |
other (Object) | Boolean | Equality comparison |
#=== |
other (Object) | Boolean | Case equality (same as ==) |
#=~ |
other (Object) | nil | Always returns nil |
#[] |
index (Integer/Range) | String/nil | Extracts characters from symbol name |
#capitalize |
none | Symbol | Returns symbol with capitalized name |
#casecmp |
other (Symbol) | Integer/nil | Case-insensitive comparison |
#downcase |
none | Symbol | Returns symbol with lowercase name |
#empty? |
none | Boolean | Returns true if symbol name is empty |
#encoding |
none | Encoding | Returns encoding of symbol name |
#end_with? |
suffix (String) | Boolean | Tests if symbol name ends with suffix |
#id2name |
none | String | Converts symbol to string (alias for to_s) |
#inspect |
none | String | Returns symbol representation with colon |
#intern |
none | Symbol | Returns self |
#length |
none | Integer | Returns character count of symbol name |
#match |
pattern (Regexp) | MatchData/nil | Matches pattern against symbol name |
#next |
none | Symbol | Returns symbol with successor name |
#size |
none | Integer | Alias for length |
#slice |
index (Integer/Range) | String/nil | Extracts characters (alias for []) |
#start_with? |
prefix (String) | Boolean | Tests if symbol name starts with prefix |
#succ |
none | Symbol | Alias for next |
#swapcase |
none | Symbol | Returns symbol with swapped case |
#to_i |
none | Integer | Always returns 0 |
#to_proc |
none | Proc | Converts symbol to proc for method calling |
#to_s |
none | String | Converts symbol to string |
#to_sym |
none | Symbol | Returns self |
#upcase |
none | Symbol | Returns symbol with uppercase name |
Class Methods
Method | Class | Parameters | Returns | Description |
---|---|---|---|---|
::all_symbols |
Symbol | none | Array | Returns array of all symbols in symbol table |
::new |
String | str (String) | String | Creates new string (rarely used) |
::try_convert |
String | obj (Object) | String/nil | Attempts to convert object to string |
Conversion Methods
Source | Method | Target | Description |
---|---|---|---|
String | #to_sym |
Symbol | Adds string to symbol table, returns symbol |
String | #intern |
Symbol | Alias for to_sym |
Symbol | #to_s |
String | Creates new string from symbol name |
Symbol | #id2name |
String | Alias for to_s |
Performance Characteristics
Operation | String | Symbol | Notes |
---|---|---|---|
Creation | O(n) memory allocation | O(1) table lookup | Symbol reuses existing objects |
Comparison | O(n) character comparison | O(1) object identity | String length affects comparison time |
Hash key access | O(n) hash calculation | O(1) cached hash | Symbols pre-compute hash values |
Memory usage | Per-instance allocation | Shared object reference | Symbols never garbage collected |
Mutability | Mutable object | Immutable object | String methods can modify content |
Encoding Behavior
Aspect | String | Symbol | Notes |
---|---|---|---|
Encoding storage | Per-string encoding | Encoding preserved in name | Each maintains encoding information |
Encoding conversion | #encode creates new string |
No conversion methods | Symbols cannot change encoding |
Invalid bytes | Can contain invalid sequences | Must have valid encoding | Symbol creation validates encoding |
Binary data | Supports arbitrary byte sequences | Requires valid character data | Symbols represent textual identifiers |