Syntax
Ruby’s syntax is expression-oriented (every construct produces a value), object-oriented (every value is an object — including nil, true, integers, classes, and the result of every expression), and minimally punctuated (parentheses around method arguments are typically optional, semicolons separate statements only when multiple appear on one line). Method calls dispatch dynamically; blocks (Ruby’s signature feature) attach to method calls and admit substantial inline computation. Indentation is purely conventional — Ruby uses end keywords to close blocks, methods, classes, and modules. The combination — expression-orientation, ubiquitous object-message dispatch, blocks, and the conventional readability — is the substance of Ruby’s syntactic identity.
This page covers the surface a working programmer encounters routinely.
A complete program
The classical hello world:
puts "Hello, world!"
A more substantial example:
require "json"
class Person
attr_accessor :name, :age
def initialize(name:, age:)
@name = name
@age = age
end
def greeting
"Hello, #{@name}."
end
end
people = JSON.parse(File.read("people.json"))
people.map { |p| Person.new(name: p["name"], age: p["age"]) }
.sort_by(&:age)
.each { |p| puts p.greeting }
The principal features visible:
require— loads a library.class Person ... end— class definition.attr_accessor :name, :age— generated getters and setters.def initialize(...)— the constructor.- Keyword arguments
name:, age:. @name— instance variable."Hello, #{@name}."— string interpolation.map { |p| ... }— block syntax with curly braces.&:age— the symbol-to-proc shorthand.- Method chaining with
.continuations.
Execution:
ruby hello.rb
Source character set
Ruby source is interpreted as UTF-8 by default. Identifiers may use Unicode letters; ASCII identifiers are conventional.
A magic comment admits declaring an alternate encoding:
# encoding: UTF-8
# frozen_string_literal: true
The frozen_string_literal: true magic comment is conventional in modern Ruby — it admits substantial performance gains by interning string literals.
Identifiers and naming conventions
Ruby’s naming conventions are enforced by the language (not just by style):
| Form | Use | Example |
|---|---|---|
lowercase_snake_case | local variables, method names | user_name, to_s |
CamelCase | classes, modules | Person, Enumerable |
UPPER_SNAKE_CASE | constants | MAX_RETRIES |
@name | instance variables | @user |
@@name | class variables | @@count |
$name | global variables | $stdout |
:name | symbols | :age, :to_s |
name? | predicate methods (boolean return) | empty?, nil? |
name! | ”dangerous” methods (mutate or raise) | sort!, save! |
The ? and ! suffixes are part of the method name — they are not separate operators. The ? is conventional for predicates; the ! for methods that mutate the receiver or have a stricter alternative.
Reserved keywords
The keywords:
__ENCODING__ __LINE__ __FILE__ BEGIN END
alias and begin break case
class def defined? do else
elsif end ensure false for
if in module next nil
not or redo rescue retry
return self super then true
undef unless until when while
yield
Reserved words may not be used as method names without the define_method trick or other metaprogramming.
Comments
Two comment forms:
# A single-line comment, terminated by the end of the line.
=begin
A block comment.
The =begin and =end must be at the start of a line.
=end
The block-comment form is rare in idiomatic Ruby; multi-line # comments are conventional.
For documentation, RDoc parses comments preceding declarations:
# Greets the named user.
#
# @param name [String] the name to greet
# @return [String] the greeting
def greet(name)
"Hello, #{name}"
end
The conventional documentation tooling is YARD — a more substantial RDoc replacement that admits structured annotations.
The expression-oriented language
Ruby is expression-oriented: every construct (including if, case, begin, blocks, method definitions) produces a value:
max = if a > b then a else b end # if as expression
result = case n
when 0 then "zero"
when 1..9 then "small"
else "large"
end # case as expression
x = (1..10).map { |i| i * i } # block expression
The last expression in a block, method, or begin block is the value of that block:
def square(n)
n * n # implicit return
end
result = begin
do_first
do_second
compute_result # this value
end
The return keyword is admitted but conventional only for early returns.
Variables and assignment
Ruby admits five kinds of variables, distinguished by prefix:
x = 5 # local — visible in current scope
@x = 5 # instance — per-object
@@x = 5 # class — shared across instances
$x = 5 # global — visible everywhere
X = 5 # constant — uppercase initial
Local variables are not declared — assignment creates them. The first assignment to a name introduces it in the current scope.
Multiple assignment:
a, b = 1, 2
a, b = b, a # swap
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
*init, last = [1, 2, 3, 4] # init=[1,2,3], last=4
a, b, c = [10, 20, 30] # destructure array
The splat (*) collects remaining elements; conventional for variable-length unpacking.
Method calls
Method calls dispatch via the . operator:
puts "hello" # method call on Kernel
"hello".upcase # method on String
[1, 2, 3].sum # method on Array
5.times { puts "hi" } # method on Integer
obj.method_name(arg1, arg2)
obj.method_name arg1, arg2 # parens optional
obj.method_name # no args (no parens)
Method calls without parentheses are admitted; the conventional discipline includes parentheses for methods with arguments and omits them for argument-less methods.
The . precedes the method name; alternatively, the safe navigation &. returns nil if the receiver is nil:
user.name # raises if user is nil
user&.name # returns nil if user is nil
Blocks
Blocks are anonymous code passed to method calls. Two forms:
# Curly braces (single-expression):
[1, 2, 3].map { |n| n * 2 } # [2, 4, 6]
# do...end (multi-line):
[1, 2, 3].each do |n|
puts n
puts n * 2
end
The conventional discipline:
{ }for single-expression blocks.do...endfor multi-line or method-chained blocks.
Blocks are passed implicitly — they are not part of the argument list. The receiving method calls yield to invoke them or accepts an explicit &block parameter. Treated in Blocks and procs.
Statements and statement separators
Newlines terminate statements; semicolons admit multiple statements on one line:
x = 5
y = 10
z = x + y
# Equivalent:
x = 5; y = 10; z = x + y
Statements may continue across multiple lines via:
- A trailing
\(line continuation). - A trailing operator (
+,,,&&, etc.). - An open paren, bracket, or brace.
- A trailing
.(method chain continuation).
result = compute_a +
compute_b +
compute_c
users.filter { |u| u.active }
.map(&:name)
.sort
The trailing-. form is conventional for method chains.
Modifier conditional and loops
Ruby admits modifier (postfix) forms for if, unless, while, until:
puts "found" if found
return unless valid?
n -= 1 while n > 0
The modifier forms are conventional for short, single-statement conditionals.
Method definition
The def keyword introduces a method:
def add(a, b)
a + b
end
def greet(name = "world") # default argument
puts "Hello, #{name}"
end
def with_keywords(name:, age:) # keyword arguments
puts "#{name} is #{age}"
end
def with_splat(*args) # variadic positional
args.sum
end
def with_block(&block) # capture block
block.call(42)
end
The form: def name(params)\n body\nend. The last expression of the body is the implicit return value. Parentheses around parameters are optional:
def add a, b # admitted but rare
a + b
end
The conventional discipline includes parentheses around parameters.
Treated in Methods.
Classes
The class keyword introduces a class:
class Counter
def initialize(initial = 0)
@count = initial
end
def increment
@count += 1
end
def to_s
"Counter(#{@count})"
end
end
c = Counter.new
c.increment
puts c # "Counter(1)"
Treated in Classes and OOP.
A note on what Ruby admits
Several distinguishing features:
- Everything is an object — including
nil,true, classes, integers. - Open classes — existing classes can be reopened and extended.
- Optional parentheses —
puts "hello"andputs("hello")are equivalent. - Implicit returns — the last expression’s value is returned.
- No type declarations — types are inferred at runtime.
- Blocks — first-class anonymous code attached to method calls.
- Symbols — interned strings used as identifiers.
- Method missing — dynamic method dispatch via
method_missing. - No primitive vs. object distinction —
5is aIntegerobject. - Garbage collection — automatic memory management.
- No type coercion in comparisons —
1 == "1"isfalse. - Multiple inheritance via mixins —
includeadmits combining modules.
The combination — pure object orientation, blocks, dynamic dispatch, the substantial standard library, the metaprogramming facilities — is the substance of Ruby’s identity. The discipline is largely about expressing intent through idiomatic message-passing rather than through structural types or static analysis.