Learn Crystal in a Day

Crystal is a programming language invented by the last Gelfling to stop evil muppets from taking over his planet. It strongly resembles Ruby and shares many of its characteristics. It is statically typed and compiles to super fast native code. Basically it is a dynamic modern language but is also fast.

It looks like this:

require "http/server"

server = HTTP::Server.new(8080) do |context|
  context.response.content_type = "text/plain"
  context.response.print "Cats are amazing!"

puts "Listening on"

Checkout Crystal in Five Minutes to learn how to setup a Crystal dev environment.

The Crystal Paradigm

In Crystal every thing is an object. The numbers are objects:

42.class  #=> Int32

True and false are objects:

true.class #=> Bool

So are classes:

class Cats
Cats.class #=> Class

Even nil is an object:

nil.class  #=> Nil

In Crystal you will rarely encounter API that isn't a method call. For example, there is no new keyword in Crystal. To create a new instance of an object we call a method named new like this:

myCat = Cat.new

The API for primitive data types like strings or arrays, are also methods:

cat_name = "Ferguson"
cat_name.upcase #=> FERGUSON

cats = ['tabby', 'tiny']
puts cats.first #=> 'tabby'

This object oriented approach keeps the mental surface area of Crystal really tiny. Learning Crystal is often just a matter of learning the methods you can call.

The way we build Crystal programs is through objects and composition of those objects. We can construct a basic object like this:

class Cat
  def talk
    return "meow"
cat = Cat.new()

This model becomes really powerful when we use libraries or frameworks. Instead of learning the API of an external system, we import it and compose it into our own objects. For example, if we want to build a web app we could require an external lib called Kemal and import it into our project:

require "kemal"
get "/" do
  "Hello World!"


Primitive Data types

Let's take a look at all the primitive types we have available in Crystal. We have our fundamental value types; booleans and nil:

# This is a comment btw

nil   #=> false : Bool
true  #=> true : Bool
false #=> false : Bool

Two things are false in Crystal; nil and false. That is it. Everything else is true.


We have integers:

# Integers
42.class #=> Int32

# Signed integer types
1_i8.class  #=> Int8
1_i16.class #=> Int16
1_i32.class #=> Int32
1_i64.class #=> Int64


# Floats
1.0.class #=> Float64

# There 64bit and 32bit floating types
1e10.class    #=> Float64
1.5e10.class  #=> Float64
1.5e-7.class  #=> Float64
1.0_f32.class #=> Float32
1_f32.class   #=> Float32

And finally we have a few other number type thingys:

# Binary numbers
0b1101 #=> 13 : Int32

# Octal numbers
0o123 #=> 83 : Int32

# Hexadecimal numbers
0xFE012D #=> 16646445 : Int32
0xfe012d #=> 16646445 : Int32

Chars and Strings

Chars are done using single quotes like this:

'c'.class #=> Char

And strings use double quotes:

"cats".class #=> String

Strings are immutable and every time you add to one you are actually creating an entirely new string:

cats = "Ferguson"   #=> ​"Ferguson"          : String
cats.object_id      #=> 4523706304          : UInt64
cats += ", Crystal" #=> "Ferguson, Crystal" : String
cats.object_id      #=> 4402814464          : UInt64

You can place vars in a string using #{var} like this:

greatest_cat = "Ferguson"
"The greatest cat is #{greatest_cat}" #=> "The greatest cat is Ferguson"

You can declare a multiline string by just letting it span multiple lines:

"this is a
      multline string
      it just goes
      on and on my friend
      some people started quoting it

If you have a string with some double quotes you can wrap the string in %() instead of quotes:

%(hello "world")


Crystal has immutable constants just like ruby:

:cats.class #=> Symbol
greatest_cat == :cat? #=> true  : Bool


Crystal has arrays:

[1, 2, 3, 4].class #=> Array(Int32)
[1, "gargleblaster", "infinite phaser"].class #=> Array(Int32 | String)

You need to make sure that you specify a type for an empty array because Crystal cant do type inference on an empty array:

cats = [] # Syntax error in :1: for empty arrays use '[] of ElementType'
cats = [] of String      #=> [] : Array(String)

Arrays have indexes:

cats = ["Ferguson", "Snowball", "McFluffypants"]
cats[0] #=> "Ferguson" : String

You can use a range as an index:

array = [1, 2, 3, 4, 5] #=> [1, 2, 3, 4, 5] : Array(Int32)
array[2, 3] #=> [3, 4, 5]
array[1..3] #=> [2, 3, 4]

We can add to an array using <<:

array << 6  #=> [1, 2, 3, 4, 5, 6]

And remove items using pop and shift:

# Remove from the end of the array
array.pop #=> 6
array     #=> [1, 2, 3, 4, 5]

# Remove from the beginning of the array
array.shift #=> 1
array       #=> [2, 3, 4, 5]

You can check if an item exists in an array using includes?:

array.includes? 3 #=> true

If your array only consists of strings or symbols you can use a special syntax to eliminate those pesky commas and quotes:

%w(Ferguson Sardine) #=> ["Ferguson", "Sardine"] : Array(String)
%i(Ferguson Sardine) #=> [:Ferguson, :Sardine]   : Array(Symbol)


You can create ranges of numbers with dots:

1..10                  #=> Range(Int32, Int32)
Range.new(1, 10).class #=> Range(Int32, Int32)

If you use two dots the range is inclusive, with three dots it is exclusive:

(3..5).to_a  #=> [3, 4, 5]
(3...5).to_a #=> [3, 4]


Hashes exist in Crystal. You can use any type as a key:

{1 => 2, 3 => 4}.class                     #=> Hash(Int32, Int32)
{ :cats => ["Ferguson", "Sardine"] }.class #=> Hash(Symbol, Array(String))

You must specify a type for empty hashes (just like with arrays):

{}                     # Syntax error
{} of Int32 => Int32   # {}

You can lookup elements in a hash using []:

cats = {
  :Ferguson => {
    :weight => "fat"
cats[:Ferguson][:weight] #=> "fat" : String

colors = {"green" => "frog"}
colors["green"] #=> "frog"

numbers = {1 => 2, 3 => 4}
numbers[1] #=> 2

Looking up a hash a non-existent key will throw an error:

hash = { "dogs" => "are awesome" }
hash["no_such_key"]  #=> Missing hash key: "no_such_key" (KeyError)

You can get nil instead of an error by using a question mark:

hash = { "dogs" => "are awesome" }
hash["no_such_key"]? #=> nil


Tuples are a fixed size, immutable, ordered list of values. They are constructed like this:

{1, "cats", 2, 'c'} #=> Tuple(Int32, String, Int32, Char)

You can access values by index:

cats = {"Ferguson", "Sardine"}
cats[0] #=> "Ferguson"

Accessing a value that doesn't exist will throw an error:

cats = {"Ferguson", "Sardine"}
cats[2] #=> syntax error : Index out of bound

To check for the existence of a value by index use a question mark:

cats = {"Ferguson", "Sardine"}
cats[2]? #=> nil


Crystal is not a very functional language but it does provide procs. Procs are objects with a call method. (If you ask some programmers, they will tell you that is all a function is). Procs give you the ability to pass functions around and change their context. You can create one like this:

talk = ->(name: String) { "meow! my name is #{name}" }
talk.call("Ferguson") #=> "meow! my name is Ferguson" : String

Flow Control

Crystal has all the typical control statements. They are closed with the end keyword.

Crystal has ifs which look like:

if true
  "cats are awesome"
elsif false
  "dogs are awesome"
  "all animals are awesome"

You can inline an if:

puts "cats are awesome" if true

It is important to remember that Crystal is a language with types, this means that control flow statements will influence how Crystal infers types. A var's type depends on the type of the expressions used:

if a.is_a? String
  a.class #=> String

if a < 4625
  a = "mysterious"
  a = true
# a : String | Bool

b = 1
if cats_are_awesome
  b = "cats"
# b : Int32 | String

if can be used as an expression:

a = if 2 > 1

a #=> 3

And as a ternary operator:

a = 1 > 2 ? 3 : 4 #=> 4

Case Statements

Crystal has case/switch statements:

tasks = "meow"

action = case cmd
  when "meow"

action #=> "meowing"


Crystal has loops for looping your loopy stuff. There are three forms; while, until, and each. each is the most common one, as the enumerable protocol uses that form. When you have an enumerable object, it will provide an each method which expects a block to pass each item to:

(1..3).each do |Index|
  puts "Number: #{num}"
# "Number: 1"
# "Number: 2"
# "Number: 3"

The while form looks like this:

index = 1
while index <= 3
  puts "Index: #{index}"
  index += 1
# Index: 1
# Index: 2
# Index: 3

And the until form:

index = 1
until index > 3
  puts "Index: #{index}"
  index += 1
# Index: 1
# Index: 2
# Index: 3


Functions in Crystal look like this:

def double(x)
  x * 2

def cats
  return "are awesome"

You can call them like this:

double 2  #=> 4 : Int32
double(4) #=> 8 : Int32
cats      #=> "are awesome" : String

Parentheses are optional when the call is unambiguous

def sum(x, y)
  x + y

sum 3, 4 #=> 7

sum sum(3, 4), 5 #=> 12

Functions can have question marks:

def are_cats_awesome?
  return true

And exclamation marks:

def cats_are_awesome!
  "cats are awesome!"

The standard convention is to use exclamation marks when a method will modify the object instead of returning a new one:

name = "Mr. Ferguson"
name.gsub "Mr.", "Dr."  #=> "Dr. Ferguson"

# The value of name is still "Mr. Ferguson"
name #=> "Mr. Ferguson"

# But if we use gsub! instead it gets changed
name.gsub! "Mr.", "Dr."  #=> "Dr. Ferguson"
name #=> "Dr. Ferguson"

Blocks and yield

All functions/methods have an implicit block parameter that we can execute with yield:

def surround
  puts '{'
  puts '}'

surround { puts "hello world" }

# Outputs
# {
# hello world
# }

Blocks can be used like procs and have a call method:

def cats(&block)
  block.call "are awesome"


You define a class with the class keyword:

class Cat
  # A static class variable
  @@species = "hallway speeders"

  # an instance var with type
  @name : String

  def initialize(@name)

  # Setter for name
  def name=(name)
    @name = name

  # Getter for name
  def name

  # We can declare both getters and setters in one shot with `property`
  property :name

  # We can also declare getters and setters individually
  getter :name
  setter :name

  # An instance method
  def talk(msg)
    puts "I'm talking from an instance #{msg}"

  # A class method
  def self.talk(msg)
    puts "I'm talking from a class #{msg}"

We can use it like this:

ferguson = Cat.new("Ferguson")

And we can call the methods like this:

ferguson.species #=> "hallway speeders"
ferguson.name #=> "Ferguson"
ferguson.talk #=> "I'm talking from an instance"
Cat.talk #=> "I'm talking from a class"

We can extend a class like this:

class AdorableCreature < Cat


Modules are a way encapsulating a bunch of shared methods:

module CatsModule
  def talk
    "I'm a cat and I'm awesome"

You can include these methods onto a class instance with the include keyword:

class Cat
  include CatsModule

ferguson = Cat.new
ferguson.talk #=> "I'm a cat and I'm awesome"

You can add the same methods to a class by extending it:

class Cat
  extend CatsModule
Cat.talk #=> "I'm a cat and I'm awesome"

Exception handling

Crystal has try/catch like exception handling. Wrap the code the code that might error in a begin statement and then catch it with rescue:

exception = begin
  raise Exception.new
rescue ex1 : IndexError
  puts ex1
  "Index error"
rescue ex2 : MyException | MyAnotherException
  puts ex2
  "Custom exception"
rescue ex3 : Exception
  puts ex3
  "Default exception"
rescue ex4
  "catch all". # the last rescue is the equivalent of a "finally" statement.
exception #=> "Default exception"

You can define a custom exception simply by extending exception:

class MyException < Exception


Crystal supports destructuring arrays and tuples. You can destructure an array like this:

one, two, three = [1,2,3]
one   #=> 1
two   #=> 2
three #=> 3

It works on functions to:

def cats
  ["Ferguson", "Sardine"]
ferguson, sardine = cats
ferguson #=> "Ferguson"

Works on tuples too:

def cats
  {"Ferguson", "Sardine"}
ferguson, sardine = cats
ferguson #=> "Ferguson"


Splats are like destructuring but the reverse. It can take arguments and convert them into an array:

def cats(*array)
  array.each { |cats| puts cats }

That does it! You should have a decent ground in the basics of Crystal now! Be sure to checkout awesome-crystal for a bunch more resources.