case
(often referred to as switch in other languages) is a form of control expression like if-else.
case
allows for chaining multiple if-else-if statements, can be more readable, and allows for powerful constructs.
A case
is defined by the keyword case
followed by an optional expression.
Then for each case, the keyword when
is used followed by an expression that is compared to the case expression.
The when
keyword should not be indented from the case
keyword.
After the when
keyword is the code that should be executed if the case expression matches the when expression.
Case allows for an optional else
statement which is executed if no other case matches.
The case expression is evaluated and then compared to each when expression.
The expression is compared using the case subsumption operator (===
).
case 1
when 1
 puts "One"
when 2
 puts "Two"
else
 puts "Other"
end
# => One
# This is the same as:
tmp = 1
if 1 === tmp
 puts "One"
elsif 2 === tmp
 puts "Two"
else
 puts "Other"
end
===
)The case subsumption operator (===
) is a bit different from the equality operator (==
).
The operator checks if the right side is a member of the set described by the left side.
This means that it does matter where each operand is placed.
How this works depends on the type of the left side, for example a Range
would check if the right side is in the range or a Object
would check if the right side is an instance of the Object
.
(1..3) == 1 Â # => false
(1..3) === 1 # => true
String == "foo" Â # => false
String === "foo" # => true
Cases allow matching multiple expressions in a single case with each possible value separated by a comma. It will execute the code if any of the expressions match. This can be useful for a single case with multiple possible values.
case 1
when 1, 2
 puts "One or two"
else
 puts "Other"
end
Cases can also check if a value is in a range. This is done by having a range as the when expression.
case var
when 1..3
 puts "One to three"
else
 puts "Other"
end
When there is no need for a case expression, it is possible to omit it. Doing this will make it so that each case expression is evaluated for truthiness. And makes them behave like if-else-if statements.
case
when 1 == 1
 puts "One is equal to one"
when 1 > 2
 puts "One is greater than two"
else
 puts "Other"
end
Crystal allows for single-line case statements.
This can be used when you have a simple single-line statement.
The single line when the statement is written as when <expression> then <statement>.
And when used in the else statement, it is written as else <statement>
.
case var
when 1 then puts "One"
when 2 then puts "Two"
else puts "Other"
end
Case allows for the matching with types.
The comparison will use the is_a?
operator to check the variable type.
For example, this allows one branch for each type in a union type.
case var
when Int32
 puts "Int32"
when String
 puts "String"
else
 puts "Other"
end
Case allows for the use of an implicit object. This allows for example a when expression to be a method call on the case expression. Based on whether the method returns a truthy or falsey value, will the case be matched or not.
case 1
when .odd?
 puts "Odd"
when .even?
 puts "Even"
end
Case allows for exhaustive cases, which means that it is possible to ensure that all possible values are handled.
It is similar to the when statement but with an in
keyword instead of when
.
When a case is exhaustive, it does not support any when
or else
statements.
Exhaustive cases can be used on Bools
, Union
types, Enum
, and Tuples
.
The two latter ones will be covered in later concepts.
Exhaustive cases mean that all possibilities must be handled otherwise a compile error will be raised.
Handling Union
types as an exhaustive case is similar to a non-exhaustive case.
var : (Int32 | String | Bool)
case var
in Int32
 puts "Int32"
in String
 puts "String"
in Bool
 puts "Bool"
end
Bools
can be handled as an exhaustive case by using the true
and false
keywords.
var : Bool
case var
in true
 puts "True"
in false
 puts "False"
end