CrackedRuby logo

CrackedRuby

Symbol vs String

Understanding Ruby's Symbol and String data types, their fundamental differences, performance characteristics, and appropriate usage patterns.

Core Built-in Classes Symbol Class
2.3.2

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