Types
Ruby is dynamically typed: every value carries its type at runtime; variables do not have declared types. Every value is an object — an instance of some class — including nil, true, false, integers, floats, strings, arrays, hashes, classes themselves, and methods. The language admits duck typing — code does not check types; it calls methods and trusts that the receiver responds. The conventional Ruby type discipline is: if it walks like a duck and quacks like a duck, it is a duck. The principal built-in classes — Integer, Float, String, Symbol, Array, Hash, Range, Proc, NilClass, TrueClass, FalseClass — cover the routine value surface.
Numbers
Ruby has two principal numeric types:
42 # Integer
3.14 # Float
1_000_000 # underscore separator
0xff # hex
0o755 # octal
0b1010 # binary
1e6 # 1,000,000.0 (Float)
42.class # Integer
3.14.class # Float
Integer admits arbitrary precision — there is no fixed-width integer in Ruby:
(2 ** 100).class # Integer
(2 ** 100) # 1267650600228229401496703205376
Pre-Ruby-2.4, Integer was split between Fixnum (small) and Bignum (large); the unification produces a single seamless integer type.
Float is IEEE 754 double-precision:
(0.1 + 0.2) # 0.30000000000000004 (the conventional float pitfall)
For exact decimal arithmetic, BigDecimal:
require "bigdecimal"
BigDecimal("0.1") + BigDecimal("0.2") # 0.3
For exact rationals:
Rational(1, 3) # (1/3)
Rational(1, 3) + Rational(2, 3) # (1/1)
For complex numbers:
Complex(2, 3) # (2+3i)
Strings
The String class admits substantial mutability and the conventional methods:
s = "hello"
s.length # 5
s.upcase # "HELLO"
s + " world" # "hello world"
s * 3 # "hellohellohello"
# Mutation:
s << " world" # appends in place
s.upcase! # mutates in place
# Single vs double quotes:
'no\ninterpolation' # literal backslash-n
"interpolation \n with #{name}" # newline and interpolation
Treated in Strings.
Symbols
A symbol is an interned, immutable identifier:
:name # symbol
:hello_world
:"with spaces"
:name.class # Symbol
:name == :name # true (same object)
The same symbol literal always refers to the same object; symbols are conventional for hash keys, method names in metaprogramming, and tagged enumerations.
The principal advantage over strings: substantial efficiency for repeated identifiers. The principal disadvantage: symbols are not garbage-collected before Ruby 2.2 (and only some symbols are after); user-supplied input should not be converted to symbols indiscriminately.
:name.to_s # "name"
"name".to_sym # :name
Booleans and nil
The truthy/falsy rule:
- Falsy:
falseandnil. - Truthy: everything else (including
0,"",[],0.0).
true.class # TrueClass
false.class # FalseClass
nil.class # NilClass
if 0 then puts "yes" end # prints "yes" — 0 is truthy
if "" then puts "yes" end # prints "yes" — "" is truthy
if nil then puts "yes" end # nothing
The strict falsy-only rule eliminates the C-family pitfall where 0 is falsy.
nil admits substantial methods:
nil.to_s # ""
nil.to_a # []
nil.to_i # 0
nil.inspect # "nil"
nil.nil? # true
Arrays
The Array class admits ordered, indexed, mutable sequences of any objects:
arr = [1, 2, 3, "hello", :symbol, nil] # mixed types
arr.length # 6
arr[0] # 1
arr[-1] # nil (last)
arr[1, 2] # [2, 3] — index, length
arr[1..3] # [2, 3, "hello"] — range
arr.push(99) # append
arr << 100 # append (operator form)
arr.pop # remove last
arr.unshift(0) # prepend
arr.first # 1
arr.last # 100
arr.sort # raises on mixed types
arr.reverse
arr.uniq
Treated in Data structures.
Hashes
The Hash class admits unordered key-value collections:
h = { "name" => "Alice", "age" => 30 }
h2 = { name: "Bob", age: 25 } # symbol-key shorthand
h["name"] # "Alice"
h2[:name] # "Bob"
h["email"] = "alice@example.com" # add
h.delete("age") # remove
h.size # 2
h.keys # ["name", "email"]
h.values
The symbol-key shorthand { name: "Bob" } is conventional in modern Ruby; the older { "name" => "Bob" } is reserved for non-symbol keys.
Treated in Data structures.
Ranges
A Range represents a span of values:
(1..10) # inclusive
(1...10) # exclusive (no 10)
("a".."z") # character range
(1..Float::INFINITY) # endless integer range
(...10) # beginless (Ruby 2.7+)
(10..) # endless
r = (1..5)
r.to_a # [1, 2, 3, 4, 5]
r.include?(3) # true
r.cover?(2.5) # true (within bounds)
r.sum # 15
Ranges are conventional for iteration ((1..10).each), case matching, and slicing.
Procs and lambdas
Anonymous callables — first-class objects:
square = ->(n) { n * n } # lambda (-> syntax)
square.call(5) # 25
square.(5) # 25 (alternative call syntax)
square[5] # 25 (alternative call syntax)
double = proc { |n| n * 2 } # proc
double.call(5) # 10
both = lambda { |n| n + 1 } # lambda (lambda keyword)
Treated in Blocks and procs.
Classes and modules
Classes and modules are themselves objects — instances of Class and Module:
String.class # Class
Class.class # Class
Class.superclass # Module
Comparable.class # Module
This admits substantial metaprogramming — classes can be modified at runtime, methods can be defined dynamically. Treated in Metaprogramming.
Type checking
Ruby is dynamic; type checking is conventional via duck-typing predicates:
# Class-based:
x.class # the class
x.is_a?(String) # class hierarchy check
x.kind_of?(String) # alias for is_a?
x.instance_of?(String) # exact class only
# Capability-based (duck typing):
x.respond_to?(:to_s) # has the method?
The conventional discipline favours respond_to? over is_a? — concerns the capability, not the structural class. The pattern admits substantial flexibility.
Type coercion
Ruby admits explicit conversion methods:
"42".to_i # 42
"3.14".to_f # 3.14
42.to_s # "42"
:name.to_s # "name"
"name".to_sym # :name
[1, 2].to_h # raises (need pairs)
[[:a, 1], [:b, 2]].to_h # { a: 1, b: 2 }
Implicit coercion is limited in Ruby; arithmetic admits some implicit coercion (e.g., 1 + 1.0 produces 2.0):
1 + 1.0 # 2.0 (Integer + Float)
"1" + 1 # raises TypeError
"1" + 1.to_s # "11"
For substantial coercion, the Integer(), Float(), String() “kernel methods”:
Integer("42") # 42
Integer("abc") # raises ArgumentError
Float("3.14")
String(123) # "123"
These admit strict conversion (raises on invalid input), unlike to_i which silently returns 0.
Comparable
The Comparable mixin admits ordering:
class Distance
include Comparable
attr_accessor :meters
def initialize(meters)
@meters = meters
end
def <=>(other)
@meters <=> other.meters # the spaceship
end
end
a = Distance.new(100)
b = Distance.new(200)
a < b # true
a >= b # false
a.between?(Distance.new(50), Distance.new(150)) # true
[a, b].min # the lesser
The <=> (spaceship) operator returns -1, 0, or 1; Comparable derives <, <=, >, >=, ==, between?, clamp. Treated in Modules and mixins.
Enumerable
The Enumerable mixin admits substantial iteration methods:
class TodoList
include Enumerable
def initialize
@items = []
end
def each(&block)
@items.each(&block)
end
end
list = TodoList.new
# Now list has map, select, reject, reduce, sort, etc.
Treated in Enumerable.
A note on the conventional discipline
The contemporary Ruby type advice:
- Use duck typing — call methods, trust the receiver responds.
- Use
respond_to?overis_a?for capability checks. - Use symbols for hash keys and method-name references.
- Use
Integer(),Float()for strict conversion;to_i,to_ffor lenient. - Use
Rangefor iteration spans. - Use
ComparableandEnumerablemixins — major time-savers. - Trust the standard library — many distinctive types (
Set,Pathname,URI) are built in. - Reach for
BigDecimalorRationalfor exact arithmetic.
The combination — universal object-orientation, duck typing, the rich literal syntax, the substantial standard classes, the conventional Comparable and Enumerable mixins — is the substance of Ruby’s type model. The discipline trades some compile-time discipline for substantial runtime flexibility.