Crystal supports a number of operators, with one, two or three operands.
Operator expressions are actually parsed as method calls. For example a + b
is semantically equivalent to a.+(b)
, a call to method +
on a
with argument b
.
There are however some special rules regarding operator syntax:
.
) usually put between receiver and method name (i.e. the operator) can be omitted.1 * 2 + 3 * 4
is parsed as (1 * 2) + (2 * 3)
to honour regular math rules.Operators are implemented like any regular method, and the standard library offers many implementations, for example for math expressions.
Most operators can be implemented as regular methods.
One can assign any meaning to the operators, but it is advisable to stay within similar semantics to the generic operator meaning to avoid cryptic code that is confusing and behaves unexpectedly.
A few operators are defined directly by the compiler and cannot be redefined in user code. Examples for this are the inversion operator !
, the assignment operator =
, combined assignment operators such as =
and range operators. Whether a method can be redefined is indicated by the colum Overloadable in the below operator tables.
Unary operators are written in prefix notation and have only a single operand. Thus, a method implementation receives no arguments and only operates on self
.
The following example demonstrates the Vector2
type as a twodimensional vector with a unary operator method 
for vector inversion.
struct Vector2 getter x, y def initialize(@x : Int32, @y : Int32) end # Unary operator. Returns the inverted vector to `self`. def  : self Vector2.new(x, y) end end v1 = Vector2.new(1, 2) v1 # => Vector2(@x=1, @y=2)
Binary operators have two operands. Thus, a method implementation receives exactly one argument representing the second operand. The first operand is the receiver self
.
The following example demonstrates the Vector2
type as a twodimensional vector with a binary operator method +
for vector addition.
struct Vector2 getter x, y def initialize(@x : Int32, @y : Int32) end # Binary operator. Returns *other* added to `self`. def +(other : self) : self Vector2.new(x + other.x, y + other.y) end end v1 = Vector2.new(1, 2) v2 = Vector2.new(3, 4) v1 + v2 # => Vector2(@x=4, @y=6)
Per convention, the return type of a binary operator should be the type of the first operand (the receiver), so that typeof(a <op> b) == typeof(a)
. Otherwise the assignment operator (a <op>= b
) would unintentionally change the type of a
. There can be reasonable exceptions though. For example in the standard library the float division operator /
on integer types always returns Float64
, because the quotient must not be limited to the value range of integers.
The conditional operator (? :
) is the only ternary operator. It not parsed as a method, and its meaning cannot be changed. The compiler transforms it to an if
expression.
This list is sorted by precedence, so upper entries bind stronger than lower ones.
Category  Operators 

Index accessors 
[] , []?

Unary 
+ , &+ ,  , & , ! , ~ , * , **

Exponential 
** , &**

Multiplicative 
* , &* , / , // , %

Additive 
+ , &+ ,  , &

Shift 
<< , >>

Binary AND  & 
Binary OR/XOR 
 ,^

Equality 
== , != , =~ , !~ , ===

Comparison 
< , <= , > , >= , <=>

Logical AND  && 
Logical OR   
Range 
.. , ...

Conditional  ?: 
Assignment 
= , []= , += , &+= , = , &= , *= , &*= , /= , //= , %= , = , &= ,^= ,**= ,<<= ,>>= , = , &&=

Operator  Description  Example  Overloadable 

+  positive  +1  yes 
&+  wrapping positive  &+1  yes 
  negative  1  yes 
&  wrapping negative  &1  yes 
Operator  Description  Example  Overloadable 

**  exponentiation  1 ** 2  yes 
&**  wrapping exponentiation  1 &** 2  yes 
*  multiplication  1 * 2  yes 
&*  wrapping multiplication  1 &* 2  yes 
/  division  1 / 2  yes 
//  floor division  1 // 2  yes 
%  modulus  1 % 2  yes 
Operator  Description  Example  Overloadable 

+  addition  1 + 2  yes 
&+  wrapping addition  1 &+ 2  yes 
  subtraction  1  2  yes 
&  wrapping subtraction  1 & 2  yes 
Operator  Description  Example  Overloadable 

!  inversion  !true  no 
~  binary complement  ~1  yes 
Operator  Description  Example  Overloadable 

<<  shift left, append 
1 << 2 , STDOUT << "foo"
 yes 
>>  shift right  1 >> 2  yes 
Operator  Description  Example  Overloadable 

&  binary AND  1 & 2  yes 
  binary OR  1  2  yes 
^  binary XOR  1 ^ 2  yes 
Three base operators test equality:
==
: Checks whether the values of the operands are equal=~
: Checks whether the value of the first operand matches the value of the second operand with pattern matching.===
: Checks whether the left hand operand matches the right hand operand in case equality. This operator is applied in case ... when
conditions.The first two operators also have inversion operators (!=
and !~
) whose semantical intention is just the inverse of the base operator: a != b
is supposed to be equivalent to !(a == b)
and a !~ b
to !(a =~ b)
. Nevertheless, these inversions can be defined with a custom implementation. This can be useful for example to improve performance (nonequality can often be proven faster than equality).
Operator  Description  Example  Overloadable 

==  equals  1 == 2  yes 
!=  not equals  1 != 2  yes 
=~  pattern match  "foo" =~ /fo/  yes 
!~  no pattern match  "foo" !~ /fo/  yes 
===  case equality  /foo/ === "foo"  yes 
Operator  Description  Example  Overloadable 

<  less  1 < 2  yes 
<=  less or equal  1 <= 2  yes 
>  greater  1 > 2  yes 
>=  greater or equal  1 >= 2  yes 
<=>  comparison  1 <=> 2  yes 
Operator  Description  Example  Overloadable 

&&  logical AND  true && false  no 
  logical OR  true  false  no 
The range operators are used in Range literals.
Operator  Description  Example  Overloadable 

..  range  1..10  no 
...  exclusive range  1...10  no 
Splat operators can only be used for destructing tuples in method arguments. See Splats and Tuples for details.
Operator  Description  Example  Overloadable 

*  splat  *foo  no 
**  double splat  **foo  no 
The conditional operator (? :
) is internally rewritten to an if
expression by the compiler.
Operator  Description  Example  Overloadable 

? :  conditional  a == b ? c : d  no 
The assignment operator =
assigns the value of the second operand to the first operand. The first operand is either a variable (in this case the operator can't be redefined) or a call (in this case the operator can be redefined). See assignment for details.
Operator  Description  Example  Overloadable 

=  variable assignment  a = 1  no 
=  call assignment  a.b = 1  yes 
[]=  index assignment  a[0] = 1  yes 
The assignment operator =
is the basis for all operators that combine an operator with assignment. The general form is a <op>= b
and the compiler transform that into a = a <op> b
.
Exceptions to the general expansion formula are the logical operators:
a = b
transforms to a  (a = b)
a &&= b
transforms to a && (a = b)
There is another special case when a
is an index accessor ([]
), it is changed to the nilable variant ([]?
on the right hand side:
a[i] = b
transforms to a[i] = (a[i]?  b)
a[i] &&= b
transforms to a[i] = (a[i]? && b)
All transformations assume the receiver (a
) is a variable. If it is a call, the replacements are semantically equivalent but the implementation is a bit more complex (introducing an anonymous temporary variable) and expects a=
to be callable.
The receiver can't be anything else than a variable or call.
Operator  Description  Example  Overloadable 

+=  addition and assignment  i += 1  no 
&+=  wrapping addition and assignment  i &+= 1  no 
=  subtraction and assignment  i = 1  no 
&=  wrapping subtraction and assignment  i &= 1  no 
*=  multiplication and assignment  i *= 1  no 
&*=  wrapping multiplication and assignment  i &*= 1  no 
/=  division and assignment  i /= 1  no 
//=  floor division and assignment  i //= 1  no 
%=  modulo and assignment  i %= 1  yes 
=  binary or and assignment  i = 1  no 
&=  binary and and assignment  i &= 1  no 
^=  binary xor and assignment  i ^= 1  no 
**=  exponential and assignment  i **= 1  no 
<<=  left shift and assignment  i <<= 1  no 
>>=  right shift and assignment  i >>= 1  no 
=  logical or and assignment  i = true  no 
&&=  logical and and assignment  i &&= true  no 
Index accessors are used to query a value by index or key, for example an array item or map entry. The nilable variant []?
is supposed to return nil
when the index is not found, while the nonnilable variant raises in that case. Implementations in the standardlibrary usually raise KeyError
or IndexError
.
Operator  Description  Example  Overloadable 

[]  index accessor  ary[i]  yes 
[]?  nilable index accessor  ary[i]?  yes 
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystallang.org/docs/syntax_and_semantics/operators.html