Operators
Most Ruby operators are methods — 1 + 2 is sugar for 1.+(2). The principal exceptions (operators that are not methods): &&, ||, !, and, or, not, =, .., ..., ?:, defined?. The mechanism admits operator overloading — any class may define +, -, *, <=>, ==, etc. The combination — operators-as-methods, the spaceship <=> for ordering, the safe navigation &., the splat (*) and double splat (**) for unpacking — is the substance of Ruby’s expression surface.
Arithmetic
a + b # addition
a - b # subtraction
a * b # multiplication
a / b # division (integer for Integer/Integer)
a % b # modulo
a ** b # exponentiation
-a # unary negation
+a # unary plus
Integer division truncates toward negative infinity:
puts 7 / 2 # 3
puts -7 / 2 # -4 (not -3)
puts 7 % 2 # 1
puts -7 % 2 # 1 (not -1)
The behaviour differs from C-family languages where division truncates toward zero. The mathematical identity (a / b) * b + (a % b) == a always holds.
For exact rationals:
Rational(1, 3) + Rational(2, 3) # (1/1)
The arithmetic operators are methods; 1 + 2 is 1.+(2). Class authors may overload them:
class Vector
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def +(other)
Vector.new(@x + other.x, @y + other.y)
end
end
a = Vector.new(1, 2)
b = Vector.new(3, 4)
(a + b).inspect # #<Vector ...>
Comparison
a == b # equality (compares values)
a != b
a < b
a > b
a <= b
a >= b
a <=> b # spaceship: -1, 0, 1, or nil
The <=> returns -1 if less, 0 if equal, 1 if greater, nil if incomparable:
1 <=> 2 # -1
"a" <=> "b" # -1
1 <=> "a" # nil
The <=> is the conventional foundation for Comparable; defining it admits <, <=, >, >=, between?, etc. Treated in Modules and mixins.
Three equality methods
Ruby has three equality-related methods:
a == b # value equality (the conventional default)
a.eql?(b) # strict equality (no type coercion)
a.equal?(b) # identity (same object)
The principal differences:
1 == 1.0 # true (value equality)
1.eql?(1.0) # false (different types)
1.equal?(1.0) # false
a = "hello"
b = "hello"
a == b # true (same value)
a.equal?(b) # false (different objects)
a.equal?(a) # true (same object)
The conventional discipline:
==— value equality; the default.eql?— used byHashfor key comparison.equal?— never overridden; admits identity check.
=== (case equality)
The === operator is special — used by case/when:
case x
when Integer then "integer"
when /^[0-9]+$/ then "digits"
when 1..10 then "small"
when ->(n) { n > 100 } then "large"
end
The semantics depend on the receiver type:
Class === obj—obj.is_a?(Class).Regex === str— regex match.Range === val—valis in range.Proc === val—proc.call(val)(since 1.9).- Otherwise: same as
==.
The mechanism admits substantial polymorphism in case expressions; treated in Conditionals.
Logical operators
Ruby has two sets of logical operators:
# Symbolic (high precedence):
a && b # short-circuit AND
a || b # short-circuit OR
!a # NOT
# Word-form (low precedence):
a and b
a or b
not a
The two sets differ in precedence:
result = a || b # parses as: result = (a || b)
result = a or b # parses as: (result = a) or b
The conventional discipline:
&&,||,!for boolean expressions.and,or,notfor control flow (e.g.,do_thing or fail).
Both forms return the operand value, not just true/false:
nil || "default" # "default"
0 || "default" # 0 (0 is truthy in Ruby)
false || "default" # "default"
"first" && "second" # "second" (returns last truthy)
nil && "ignored" # nil
For “default value when nil”:
config.port || 8080 # 8080 if port is nil OR false
config.port.nil? ? 8080 : config.port # 8080 only if nil
Assignment operators
a = 5 # simple assignment
a += 1 # a = a + 1
a -= 1
a *= 2
a /= 2
a %= 3
a **= 2
a &= true
a |= false
a ^= b
a <<= 1
a >>= 1
a &&= b # a = a && b
a ||= default # a = a || default — set if nil/false
The ||= is the conventional Ruby memoization idiom:
def expensive_data
@data ||= compute_data
end
The form admits “compute once, cache the result”.
The mass assignment:
a, b = 1, 2
a, b, c = [10, 20, 30]
a, *rest = [1, 2, 3, 4] # a=1, rest=[2,3,4]
a, b = b, a # swap
Bitwise operators
a & b # AND
a | b # OR
a ^ b # XOR
~a # NOT
a << b # left shift
a >> b # right shift
The bitwise operators work on integers; for Array, &, |, ^ are set operations:
[1, 2, 3] & [2, 3, 4] # [2, 3] (intersection)
[1, 2, 3] | [3, 4, 5] # [1, 2, 3, 4, 5] (union)
[1, 2, 3] - [2] # [1, 3] (difference)
For String, * repeats; + concatenates; << appends:
"ab" * 3 # "ababab"
"a" + "b" # "ab"
s = "a"; s << "b" # mutates: "ab"
For Hash, merge is the conventional combiner:
{ a: 1 }.merge({ b: 2 }) # { a: 1, b: 2 }
Range operators
1..10 # inclusive
1...10 # exclusive (no 10)
Ranges are first-class objects; treated in Types.
Splat operators
The * (splat) and ** (double splat) admit unpacking and collecting:
# Splat in argument list (collect):
def sum(*args)
args.sum
end
sum(1, 2, 3) # 6
# Splat in call (spread):
nums = [1, 2, 3]
sum(*nums) # 6
# Double splat (keyword):
def configure(**opts)
opts.each { |k, v| puts "#{k}=#{v}" }
end
configure(host: "localhost", port: 8080)
opts = { host: "localhost", port: 8080 }
configure(**opts)
Treated in Methods.
Safe navigation &.
Since Ruby 2.3, the &. admits “call this method if the receiver is not nil”:
user&.name # nil if user is nil
user&.address&.city # nil-safe chain
result = list.first&.upcase # nil if list is empty
The conventional alternative pre-2.3 was try:
user.try(:name) # ActiveSupport (Rails)
The &. is the contemporary form.
Conditional and ternary
result = condition ? value_if_true : value_if_false
# Modifier forms:
puts "hello" if condition
puts "hello" unless condition
The ternary is admitted but conventional only for short value-returning conditionals; for substantial logic, if/else is conventional.
Range === and case
case n
when 1..10 then "small"
when 11..100 then "medium"
when 101.. then "large" # endless range
end
The mechanism admits range-based dispatch; treated in Pattern matching.
Defined?
The defined? operator returns a string describing the kind of expression — or nil if the expression is not defined:
defined?(x) # nil if x undeclared, "local-variable" otherwise
defined?(@x) # "instance-variable" or nil
defined?(puts) # "method"
defined?(String) # "constant"
defined?(self) # "self"
The form is rare in idiomatic code; nil? and respond_to? are conventional alternatives.
Operator precedence
Ruby’s operator precedence (high to low):
| Operators |
|---|
!, ~, +@, -@ |
** |
*, /, % |
+, - |
<<, >> |
& |
|, ^ |
<, <=, >, >= |
==, !=, ===, =~, !~, <=> |
&& |
|| |
.., ... |
?: |
=, +=, etc. |
not |
and, or |
The principal pitfall is not, and, or having very low precedence — even lower than =. The conventional defence:
result = a or b # PROBABLY NOT what you want
# parses as: (result = a) or b
result = a || b # what you want
Common patterns
||= for memoization
def cache_key
@cache_key ||= compute_cache_key
end
Spaceship for sort
people.sort { |a, b| a.age <=> b.age }
people.sort_by(&:age) # cleaner
Splat for forwarding
def wrapper(*args, **opts, &block)
log_call(*args, **opts)
underlying_method(*args, **opts, &block)
end
Set operations on arrays
required = [:name, :email, :password]
provided = params.keys
missing = required - provided
extra = provided - required
intersection = required & provided
Safe navigation chain
city = user&.profile&.address&.city || "unknown"
Range iteration
(1..100).step(5) { |n| puts n }
(1..10).each_slice(3) { |slice| puts slice.inspect }
Multiple assignment
def divmod(a, b)
[a / b, a % b]
end
quotient, remainder = divmod(17, 5)
Symbol-to-proc
[1, 2, 3].map(&:to_s) # ["1", "2", "3"]
people.sort_by(&:age)
strings.select(&:empty?)
The &:method_name admits substantial conciseness for “call this method on each item”.
A note on the conventional discipline
The contemporary Ruby operator advice:
- Use
==for value equality; rarely override. - Use
<=>withComparablefor ordering. - Use
||=for memoization. - Use
&&and||(symbolic forms) overand/or. - Use
&.for nil-safe access. - Use
*and**for splat unpacking. - Use
Comparablemixin to derive comparisons from<=>. - Use parentheses freely — Ruby’s precedence has subtle pitfalls.
The combination — operators-as-methods, three equality methods (==, eql?, equal?), the spaceship <=>, the splat operators, the safe navigation &., the symbolic-vs-word logical operators — is the substance of Ruby’s expression surface. The discipline produces concise, expressive code with substantial flexibility.