CrackedRuby logo

CrackedRuby

Rightward Assignment Operator (=>)

Overview

Rightward assignment in Ruby uses the => operator to reverse the traditional assignment direction, allowing expressions like value => variable instead of variable = value. Introduced as an experimental feature in Ruby 2.7 and stabilized in Ruby 3.0, this operator serves dual purposes: simple rightward variable assignment and sophisticated pattern matching with destructuring capabilities.

The operator reuses Ruby's hash rocket syntax (=>) in a new context. Unlike traditional leftward assignment using =, rightward assignment enables writing expressions before assigning results to variables. This becomes particularly powerful when combined with Ruby's pattern matching system, allowing developers to simultaneously validate data structure and extract values into local variables.

Ruby 3.0 introduced rightward assignment to make pattern matching more readable than traditional case/in expressions. The feature works with arrays, hashes, and objects that implement the deconstruct or deconstruct_keys methods, providing comprehensive support for complex data destructuring scenarios.

# Simple rightward assignment
42 => answer
puts answer
# => 42

# Hash destructuring with rightward assignment
user_data = {name: "Alice", age: 30, role: "admin"}
user_data => {name:, age:, role:}
puts "#{name} (#{age}) - #{role}"
# => "Alice (30) - admin"

# Array pattern matching
coordinates = [10, 20, 30]
coordinates => [x, y, z]
puts "Position: (#{x}, #{y}, #{z})"
# => "Position: (10, 20, 30)"

Basic Usage

Rightward assignment operates in two primary modes: simple assignment and pattern matching. Simple assignment transfers a value to a variable using rightward syntax, while pattern matching validates structure and extracts multiple values simultaneously.

For basic variable assignment, rightward assignment provides an alternative to traditional leftward assignment. The value appears on the left side of the => operator, with the variable name on the right. This syntax proves useful when chaining operations or when the natural flow of data moves from left to right.

# Traditional vs rightward assignment
traditional = 100 + 50
100 + 50 => rightward

# Both variables now contain 150
puts traditional == rightward
# => true

# Useful in method chains
[1, 2, 3, 4, 5]
  .select(&:even?)
  .map(&:to_s) => even_strings

puts even_strings
# => ["2", "4"]

Pattern matching with rightward assignment enables destructuring complex data structures. Hash patterns extract values using symbol keys, while array patterns work positionally. The operator validates that the structure matches the expected pattern before performing assignments.

# Hash pattern matching
person = {
  first_name: "John",
  last_name: "Doe",
  contact: {
    email: "john@example.com",
    phone: "+1234567890"
  }
}

person => {first_name:, last_name:, contact: {email:}}
full_name = "#{first_name} #{last_name}"
puts "Contact: #{full_name} at #{email}"
# => "Contact: John Doe at john@example.com"

# Array destructuring with type checking
measurement_data = [25.5, "celsius", true]
measurement_data => [Float => temp, String => unit, Boolean => active]
puts "Temperature: #{temp}°#{unit.upcase} (Active: #{active})"
# => "Temperature: 25.5°CELSIUS (Active: true)"

Array patterns support splat operators for handling variable-length collections. The * operator captures remaining elements, while ** in hash patterns captures unmatched key-value pairs.

# Array with splat patterns
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers => [first, second, *middle, second_last, last]
puts "First: #{first}, Last: #{last}, Middle count: #{middle.length}"
# => "First: 1, Last: 10, Middle count: 6"

# Hash with rest pattern
config = {host: "localhost", port: 3000, ssl: true, debug: false, timeout: 30}
config => {host:, port:, **other_options}
puts "Server: #{host}:#{port}"
puts "Other options: #{other_options}"
# => "Server: localhost:3000"
# => "Other options: {:ssl=>true, :debug=>false, :timeout=>30}"

Ruby allows nesting patterns within rightward assignments, enabling extraction from deeply nested data structures. Each level of nesting can include its own pattern matching rules and variable bindings.

# Nested pattern matching
api_response = {
  status: "success",
  data: {
    users: [
      {id: 1, name: "Alice", roles: ["admin", "user"]},
      {id: 2, name: "Bob", roles: ["user"]}
    ],
    metadata: {count: 2, page: 1}
  }
}

api_response => {
  status:,
  data: {
    users: [first_user, *other_users],
    metadata: {count:}
  }
}

first_user => {name:, roles: [primary_role, *secondary_roles]}
puts "Status: #{status}"
puts "First user: #{name} (#{primary_role})"
puts "Total users: #{count}"
# => "Status: success"
# => "First user: Alice (admin)"
# => "Total users: 2"

Advanced Usage

Rightward assignment excels in scenarios involving complex data validation, transformation pipelines, and conditional extraction. Advanced patterns leverage type checking, guard clauses, and custom object destructuring through deconstruct and deconstruct_keys methods.

Type-based pattern matching validates data types while performing assignment. This technique proves valuable for API data processing, configuration validation, and runtime type checking in Ruby's dynamic environment.

# Type validation in assignment patterns
def process_api_response(response)
  response => {
    status: String => status_code,
    data: Hash => payload,
    timestamp: Integer => unix_time
  }

  time = Time.at(unix_time)
  puts "Response #{status_code} received at #{time}"
  payload
rescue NoMatchingPatternError => e
  puts "Invalid response format: #{e.message}"
  nil
end

# Usage with valid data
valid_response = {
  status: "200",
  data: {message: "Success"},
  timestamp: 1640995200
}
process_api_response(valid_response)
# => "Response 200 received at 2022-01-01 00:00:00 UTC"

# Usage with invalid data
invalid_response = {status: 200, data: "Not a hash", timestamp: "invalid"}
process_api_response(invalid_response)
# => "Invalid response format: ..."

Custom objects gain pattern matching capabilities by implementing deconstruct for array-like matching and deconstruct_keys for hash-like matching. These methods define how objects decompose during pattern matching operations.

class Coordinate
  attr_reader :x, :y, :z

  def initialize(x, y, z = 0)
    @x, @y, @z = x, y, z
  end

  def deconstruct
    [x, y, z]
  end

  def deconstruct_keys(keys)
    {x: x, y: y, z: z}
  end
end

class BoundingBox
  attr_reader :min_point, :max_point

  def initialize(min_point, max_point)
    @min_point, @max_point = min_point, max_point
  end

  def deconstruct_keys(keys)
    {min: min_point, max: max_point}
  end
end

# Array-style destructuring
point = Coordinate.new(10, 20, 30)
point => [px, py, pz]
puts "3D Point: (#{px}, #{py}, #{pz})"
# => "3D Point: (10, 20, 30)"

# Hash-style destructuring
point => {x:, y:}
puts "2D projection: (#{x}, #{y})"
# => "2D projection: (10, 20)"

# Nested custom object patterns
bbox = BoundingBox.new(
  Coordinate.new(0, 0),
  Coordinate.new(100, 200)
)

bbox => {min: {x: min_x, y: min_y}, max: {x: max_x, y: max_y}}
area = (max_x - min_x) * (max_y - min_y)
puts "Bounding box area: #{area}"
# => "Bounding box area: 20000"

The pin operator (^) prevents variable reassignment during pattern matching, instead using existing variable values as match constraints. This enables powerful validation patterns where current values must match specific requirements.

# Pin operator for value validation
expected_version = "2.1.0"
config_data = {
  app_name: "MyApp",
  version: "2.1.0",
  features: ["auth", "payments", "analytics"]
}

# Validate version matches expected value
config_data => {app_name:, version: ^expected_version, features:}
puts "#{app_name} v#{expected_version} loaded with #{features.length} features"
# => "MyApp v2.1.0 loaded with 3 features"

# Advanced pin usage with computed values
def validate_user_permissions(user_data, required_role)
  user_data => {
    id: Integer => user_id,
    roles: [*all_roles],
    active: true,  # Literal match
    permissions: {^required_role => role_permissions}
  }

  puts "User #{user_id} has #{role_permissions.length} #{required_role} permissions"
  role_permissions
rescue NoMatchingPatternError
  puts "User lacks required #{required_role} role or is inactive"
  []
end

user = {
  id: 123,
  roles: ["user", "admin", "moderator"],
  active: true,
  permissions: {
    "admin" => ["create", "update", "delete"],
    "user" => ["read"]
  }
}

admin_perms = validate_user_permissions(user, "admin")
# => "User 123 has 3 admin permissions"

Rightward assignment integrates seamlessly with method chaining and functional programming patterns. Complex data transformations can terminate with pattern matching to extract and validate results.

# Functional pipeline with rightward assignment
def analyze_log_entries(log_data)
  log_data
    .select { |entry| entry[:level] == "ERROR" }
    .group_by { |entry| entry[:service] }
    .transform_values { |errors| errors.count }
    .select { |service, count| count >= 3 }
    .sort_by { |service, count| -count } => critical_services

  unless critical_services.empty?
    critical_services => [[most_critical_service, error_count], *other_services]

    puts "ALERT: #{most_critical_service} has #{error_count} errors"
    puts "Other critical services: #{other_services.map(&:first).join(', ')}" unless other_services.empty?
  end

  critical_services
end

logs = [
  {service: "auth", level: "ERROR", message: "Login failed"},
  {service: "auth", level: "ERROR", message: "Token expired"},
  {service: "auth", level: "ERROR", message: "Invalid credentials"},
  {service: "auth", level: "ERROR", message: "Rate limit exceeded"},
  {service: "payments", level: "ERROR", message: "Transaction failed"},
  {service: "payments", level: "ERROR", message: "Card declined"},
  {service: "payments", level: "ERROR", message: "Timeout"},
  {service: "notifications", level: "INFO", message: "Email sent"}
]

analyze_log_entries(logs)
# => "ALERT: auth has 4 errors"
# => "Other critical services: payments"

Error Handling & Debugging

Rightward assignment throws NoMatchingPatternError when patterns cannot be matched, unlike boolean pattern checking which returns false. Understanding error types, implementing proper exception handling, and debugging pattern matching failures requires specific strategies for rightward assignment contexts.

Ruby raises different exception types depending on the pattern matching failure mode. NoMatchingPatternError occurs when the overall pattern structure fails to match, while NoMatchingPatternKeyError indicates missing hash keys during pattern matching.

# NoMatchingPatternError examples
begin
  [1, 2] => [x, y, z]  # Array length mismatch
rescue NoMatchingPatternError => e
  puts "Pattern error: #{e.message}"
  puts "Expected 3 elements, got 2"
end
# => "Pattern error: [1, 2]"

begin
  42 => String  # Type mismatch
rescue NoMatchingPatternError => e
  puts "Type error: #{e.message}"
  puts "Expected String, got Integer"
end
# => "Type error: 42: String === 42 does not return true"

# NoMatchingPatternKeyError for missing keys
user_incomplete = {name: "Alice", age: 30}
begin
  user_incomplete => {name:, age:, email:}  # Missing email key
rescue NoMatchingPatternKeyError => e
  puts "Key error: #{e.message}"
  puts "Missing key: #{e.key}"
  puts "In object: #{e.matchee}"
end
# => "Key error: key not found: :email"
# => "Missing key: email"
# => "In object: {:name=>\"Alice\", :age=>30}"

Defensive programming with rightward assignment involves wrapping potentially failing patterns in exception handlers or using alternative validation approaches. Consider graceful degradation strategies when pattern matching might fail.

def safe_extract_user_info(data)
  # Primary extraction attempt
  begin
    data => {
      profile: {
        name: {first:, last:},
        contact: {email:}
      },
      preferences: {theme:, notifications:}
    }

    return {
      full_name: "#{first} #{last}",
      email: email,
      theme: theme,
      notifications: notifications
    }
  rescue NoMatchingPatternError, NoMatchingPatternKeyError => e
    puts "Primary pattern failed: #{e.message}"
  end

  # Fallback extraction with minimal requirements
  begin
    data => {profile: {name: name_data, contact: contact_data}}

    # Handle various name formats
    name = case name_data
           when String then name_data
           when Hash
             name_data => {first: fname} rescue nil
             name_data => {name: full} rescue nil
             fname || full || "Unknown"
           else "Unknown"
           end

    # Extract email with fallbacks
    email = case contact_data
            when String then contact_data
            when Hash then contact_data[:email] || contact_data["email"] || "No email"
            else "No email"
            end

    return {
      full_name: name,
      email: email,
      theme: "default",
      notifications: true
    }
  rescue => e
    puts "Fallback extraction failed: #{e.message}"
  end

  # Last resort - return defaults
  {
    full_name: "Unknown User",
    email: "No email",
    theme: "default",
    notifications: true
  }
end

# Test with various data formats
complete_data = {
  profile: {
    name: {first: "John", last: "Doe"},
    contact: {email: "john@example.com"}
  },
  preferences: {theme: "dark", notifications: false}
}

incomplete_data = {
  profile: {
    name: "Jane Smith",
    contact: "jane@example.com"
  }
}

malformed_data = {user: "Bob"}

puts safe_extract_user_info(complete_data)
# => {:full_name=>"John Doe", :email=>"john@example.com", :theme=>"dark", :notifications=>false}

puts safe_extract_user_info(incomplete_data)
# => "Primary pattern failed: ..."
# => {:full_name=>"Jane Smith", :email=>"jane@example.com", :theme=>"default", :notifications=>true}

puts safe_extract_user_info(malformed_data)
# => "Primary pattern failed: ..."
# => "Fallback extraction failed: ..."
# => {:full_name=>"Unknown User", :email=>"No email", :theme=>"default", :notifications=>true}

Debugging complex rightward assignment patterns requires understanding how Ruby evaluates nested patterns and where failures occur. Use incremental pattern building and intermediate assignments to isolate problematic sections.

def debug_pattern_matching(complex_data)
  puts "Debugging data: #{complex_data.inspect}"

  # Step 1: Verify top-level structure
  begin
    complex_data => {metadata:, records:}
    puts "✓ Top-level structure valid"
  rescue => e
    puts "✗ Top-level structure invalid: #{e.message}"
    return false
  end

  # Step 2: Verify metadata structure
  begin
    metadata => {version:, count: Integer => expected_count}
    puts "✓ Metadata valid: version=#{version}, count=#{expected_count}"
  rescue => e
    puts "✗ Metadata invalid: #{e.message}"
    return false
  end

  # Step 3: Verify records array length
  begin
    raise "Record count mismatch" unless records.length == expected_count
    puts "✓ Record count matches metadata"
  rescue => e
    puts "✗ Record count issue: #{e.message}"
    return false
  end

  # Step 4: Verify each record structure
  records.each_with_index do |record, index|
    begin
      record => {id: Integer, name: String, active: true | false}
      puts "✓ Record #{index}: valid structure"
    rescue => e
      puts "✗ Record #{index}: invalid structure - #{e.message}"
      puts "  Record data: #{record.inspect}"
      return false
    end
  end

  # Step 5: Full pattern test
  begin
    complex_data => {
      metadata: {version:, count:},
      records: [*all_records]
    }

    all_records => [first_record, *remaining_records]
    first_record => {id: first_id, name: first_name}

    puts "✓ Full pattern successful"
    puts "  First record: #{first_name} (ID: #{first_id})"
    puts "  Total records processed: #{all_records.length}"
    true
  rescue => e
    puts "✗ Full pattern failed: #{e.message}"
    false
  end
end

# Test data
valid_data = {
  metadata: {version: "1.0", count: 2},
  records: [
    {id: 1, name: "Alice", active: true},
    {id: 2, name: "Bob", active: false}
  ]
}

invalid_data = {
  metadata: {version: "1.0", count: "two"},  # count should be Integer
  records: [
    {id: "1", name: "Alice", active: true}   # id should be Integer
  ]
}

debug_pattern_matching(valid_data)
debug_pattern_matching(invalid_data)

Ruby provides warning mechanisms for experimental pattern matching features. Pattern matching warnings are raised at compile time, so runtime warning suppression requires careful timing. Consider development versus production warning strategies.

# Suppress experimental warnings in development
Warning[:experimental] = false if ENV['RAILS_ENV'] == 'development'

# Or suppress selectively for specific code blocks
def with_suppressed_warnings(&block)
  original_setting = Warning[:experimental]
  Warning[:experimental] = false
  result = yield
  Warning[:experimental] = original_setting
  result
end

# Usage in production code with experimental features
experimental_result = with_suppressed_warnings do
  # Use experimental find patterns without warnings
  log_entries = [
    {level: "INFO", message: "Starting"},
    {level: "ERROR", message: "Database error"},
    {level: "INFO", message: "Continuing"},
    {level: "WARN", message: "Low memory"}
  ]

  log_entries => [*, {level: "ERROR", message: error_msg}, *]
  error_msg
end

puts "Found error: #{experimental_result}"
# => "Found error: Database error"

Common Pitfalls

Several subtle behaviors and edge cases can trap developers new to rightward assignment. Understanding operator precedence, variable scoping, pattern evaluation order, and the differences between rightward assignment and boolean pattern matching prevents common mistakes.

Variable scoping with rightward assignment differs from traditional assignment patterns. The parser reads from left-to-right, evaluating the left side before attempting assignment on the right side. This creates potential issues when variable names appear on both sides of the assignment.

# PITFALL: Variable scoping confusion
x = 10
puts x  # => 10

# This doesn't work as expected - undefined variable error
begin
  x => x  # Tries to evaluate right-side x before assignment
rescue NameError => e
  puts "Error: #{e.message}"
end
# => "Error: undefined local variable or method `x'"

# CORRECT: Use different variable names or pin operator
x = 10
x => y          # Assigns x to new variable y
puts y          # => 10

x => ^x         # Pin operator uses existing x value for pattern match
puts "Pin match successful"

# Another common mistake - assuming variable inheritance
outer_var = "hello"
[1, 2, 3] => [a, b, outer_var]  # This creates NEW local variable
puts outer_var  # => 1 (not "hello"!)

# CORRECT: Use pin to preserve outer variable
outer_var = "hello"
[1, 2, 3] => [a, b, inner_var]
puts outer_var  # => "hello"
puts inner_var  # => 3

Method call precedence creates unexpected behavior when rightward assignment appears near method invocations. The assignment operator has specific precedence rules that may not align with developer expectations.

# PITFALL: Method precedence issues
def process_value(val)
  puts "Processing: #{val}"
  val * 2
end

# This doesn't work as expected
begin
  process_value 10 => result  # Parser confusion
rescue SyntaxError => e
  puts "Syntax error with method call precedence"
end

# CORRECT: Use explicit parentheses
process_value(10) => result
puts result  # => 20

# Another precedence trap - chaining methods
class Dataset
  def initialize(data)
    @data = data
  end

  def filter(&block)
    Dataset.new(@data.select(&block))
  end

  def to_a
    @data
  end
end

dataset = Dataset.new([1, 2, 3, 4, 5])

# PITFALL: Method chain precedence
begin
  dataset.filter(&:even?).to_a => even_numbers.length  # Syntax error
rescue => e
  puts "Chain precedence error"
end

# CORRECT: Proper grouping
dataset.filter(&:even?).to_a => even_numbers
length = even_numbers.length
puts "Even numbers: #{even_numbers}, Count: #{length}"

Pattern matching assumptions often lead to errors, particularly around partial matching, type coercion, and nil handling. Hash patterns match subsets by default, while array patterns require exact matches, creating inconsistent behavior expectations.

# PITFALL: Hash vs Array pattern behavior differences
hash_data = {a: 1, b: 2, c: 3, d: 4}
array_data = [1, 2, 3, 4]

# Hash patterns match SUBSETS (this works)
hash_data => {a:, b:}
puts "Hash subset match: a=#{a}, b=#{b}"  # => "Hash subset match: a=1, b=2"

# Array patterns require EXACT matches (this fails)
begin
  array_data => [x, y]  # Tries to match 4 elements with 2-element pattern
rescue NoMatchingPatternError => e
  puts "Array exact match failed: #{e.message}"
end

# CORRECT: Use splat for flexible array matching
array_data => [x, y, *rest]
puts "Array flexible match: x=#{x}, y=#{y}, rest=#{rest}"
# => "Array flexible match: x=1, y=2, rest=[3, 4]"

# PITFALL: Nil handling assumptions
user_data = {name: "Alice", age: nil, email: "alice@example.com"}

# This fails because age is nil, not Integer
begin
  user_data => {name:, age: Integer, email:}
rescue NoMatchingPatternError => e
  puts "Nil value failed Integer match: #{e.message}"
end

# CORRECT: Handle nil explicitly
user_data => {name:, age: nil | Integer => user_age, email:}
puts "Name: #{name}, Age: #{user_age || 'unknown'}, Email: #{email}"

String versus symbol key confusion in hash patterns creates subtle bugs. Pattern matching only works with symbol keys, not string keys, but this restriction isn't always obvious when working with external data sources.

# PITFALL: String vs Symbol key confusion
json_data = {"name" => "John", "age" => 30}  # String keys from JSON
symbol_data = {name: "Jane", age: 25}        # Symbol keys

# This fails silently - no match because of string keys
begin
  json_data => {name:, age:}  # Looking for symbol keys in string-key hash
rescue NoMatchingPatternError => e
  puts "String key pattern failed: #{e.message}"
end

# CORRECT: Convert keys or use string patterns
# Option 1: Convert keys
json_symbol_data = json_data.transform_keys(&:to_sym)
json_symbol_data => {name:, age:}
puts "Converted data: #{name}, #{age}"

# Option 2: Use explicit string key patterns (requires different syntax)
# Note: Direct string key patterns not supported in standard hash patterns
# Must use case/in for string keys
case json_data
in {"name" => name, "age" => age}
  puts "String key case match: #{name}, #{age}"
end

# PITFALL: Mixed key types
mixed_data = {name: "Alice", "age" => 30, :email => "alice@example.com"}

# Partially matches only symbol keys
mixed_data => {name:, email:}  # Works for :name and :email
puts "Mixed match: #{name}, #{email}"
# Note: "age" key ignored because it's a string

Error handling patterns often mask underlying issues through over-broad exception catching. Distinguish between pattern matching failures and other runtime errors for effective debugging.

# PITFALL: Over-broad exception handling
def risky_pattern_extraction(data)
  begin
    data => {users: [{id: first_id, profile: {name:}}]}
    return {success: true, name: name, id: first_id}
  rescue => e
    # This catches ALL errors, not just pattern matching
    return {success: false, error: e.message}
  end
end

# BETTER: Specific exception handling
def safer_pattern_extraction(data)
  begin
    data => {users: [{id: first_id, profile: {name:}}]}
    return {success: true, name: name, id: first_id}
  rescue NoMatchingPatternError, NoMatchingPatternKeyError => pattern_error
    return {success: false, error: "Pattern mismatch: #{pattern_error.message}"}
  rescue => unexpected_error
    # Re-raise unexpected errors for proper debugging
    puts "Unexpected error in pattern extraction: #{unexpected_error.class}"
    raise
  end
end

# Test with various problematic data
empty_data = {}
malformed_data = {users: ["not_a_hash"]}
nil_data = nil

[empty_data, malformed_data, nil_data].each_with_index do |test_data, i|
  puts "Test #{i + 1}: #{safer_pattern_extraction(test_data)}"
end

Reference

Rightward Assignment Operators

Operator Syntax Description
=> expression => variable Simple rightward assignment
=> expression => pattern Pattern matching assignment
=> expression => Type Type-checking assignment
=> expression => {key:} Hash destructuring assignment
=> expression => [var1, var2] Array destructuring assignment

Pattern Matching Syntax Elements

Element Syntax Purpose
Variable Pattern => variable_name Binds matched value to variable
Type Pattern => ClassName Matches specific type/class
Literal Pattern => 42 or => "text" Matches exact literal value
Array Pattern => [a, b, c] Matches array with exact length
Splat Pattern => [a, *rest, z] Captures remaining array elements
Hash Pattern => {key:} Extracts hash value to same-named variable
Hash Alias => {key: variable} Extracts hash value to different variable
Rest Hash => {key:, **rest} Captures remaining hash pairs
Pin Operator => ^variable Uses existing variable value in pattern
Alternative => pattern1 | pattern2 Matches either pattern
As Pattern => pattern => variable Binds entire match to variable

Hash Pattern Matching

Pattern Example Result
Key extraction {name: "Alice"} => {name:} name = "Alice"
Key aliasing {name: "Alice"} => {name: user_name} user_name = "Alice"
Multiple keys {a: 1, b: 2} => {a:, b:} a = 1, b = 2
Nested patterns {user: {name: "Alice"}} => {user: {name:}} name = "Alice"
Rest capture {a: 1, b: 2, c: 3} => {a:, **rest} a = 1, rest = {b: 2, c: 3}
Type checking {age: 30} => {age: Integer} Validates age is Integer
Optional keys Uses case/in with else clause N/A for rightward assignment

Array Pattern Matching

Pattern Example Result
Fixed length [1, 2, 3] => [a, b, c] a = 1, b = 2, c = 3
With splat [1, 2, 3, 4] => [first, *middle, last] first = 1, middle = [2, 3], last = 4
Type patterns [1, "hello"] => [Integer, String] Validates types, no variable binding
Mixed patterns [1, "hello"] => [Integer => num, String => str] num = 1, str = "hello"
Nested arrays [[1, 2], [3, 4]] => [[a, b], [c, d]] a = 1, b = 2, c = 3, d = 4
Rest at start [1, 2, 3, 4] => [*start, x, y] start = [1, 2], x = 3, y = 4
Middle splat [1, 2, 3, 4, 5] => [a, *mid, b] a = 1, mid = [2, 3, 4], b = 5

Exception Hierarchy

Exception Inheritance When Raised
NoMatchingPatternError < StandardError Pattern structure doesn't match
NoMatchingPatternKeyError < NoMatchingPatternError Required hash key missing
StandardError < Exception Parent class for both pattern errors

Exception Attributes

Exception Attributes Description
NoMatchingPatternError #message Human-readable error description
NoMatchingPatternKeyError #key Symbol representing missing key
NoMatchingPatternKeyError #matchee Object being matched against

Method Requirements for Custom Objects

Method Parameters Return Type Usage
#deconstruct None Array Enables array-style pattern matching
#deconstruct_keys keys (Array of symbols) Hash Enables hash-style pattern matching

Precedence Rules

Context Precedence Behavior
Method calls method_name args => var groups as method_name(args) => var
Arithmetic a + b => var groups as (a + b) => var
Ternary operator cond ? a : b => var groups as (cond ? a : b) => var
Logical operators a || b => var groups as a || (b => var)
Assignment chaining a => b => c not supported - syntax error

Pattern Matching vs Boolean Checking

Feature Rightward Assignment (=>) Boolean Check (in)
Return value nil on success true on success
Failure behavior Raises NoMatchingPatternError Returns false
Variable binding Yes Yes
Use in expressions Limited Yes
Error handling Requires rescue blocks Can use conditional logic

Ruby Version Support

Ruby Version Feature Status Notes
2.7.0 Experimental Pattern matching introduced, warnings shown
3.0.0 Stable for case/in Rightward assignment experimental
3.1.0 Stable Rightward assignment and boolean checks stable
3.2.0+ Stable Find patterns no longer experimental

Warning Control

Method Usage Effect
Warning[:experimental] = false Global setting Suppresses experimental warnings
-W:no-experimental Command line flag Suppresses warnings for entire process
Code-level suppression Wrap in warning control block Temporary suppression