Polyglot
Languages Ruby types
Ruby § types

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: false and nil.
  • 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? over is_a? for capability checks.
  • Use symbols for hash keys and method-name references.
  • Use Integer(), Float() for strict conversion; to_i, to_f for lenient.
  • Use Range for iteration spans.
  • Use Comparable and Enumerable mixins — major time-savers.
  • Trust the standard library — many distinctive types (Set, Pathname, URI) are built in.
  • Reach for BigDecimal or Rational for 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.