Control Expressions

Ruby has a variety of ways to control execution. All the expressions described here return a value.

For the tests in these control expressions,nil andfalse are false-values andtrue and any other object are true-values. In this document “true” will mean “true-value” and “false” will mean “false-value”.

if Expression

The simplestif expression has two parts, a “test” expression and a “then” expression. If the “test” expression evaluates to a true then the “then” expression is evaluated.

Here is a simple if statement:

iftruethenputs"the test resulted in a true-value"end

This will print “the test resulted in a true-value”.

Thethen is optional:

iftrueputs"the test resulted in a true-value"end

This document will omit the optionalthen for all expressions as that is the most common usage ofif.

You may also add anelse expression. If the test does not evaluate to true theelse expression will be executed:

iffalseputs"the test resulted in a true-value"elseputs"the test resulted in a false-value"end

This will print “the test resulted in a false-value”.

You may add an arbitrary number of extra tests to an if expression usingelsif. Anelsif executes when all tests above theelsif are false.

a =1ifa==0puts"a is zero"elsifa==1puts"a is one"elseputs"a is some other value"end

This will print “a is one” as1 is not equal to0. Sinceelse is only executed when there are no matching conditions.

Once a condition matches, either theif condition or anyelsif condition, theif expression is complete and no further tests will be performed.

Like anif, anelsif condition may be followed by athen.

In this example only “a is one” is printed:

a =1ifa==0puts"a is zero"elsifa==1puts"a is one"elsifa>=1puts"a is greater than or equal to one"elseputs"a is some other value"end

The tests forif andelsif may have side-effects. The most common use of side-effect is to cache a value into a local variable:

ifa =object.some_value# do something to aend

The result value of anif expression is the last value executed in the expression.

Ternary if

You may also write a if-then-else expression using? and:. This ternary if:

input_type =gets=~/hello/i?"greeting":"other"

Is the same as thisif expression:

input_type =ifgets=~/hello/i"greeting"else"other"end

While the ternary if is much shorter to write than the more verbose form, for readability it is recommended that the ternary if is only used for simple conditionals. Also, avoid using multiple ternary conditions in the same expression as this can be confusing.

unless Expression

Theunless expression is the opposite of theif expression. If the value is false, the “then” expression is executed:

unlesstrueputs"the value is a false-value"end

This prints nothing as true is not a false-value.

You may use an optionalthen withunless just likeif.

Note that the aboveunless expression is the same as:

ifnottrueputs"the value is a false-value"end

Like anif expression you may use anelse condition withunless:

unlesstrueputs"the value is false"elseputs"the value is true"end

This prints “the value is true” from theelse condition.

You may not useelsif with anunless expression.

The result value of anunless expression is the last value executed in the expression.

Modifierif andunless

if andunless can also be used to modify an expression. When used as a modifier the left-hand side is the “then” statement and the right-hand side is the “test” expression:

a =0a+=1ifa.zero?pa

This will print 1.

a =0a+=1unlessa.zero?pa

This will print 0.

While the modifier and standard versions have both a “test” expression and a “then” statement, they are not exact transformations of each other due to parse order. Here is an example that shows the difference:

paifa =0.zero?

This raises theNameError “undefined local variable or method ‘a’”.

When ruby parses this expression it first encountersa as a method call in the “then” expression, then later it sees the assignment toa in the “test” expression and marksa as a local variable.

When running this line it first executes the “test” expression,a = 0.zero?.

Since the test is true it executes the “then” expression,p a. Since thea in the body was recorded as a method which does not exist theNameError is raised.

The same is true forunless.

case Expression

Thecase expression can be used in two ways.

The most common way is to compare an object against multiple patterns. The patterns are matched using the=== method which is aliased to== onObject. Other classes must override it to give meaningful behavior. SeeModule#=== andRegexp#=== for examples.

Here is an example of usingcase to compare aString against a pattern:

case"12345"when/^1/puts"the string starts with one"elseputs"I don't know what the string starts with"end

Here the string"12345" is compared with/^1/ by calling/^1/ === "12345" which returnstrue. Like theif expression, the firstwhen that matches is executed and all other matches are ignored.

If no matches are found, theelse is executed.

Theelse andthen are optional, thiscase expression gives the same result as the one above:

case"12345"when/^1/puts"the string starts with one"end

You may place multiple conditions on the samewhen:

case"2"when/^1/,"2"puts"the string starts with one or is '2'"end

Ruby will try each condition in turn, so first/^1/ === "2" returnsfalse, then"2" === "2" returnstrue, so “the string starts with one or is ‘2’” is printed.

You may usethen after thewhen condition. This is most frequently used to place the body of thewhen on a single line.

caseawhen1,2thenputs"a is one or two"when3thenputs"a is three"elseputs"I don't know what a is"end

The other way to use acase expression is like an if-elsif expression:

a =2casewhena==1,a==2puts"a is one or two"whena==3puts"a is three"elseputs"I don't know what a is"end

Again, thethen andelse are optional.

The result value of acase expression is the last value executed in the expression.

Since Ruby 2.7,case expressions also provide a more powerful pattern matching feature via thein keyword:

case {a:1,b:2,c:3}ina:Integer=>m"matched: #{m}"else"not matched"end# => "matched: 1"

The pattern matching syntax is described onits own page.

while Loop

Thewhile loop executes while a condition is true:

a =0whilea<10dopaa+=1endpa

Prints the numbers 0 through 10. The conditiona < 10 is checked before the loop is entered, then the body executes, then the condition is checked again. When the condition results in false the loop is terminated.

Thedo keyword is optional. The following loop is equivalent to the loop above:

whilea<10paa+=1end

The result of awhile loop isnil unlessbreak is used to supply a value.

until Loop

Theuntil loop executes while a condition is false:

a =0untila>10dopaa+=1endpa

This prints the numbers 0 through 11. Like a while loop the conditiona > 10 is checked when entering the loop and each time the loop body executes. If the condition is false the loop will continue to execute.

Like awhile loop, thedo is optional.

Like awhile loop, the result of anuntil loop is nil unlessbreak is used.

for Loop

Thefor loop consists offor followed by a variable to contain the iteration argument followed byin and the value to iterate over using each. Thedo is optional:

forvaluein [1,2,3]doputsvalueend

Prints 1, 2 and 3.

Likewhile anduntil, thedo is optional.

Thefor loop is similar to using each, but does not create a new variable scope.

The result value of afor loop is the value iterated over unlessbreak is used.

Thefor loop is rarely used in modern ruby programs.

Modifierwhile anduntil

Likeif andunless,while anduntil can be used as modifiers:

a =0a+=1whilea<10pa# prints 10

until used as a modifier:

a =0a+=1untila>10pa# prints 11

You can usebegin andend to create awhile loop that runs the body once before the condition:

a =0begina+=1endwhilea<10pa# prints 10

If you don’t userescue orensure, Ruby optimizes away any exception handling overhead.

break Statement

Usebreak to leave a block early. This will stop iterating over the items invalues if one of them is even:

values.eachdo|value|breakifvalue.even?# ...end

You can also terminate from awhile loop usingbreak:

a =0whiletruedopaa+=1breakifa<10endpa

This prints the numbers 0 and 1.

break accepts a value that supplies the result of the expression it is “breaking” out of:

result = [1,2,3].eachdo|value|breakvalue*2ifvalue.even?endpresult# prints 4

next Statement

Usenext to skip the rest of the current iteration:

result = [1,2,3].mapdo|value|nextifvalue.even?value*2endpresult# prints [2, nil, 6]

next accepts an argument that can be used as the result of the current block iteration:

result = [1,2,3].mapdo|value|nextvalueifvalue.even?value*2endpresult# prints [2, 2, 6]

redo Statement

Useredo to redo the current iteration:

result = []whileresult.length<10doresult<<result.lengthredoifresult.last.even?result<<result.length+1endpresult

This prints [0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11]

In Ruby 1.8, you could also useretry where you usedredo. This is no longer true, now you will receive aSyntaxError when you useretry outside of arescue block. SeeExceptions for proper usage ofretry.

Modifier Statements

Ruby’s grammar differentiates between statements and expressions. All expressions are statements (an expression is a type of statement), but not all statements are expressions. Some parts of the grammar accept expressions and not other types of statements, which causes code that looks similar to be parsed differently.

For example, when not used as a modifier,if,else,while,until, andbegin are expressions (and also statements). However, when used as a modifier,if,else,while,until andrescue are statements but not expressions.

iftrue;1end# expression (and therefore statement)1iftrue# statement (not expression)

Statements that are not expressions cannot be used in contexts where an expression is expected, such as method arguments.

puts( 1 if true )      #=> SyntaxError

You can wrap a statement in parentheses to create an expression.

puts((1iftrue))#=> 1

If you put a space between the method name and opening parenthesis, you do not need two sets of parentheses.

puts (1iftrue)#=> 1, because of optional parentheses for method

This is because this is parsed similar to a method call without parentheses. It is equivalent to the following code, without the creation of a local variable:

x = (1iftrue)px

In a modifier statement, the left-hand side must be a statement and the right-hand side must be an expression.

So ina if b rescue c, becauseb rescue c is a statement that is not an expression, and therefore is not allowed as the right-hand side of theif modifier statement, the code is necessarily parsed as(a if b) rescue c.

This interacts with operator precedence in such a way that:

stmtifv =exprrescuexstmtifv =exprunlessx

are parsed as:

stmtifv = (exprrescuex)(stmtifv =expr)unlessx

This is because modifierrescue has higher precedence than=, and modifierif has lower precedence than=.

Flip-Flop

The flip-flop is a slightly special conditional expression. One of its typical uses is processing text from ruby one-line programs used withruby -n orruby -p.

The form of the flip-flop is an expression that indicates when the flip-flop turns on,.. (or...), then an expression that indicates when the flip-flop will turn off. While the flip-flop is on it will continue to evaluate totrue, andfalse when off.

Here is an example:

selected = []0.upto10do|value|selected<<valueifvalue==2..value==8endpselected# prints [2, 3, 4, 5, 6, 7, 8]

In the above example, the ‘on’ condition isn==2. The flip-flop is initially ‘off’ (false) for 0 and 1, but becomes ‘on’ (true) for 2 and remains ‘on’ through 8. After 8 it turns off and remains ‘off’ for 9 and 10.

The flip-flop must be used inside a conditional such as!,? :,not,if,while,unless,until etc. including the modifier forms.

When you use an inclusive range (..), the ‘off’ condition is evaluated when the ‘on’ condition changes:

selected = []0.upto5do|value|selected<<valueifvalue==2..value==2endpselected# prints [2]

Here, both sides of the flip-flop are evaluated so the flip-flop turns on and off only whenvalue equals 2. Since the flip-flop turned on in the iteration it returns true.

When you use an exclusive range (...), the ‘off’ condition is evaluated on the following iteration:

selected = []0.upto5do|value|selected<<valueifvalue==2...value==2endpselected# prints [2, 3, 4, 5]

Here, the flip-flop turns on whenvalue equals 2, but doesn’t turn off on the same iteration. The ‘off’ condition isn’t evaluated until the following iteration andvalue will never be two again.

throw/catch

throw andcatch are used to implement non-local control flow in Ruby. They operate similarly to exceptions, allowing control to pass directly from the place wherethrow is called to the place where the matchingcatch is called. The main difference betweenthrow/catch and the use of exceptions is thatthrow/catch are designed for expected non-local control flow, while exceptions are designed for exceptional control flow situations, such as handling unexpected errors.

When usingthrow, you provide 1-2 arguments. The first argument is the value for the matchingcatch. The second argument is optional (defaults tonil), and will be the value thatcatch returns if there is a matchingthrow inside thecatch block. If no matchingthrow method is called inside acatch block, thecatch method returns the return value of the block passed to it.

defa(n)throw:d,:aifn==0b(n)enddefb(n)throw:d,:bifn==1c(n)enddefc(n)throw:difn==2end4.times.mapdo|i|catch(:d)doa(i):defaultendend# => [:a, :b, nil, :default]

If the first argument you pass tothrow is not handled by a matchingcatch, anUncaughtThrowError exception will be raised. This is becausethrow/catch should only be used for expected control flow changes, so using a value that is not already expected is an error.

throw/catch are implemented asKernel methods (Kernel#throw andKernel#catch), not as keywords. So they are not usable directly if you are in aBasicObject context. You can useKernel.throw andKernel.catch in this case:

BasicObject.new.instance_execdodefabenddefbcenddefc::Kernel.throw:d,:eendresult =::Kernel.catch(:d)doaendresult# => :eend