Overview
Ruby provides splat operators for flexible assignment operations that handle variable numbers of elements. The single splat operator (*) manages array elements and method arguments, while the double splat operator (**) handles hash key-value pairs. These operators enable pattern matching, destructuring assignments, and dynamic parameter handling.
The splat operators work through Ruby's assignment protocol, converting objects using to_a
for arrays and to_hash
for hashes. Ruby evaluates splat expressions during assignment resolution, creating new array or hash objects as needed.
# Basic array destructuring
first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]
# Hash destructuring with double splat
def process_user(**user_data)
name = user_data[:name]
age = user_data[:age]
end
# Mixed assignment patterns
head, *middle, tail = (1..10).to_a
# head = 1, middle = [2, 3, 4, 5, 6, 7, 8, 9], tail = 10
Ruby implements splat operators at the parser level, generating specialized bytecode for assignment operations. The operators integrate with Ruby's method dispatch system, enabling dynamic argument handling and flexible API design.
Basic Usage
Single splat operators capture remaining array elements during destructuring assignment. Ruby assigns matching elements positionally, collecting unmatched elements in a splat variable.
# Simple array destructuring
numbers = [10, 20, 30, 40]
first, *remaining = numbers
# first = 10, remaining = [20, 30, 40]
# Multiple assignment with splats
a, b, *rest, last = [1, 2, 3, 4, 5, 6]
# a = 1, b = 2, rest = [3, 4, 5], last = 6
# Single element arrays
single, *empty = [42]
# single = 42, empty = []
Method definitions accept splat parameters to handle variable argument counts. Ruby collects excess arguments into array parameters marked with asterisks.
def flexible_method(required, *optional, keyword:)
puts "Required: #{required}"
puts "Optional: #{optional}"
puts "Keyword: #{keyword}"
end
flexible_method(1, 2, 3, 4, keyword: "test")
# Required: 1
# Optional: [2, 3, 4]
# Keyword: test
# Forwarding arguments with splats
def wrapper(*args, **kwargs)
actual_method(*args, **kwargs)
end
Hash destructuring uses double splat operators to capture unmatched key-value pairs. Ruby creates new hash objects containing remaining pairs after explicit assignment.
user_data = { name: "Alice", age: 30, city: "Boston", country: "USA" }
# Hash parameter destructuring
def create_profile(name:, **details)
puts "Creating profile for #{name}"
puts "Additional details: #{details}"
end
create_profile(**user_data)
# Creating profile for Alice
# Additional details: {:age=>30, :city=>"Boston", :country=>"USA"}
# Assignment with hash splats
config = { host: "localhost", port: 3000, timeout: 30, retries: 3 }
connection_opts, **other_opts = config.partition { |k,v| [:host, :port].include?(k) }.map(&:to_h)
Splat operators work with any object implementing appropriate conversion methods. Ruby calls to_a
for array splats and to_hash
for hash splats, enabling custom object integration.
class CustomCollection
def initialize(items)
@items = items
end
def to_a
@items.dup
end
end
collection = CustomCollection.new([1, 2, 3, 4])
first, *rest = collection
# first = 1, rest = [2, 3, 4]
Advanced Usage
Nested splat operations enable complex destructuring patterns for multi-dimensional data structures. Ruby evaluates nested assignments recursively, maintaining proper scoping for each splat variable.
# Nested array destructuring
matrix = [[1, 2], [3, 4, 5], [6]]
(a, b), (*second_row), *remaining_rows = matrix
# a = 1, b = 2, second_row = [3, 4, 5], remaining_rows = [[6]]
# Complex hash destructuring with delegation
class ApiClient
def initialize(base_url:, **connection_opts)
@base_url = base_url
@connection = build_connection(**connection_opts)
end
private
def build_connection(timeout: 30, retries: 3, **http_opts)
# timeout and retries handled specifically
# remaining options forwarded to HTTP client
HTTPClient.new(**http_opts).tap do |client|
client.timeout = timeout
client.retries = retries
end
end
end
client = ApiClient.new(
base_url: "https://api.example.com",
timeout: 60,
headers: { "Authorization" => "Bearer token" },
ssl_verify: true
)
Method chaining with splat operators enables fluent interface patterns. Ruby preserves splat semantics across method calls, supporting complex data transformation pipelines.
class DataProcessor
def initialize(data)
@data = data
end
def filter(*conditions, **options)
filtered = @data.select do |item|
conditions.all? { |condition| condition.call(item) }
end
if options[:limit]
filtered = filtered.first(options[:limit])
end
self.class.new(filtered)
end
def transform(*transformers, **config)
transformed = @data.map do |item|
transformers.reduce(item) do |current, transformer|
transformer.call(current, **config)
end
end
self.class.new(transformed)
end
def to_a
@data
end
end
# Usage with complex splat patterns
processor = DataProcessor.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
positive_filter = ->(x) { x > 0 }
even_filter = ->(x) { x.even? }
square_transform = ->(x, **opts) { opts[:power] ? x ** opts[:power] : x ** 2 }
add_transform = ->(x, increment: 1, **opts) { x + increment }
result = processor
.filter(positive_filter, even_filter, limit: 3)
.transform(square_transform, add_transform, power: 3, increment: 10)
.to_a
# Result: [18, 74, 226] (2³+10, 4³+10, 6³+10)
Metaprogramming with splat operators enables dynamic method definition and delegation patterns. Ruby preserves argument structure through method_missing and define_method calls.
class DynamicProxy
def initialize(target, prefix: nil, **delegation_options)
@target = target
@prefix = prefix
@delegation_options = delegation_options
end
def method_missing(method_name, *args, **kwargs, &block)
actual_method = @prefix ? "#{@prefix}_#{method_name}" : method_name
if @target.respond_to?(actual_method)
@target.public_send(actual_method, *args, **kwargs, &block)
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
actual_method = @prefix ? "#{@prefix}_#{method_name}" : method_name
@target.respond_to?(actual_method, include_private)
end
end
class DatabaseConnection
def user_find(id, **options)
# Find user with options
{ id: id, name: "User #{id}", **options }
end
def user_create(**attributes)
# Create user with attributes
{ id: rand(1000), **attributes, created_at: Time.now }
end
end
# Dynamic proxy with method forwarding
db_proxy = DynamicProxy.new(DatabaseConnection.new, prefix: "user")
user = db_proxy.find(123, include_profile: true)
new_user = db_proxy.create(name: "Alice", email: "alice@example.com")
Common Pitfalls
Splat operator precedence creates unexpected results in complex expressions. Ruby evaluates splats during assignment resolution, not during expression evaluation, leading to counterintuitive behavior.
# Precedence confusion
array = [1, 2, 3]
*first_half, middle, *second_half = array
# This doesn't work as expected - Ruby can't determine split point
# SyntaxError: multiple splat operators not allowed
# Correct approach - single splat per assignment
first, *rest = array
*most, last = array
# Array literal splat vs assignment splat
values = [1, 2, 3]
new_array = [0, *values, 4] # Array construction: [0, 1, 2, 3, 4]
a, *b, c = new_array # Assignment destructuring: a=0, b=[1, 2, 3], c=4
Type coercion with splat operators produces subtle errors when objects don't implement expected conversion methods. Ruby raises TypeError exceptions during assignment if coercion fails.
# Failed coercion examples
class InvalidCollection
# Missing to_a method
end
begin
first, *rest = InvalidCollection.new
rescue TypeError => e
puts "Assignment failed: #{e.message}"
# Assignment failed: can't splat InvalidCollection
end
# Hash coercion gotchas
class FakeHash
def to_hash
"not actually a hash" # Wrong return type
end
end
def process(**options)
puts options
end
begin
fake = FakeHash.new
process(**fake)
rescue TypeError => e
puts "Hash splat failed: #{e.message}"
# Hash splat failed: can't convert String into Hash
end
Memory allocation patterns with splat operators create performance bottlenecks in tight loops. Ruby allocates new array and hash objects for each splat operation, causing garbage collection pressure.
# Performance pitfall - excessive allocations
def inefficient_processing(large_array)
large_array.each do |item|
first, *rest = item # Creates new array object each iteration
process_item(first, rest)
end
end
# Better approach - avoid unnecessary splats
def efficient_processing(large_array)
large_array.each do |item|
first = item.first
rest = item[1..-1] || [] # Reuse slice operation
process_item(first, rest)
end
end
# Hash splat allocation issues
def process_batch(**common_options)
items.each do |item|
# Creates new hash each time
item_options = { **common_options, id: item.id }
process_item(item_options)
end
end
# More efficient approach
def process_batch(**common_options)
items.each do |item|
# Modify existing hash
item_options = common_options.merge(id: item.id)
process_item(item_options)
end
end
Method signature ambiguity occurs when combining positional and keyword splats. Ruby's parameter resolution follows specific precedence rules that can mask intended argument patterns.
# Ambiguous method signatures
def confusing_method(a, b = nil, *args, c:, **kwargs)
puts "a: #{a}, b: #{b}, args: #{args}, c: #{c}, kwargs: #{kwargs}"
end
# Call resolution depends on argument types
confusing_method(1, 2, 3, c: 4)
# a: 1, b: 2, args: [3], c: 4, kwargs: {}
confusing_method(1, c: 4, d: 5)
# a: 1, b: nil, args: [], c: 4, kwargs: {d: 5}
# Clearer method design
def clear_method(required, *optional_positional, required_keyword:, **optional_keywords)
# Explicit parameter roles reduce confusion
end
# Block parameter splat gotchas
[[1, 2], [3, 4], [5, 6]].each do |*pair|
# pair is [[1, 2]], not [1, 2] as might be expected
puts pair.inspect
end
# Correct block parameter usage
[[1, 2], [3, 4], [5, 6]].each do |first, second|
puts "#{first}, #{second}"
end
Reference
Splat Operator Syntax
Operator | Context | Purpose | Example |
---|---|---|---|
*variable |
Assignment | Captures remaining array elements | first, *rest = array |
*args |
Method definition | Accepts variable positional arguments | def method(*args) |
*array |
Method call | Expands array as arguments | method(*array) |
**variable |
Assignment | Captures remaining hash pairs | required:, **options = hash |
**kwargs |
Method definition | Accepts variable keyword arguments | def method(**kwargs) |
**hash |
Method call | Expands hash as keyword arguments | method(**hash) |
Assignment Patterns
Pattern | Result | Notes |
---|---|---|
a, *b = [1,2,3] |
a=1, b=[2,3] |
Basic splat assignment |
*a, b = [1,2,3] |
a=[1,2], b=3 |
Splat captures prefix |
a, *b, c = [1,2,3,4] |
a=1, b=[2,3], c=4 |
Splat captures middle |
*a = [1,2,3] |
a=[1,2,3] |
Splat captures all |
a, *b = [1] |
a=1, b=[] |
Empty splat array |
a, b, *c = [1] |
a=1, b=nil, c=[] |
Missing values are nil |
Method Parameter Types
Parameter Type | Syntax | Description | Position |
---|---|---|---|
Required positional | param |
Must be provided | First |
Optional positional | param = default |
Has default value | After required |
Splat args | *args |
Variable positional arguments | After optional |
Required keyword | param: |
Must be provided as keyword | Any order |
Optional keyword | param: default |
Has default value | Any order |
Keyword splat | **kwargs |
Variable keyword arguments | Last |
Block | &block |
Block parameter | Very last |
Type Conversion Methods
Method | Called On | Purpose | Return Type |
---|---|---|---|
to_a |
Right side of * |
Convert to array | Array |
to_hash |
Right side of ** |
Convert to hash | Hash |
to_proc |
Right side of & |
Convert to proc | Proc |
Common Error Patterns
Error | Cause | Solution |
---|---|---|
SyntaxError: multiple splat |
Multiple * in same assignment |
Use single splat operator |
TypeError: can't splat |
Object lacks to_a method |
Implement to_a or convert explicitly |
TypeError: can't convert into Hash |
Object's to_hash returns non-Hash |
Fix to_hash implementation |
ArgumentError: wrong number |
Splat args don't match method signature | Check method parameter requirements |
Performance Considerations
Operation | Allocation | Recommendation |
---|---|---|
*array in assignment |
New array created | Avoid in tight loops |
**hash in assignment |
New hash created | Use merge for single additions |
*args forwarding |
No allocation | Efficient for delegation |
**kwargs forwarding |
No allocation | Efficient for delegation |
Compatibility Notes
Ruby Version | Feature | Status |
---|---|---|
2.0+ | Basic splat operators | Stable |
2.1+ | Required keyword arguments | Stable |
2.7+ | Argument forwarding ... |
Alternative syntax |
3.0+ | Positional/keyword separation | Stricter rules |