This notebook is a work-in-progress. As it stands, it's a collection of my jots, notes and bookmarks about Crystal programming language.

Once complete, I intend to edit the entire notebook so that everything is accessible and useful to readers beside myself.

Printing

Let’s start with a brief word about printing values. Throughout the notebook, we’ll be printing values out to demonstrate what a certain piece of code does and there a few different ways we can do this:

  1. puts1 writes to STDOUT and adds a newline character unless we are printing a String that ends with a newline.
  2. p2 writes to STDOUT using inspect.
  3. pp3 writes to STDOUT using inspect and prettyprint.
  4. p!4 writes the value and the expression to STDOUT.

See printing in action:

name = "John"

puts name # => John
p name    # => "John"
pp name   # => "John"
p! name   # => name # => "John"

Another useful method is the typeof method which returns the type of the object we pass to it.

We’ll use this method in our print calls to see the type we’re working with like so:

name = "John"
p! name, typeof(name)
#OUTPUT
name         # => "John"
typeof(name) # => String

Variables

You can declare and assign value to variables with the following syntax:

# Assign variables:
first_name = "John"
last_name = "Doe"
age = 42
driver_license_type = 'A'

# Print values:
p first_name            # => "John"
p last_name             # => "Doe"
p age                   # => 42
p driver_license_type   # => 'A'

The types of the variables are inferred by the compiler:

first_name = "John"
last_name = "Doe"
age = 42
driver_license_type = 'A'

# Print types:
p typeof(first_name)            # => String
p typeof(last_name)             # => String
p typeof(age)                   # => Int32
p typeof(driver_license_type)   # => Char

It's possible to be explicit about types:

## Type declaration and assignment:
first_name : String = "John"

## Type declaration:
last_name : String

## Assignment
last_name = "Doe"

p first_name            # => "John"
p last_name             # => "Doe"

We can use the multiple assignment syntax to declare more than one variable in one statement or to reassign values:

x, y = 20, 30
p x # => 20
p y # => 30

x, y = y, x
p x # => 30
p y # => 20

It's also possible to have multiple statements on the same line by using a semicolon in between statements:

first_name = "John"; last_name = "Doe";

Constants

Constant names start with uppercase letters, though as per convention you should keep all characters uppercase.

PHI = 1.61803
# PHI += 42 # => Cannot reassign to constant.

You can declare constants at the top level or inside other types, however the value of a constant cannot be mutated.

Arithmetic Operations

Let’s have a look at some basic arithmetic operations we can perform:

add = 40 + 2
subtract = 102 - 60
multiply = 14 * 3
divide = 672 / 16       # Result type: Float64

divide_int = 9 // 4     # Result type: Int32 (Rounded down)
power = 2 ** 10
mod = 42 % 24

p! add, subtract,multiply, divide
p! divide_int, power, mod

#OUTPUT:

add      # => 42
subtract # => 42
multiply # => 42
divide   # => 42.0
divide_int # => 2
power      # => 1024
mod        # => 18

It’s important to note that it’s possible to mix floats and integers. Have a look at the following result types:

result_one = 672.5 / 16
result_two = 672 / 16
result_three = 21 * 2.0

p! result_one, typeof(result_one)
p! result_two, typeof(result_two)
p! result_three, typeof(result_three)

#OUTPUT:

result_one           # => 42.03125
typeof(result_one)   # => Float64
result_two           # => 42.0
typeof(result_two)   # => Float64
result_three         # => 42.0
typeof(result_three) # => Float64

Control Flow

Here's a simple if statement:

age = 21
if age < 21
  puts "You cannot purchase that item."
end

You can chain operators to check within a range:

x = 3
if 2 < x < 5
  puts x
end
# => 3

You can define the previous example with logical operators:

x = 3
if 2 < x && x < 5
  puts x
end
# = > 3

For more on chaining operators, take a look at the docs.

More complex statement with if/elseif/else:

x = 4

if x < 5
  puts "smaller than 5"
elsif x < 8
  puts "bigger than 5, smaller than 8"
else
  puts "bigger than 8"
end

# => "smaller than 5"

You can assign an if statement to a variable:

x = 7

result = if x < 5
           "smaller than 5"
         elsif x < 8
           "bigger than 5, smaller than 8"
         else
           "bigger than 8"
         end

p result # => "bigger than 5, smaller than 8"

You can use an if statement as a suffix:

x = 3
output = "smaller than 5" if x < 5

p output # => "smaller than 5"

You can use an unless statement for the inverse:

x = 3
output = "smaller than 5" unless x > 5

p output # => "smaller than 5"

Case/When Statements

Use case/when statements for longer and complex expressions where you test the same value against different conditions:

x = 1
output = case x
         when 3
           "x equals 3"
         when 5
           "x equals 5"
         when 9
           "x equals 9"
         else
           "x is not 3/5/9"
         end

p output # => "x is not 3/5/9"

An example of a case/when statement with comparisons:

x = 6
output = case
         when x <= 5
           "5 or smaller"
         when 5 < x < 8
           "bigger than 5, smaller than 8"
         else
           "bigger than 8"
         end
p output # => bigger than 5, smaller than 8

Ternary Operator

Crystal has a ternary operator for concise if statements:

user_age = 18
registered = false

output = user_age >= 18 && registered ? "User can vote" : "User cannot vote"
p output # => User cannot vote

Loops

Loop n times

Take a look at the following syntax examples of running a block of codes n times.

Using times method on the Int type:

5.times do
  p "hi!"
end

Using a block in curly brackets:

t.times {
  p "hi!"
}

Iterating over Array and Range types

Example 1:

friends = ["John", "Martha", "Paige", "Cooper", "Herbert"]

friends.each do |friend|
  puts friend # => John, Martha, Paige, Cooper, Herbert
end

Example 2:

friends = ["John", "Martha", "Paige", "Cooper", "Herbert"]
(1...3).each do |friend|
  puts friend # => Martha, Paige
end

Using loop do

You can express a custom logic with loop do:

x = 1
loop do
  puts "x = #{x}"
  x += 1
  break if x == 3
end

Using a classic while loop

x = 1
while (x += 1) < 10
  p j # => 2, 3, 4, 5, 6, 7, 8, 9
end

Within loop constructs, you can use next and break statements:

x = 1
while (x += 1) < 10
  if x == 3
    next # jump to next iteration
  elsif j > 6
    break # break loop here
  end
  puts x # => 2, 4, 5, 6
end

Use unless for the inverse

x = 5
until (z -= 1) == 0
  p z # => 4, 3, 2, 1
end

Methods

Methods (functions) are an important building block. Some methods are available on the top-level scope, few common examples are typeof, puts, gets. Whereas other methods are available on certain objects, such as the size method which is available on the String object.

A simple method in Crystal looks like this:

def double(num)
  num * 2
end

p double 6 # => 12

Note that, methods return their last expression implicitly. Though you can use return keyword explicitly.

What happens if we call our double method on a String?

p double "66" # => "6666"

Because the multiplication operator works on the String type as well, we get "6666" as a result.

That's where static typing and explicitly restricting types come into play:

def triple(num: Int32)
  num * 3
end

p triple 2   # => 6
p triple "2" # => Error: No overload matches triple with type string

It's possible to be flexible and overload a method to accept another type parameter:

def triple(str : String)
  str * 3
  p "You just tripled a string. Weird."
end

Default values

You can define default values for parameters of your methods:

def random_number(base, max =10)
  base + rand(0..max)
end

p random_number 5    # => Random number between 5 to 15
p random_number 5, 5 # => Random number between 5 to 10

Note that the parameter that has a default value has become an optional argument for the caller site.

Named parameters

You can explicitly use named parameters on the caller site:

p random_number 5, max: 5
p random_number base: 5, max: 5

It is also possible to force the use of named parameters on the caller site. In the following example, all parameters on the right side of * will have to be named parameters:

def greeter(*, first_name, last_name, emphatic = false)
  if emphatic
    p "Greetings #{first_name} #{last_name}!!!!"
  else
    p "Greetings #{first_name} #{last_name}."
  end
end

greeter first_name: "John", last_name: "Doe"                 # => Greetings, John Doe
greeter first_name: "John", last_name: "Doe", emphatic: true # => Greetings, John Doe!!!!

Naming parameters externally/internally

See the following example:

def multiply(value, *, by num)
  value * num
end

p multiply(3, by: 5)

Let's inspect each parameter of the method multiply:

  1. value is a regular positional parameter, caller provides a value.
  2. * indicates all the following parameters will have to be called by their name.
  3. by num represent the same parameter, by is inteded for the external use, num is intended for internal use.

Passing blocks to methods

You can pass blocks of code to a method, and these blocks can be invoked within the method with the use of the yield keyword.

See the example:

def perform_op
  p "We're in the method perform_op"
  yield
  p "We've executed the passed code block"
  yield
  p "We've executed the passed code block again"
  p "Leaving perform_op method scope"
end

# You can pass a code block in two ways:
perform_op do
  puts "this is the passed code block"
end

perform_op {
  puts "this is the passed code block"
}

Passing blocks with context (arguments and return values)

def perform_upcase(str)
  str = str.upcase
  yield str
end

def perform_exclaim(str)
  str += "!!!!!"
end

sample = "hello john"
result = perform_upcase sample do |s|
  perform_exclaim s
end

Using next keyword within a passed code block

You can use the next keyword to stop the execution of the passed code block, and return a value to the yield statement that invoked it. If a value is passed to the next keyword, yield will receive it.

def greeting_generator
  person_one = yield "John"
  person_two = yield "Jane"
  person_three = yield "Paige"

  "#{person_one}, #{person_two}, #{person_three}"
end

# Let's pass a code block to get a different type of greeting for John
result = greeting_generator do |prs|
  if prs == "John"
    next "Greetings #{prs}"
  end
  "Hi #{prs}"
end

p result # => Greetings John, Hi Jane, Hi Paige

Using the break keyword within a passed code block

You can use the break keyword to stop the method that is invoking the inner block, similar to encountering a return.

def greeting_generator
  person_one = yield "John"
  person_two = yield "Jane"
  person_three = yield "Paige"

  "#{person_one} , #{person_two}, #{person_three}"
end

result = greeting_generator do |prs|
  if prs == "John"
    # This will be returned on break, and the inner method will stop.
    break "Greetings #{prs}"
  end

  "Hi #{prs}"
end

p result # => Greetings John

Using a return statement within a passed code block

The return statement will stop the execution of the passed code block similar to break, however it will also return from the method where the passed code block was written.

Tip: Return always finalizes the execution of the method that it resides in.

See the following example:

def greeting_generator
  person_one = yield "John"
  person_two = yield "Jane"
  person_three = yield "Paige"

  "#{person_one} , #{person_two}, #{person_three}"
end

def run
  greeting_generator do |prs|
    if prs == "John"
      return "Greetings #{prs}" # => The method `run` will also exit at this point.
    end
    "Hi #{prs}"
  end

  # -> if there was a statement here, this would not be executed due to return.
end

p run # => "Greetings John"

Using Splat parameters

It's possible to define a method that accepts an arbitrary number of arguments by prefixing the argument with the * symbol:

def greet(*people)
  p "We will greet #{people}" # => We will greet {{\"John\", \"Jane\", \"Paige\", \"Herbert\"}}"
  p typeof(people)            # => Tuple(String, String, String, String)

  people.each do |prs|
    p "Hi, #{prs}!"
  end
end

greet("John", "Jane", "Paige", "Herbert")
# =>
# "Hi, John"
# "Hi, Jane"
# "Hi, Paige"
# "Hi, Herbert"

As you can see in the type check of the splat parameters, it's a tuple. Splat parameters are alwoys referred to as a Tuple of zero or more arguments.

Types

Boolean

The boolean type can have the value of either true or false:

t = true
f = false

p! t, typeof(t)
p! f, typeof(f)
#OUTPUT:

t         # => true
typeof(t) # => Bool
f         # => false
typeof(f) # => Bool

Nil

nil represents a lack of value. It’s type is Nil:

val = nil
p! val, typeof(val)
#OUTPUT:
val         # => nil
typeof(val) # => Nil

Compound Types

Arrays

Initializing Arrays

You can initialize arrays in Crystal as such:

friends = [] of String
peers = Array(String).new

Note that you cannot use initialize empty array with the following syntax:

friends = [] # => Error: for empty arrays use [] of ElementType

We can however use the following syntax to create an array whose type will be inferred by the compiler:

friends = ["John", "Edward", "Ariel"]
p typeof(friends) # => Array(String)

peers = %w(Bill Marucio)
p typeof(peers) # => Array(String)

It's also possible to create Arrays of a union type. See what the compiler infers:

items = ["John", 'C', 42]
p typeof(items) # => Array(Char|Int32|String)

When union types are used, the compiler will check if all the methods that are called on these types exist or not.

Adding Items

Let's add some items of the String type to the arrays:

friends << "Jane"
peers << "Brian"

You cannot add another type to an array of strings:

friends << "42" # => Error

Accessing Items

You can access elements via indexing. You can also use ranges:

friends = ["John", "Edward", "Ariel", "Alex", "Chael"]
p friends[0] # => "John"


# A sub-array using ranges:
p friends[1, 3] # => ["Edward", "Ariel", "Alex"]
p friends[1..3] # => ["Edward", "Ariel", "Alex"] (1 to 3, 3 inclusive)
p friends[1...3] # => ["Edward", "Ariel"] (1 to 3, 3 exclusive)

It's a good idea to check if a certain index is within bounds, otherwise you can get a runtime error. If you use the []? syntax, you'll get a nil instead of a runtime error.

friends = ["John", "Edward", "Ariel", "Alex", "Chael"]
p friends[5] # => Exception: Index out of bounds

# Making sure index 5 exists:
p friends[5]?  # => nil

Checking if an item exists in array by it's value:

friends = ["John", "Edward", "Ariel", "Alex", "Chael"]

p friends.includes? "John" # => true
p friends.includes? "Bob"  # => false

Removing items

You can use shift and pop methods to remove items at the first and the last indices.

friends = ["John", "Edward", "Ariel", "Alex", "Chael"]

p friends.shift # => "John"
p friends.pop   # => "Chael"

puts friends    # => ["Edward", "Ariel", "Alex"]

You can also use the delete method

friends = ["John", "Edward", "Ariel", "Alex", "Chael"]

friends.delete "John"

puts friends    # => ["Edward", "Ariel", "Alex", "Chael"]

Iteration

You can iterate over the elements with the each do block:

friends = ["John", "Edward", "Ariel", "Alex", "Chael"]

friends.each do |i|
  puts i
end

Recipes to display contents of array

display = [1, 2, 3, 4, 5, 6, 7, 8, 'A', "hello"]
print display # =>  [1, 2, 3, 4, 5, 6, 7, 'A', "hello"] (no newline)
puts display  # =>  [1, 2, 3, 4, 5, 6, 7, 'A', "hello"] (with newline)
pp display    # =>  [1, 2, 3, 4, 5, 6, 7, 'A', "hello"] (with newline)
p display.inspect # =>"[1, 2, 3, 4, 5, 6, 7, 8, 'A', \"hello\"]" (with newline)
printf("%s", display[1])    # => 2
p sprintf("%d", display[1]) # => "2"

Other methods

Crystal offers us different ways to manipulate data stored in Arrays, the complete list of these methods are in Crystal API docs.

Hashes

Hashes are the ideal container type for dealing with key/value pairs.

Creating a hash is simple. In the following example, we'll be creating a Hash with the type Hash(String, Int32)

friends_age = {
  "John"   => 42,
  "Jane"   => 31,
  "George" => 50,
  "Max"    => 28,
}

p typeof(friends_age) # => Hash(String, Int32)

Accessing values

You can read a value using keys:

p friends_age["Jane"] # => 31

Trying to acces a value which doesn't exist will result in an exception:

p friends_age["Patrick"] # => Error: Unhandled exception, missing hash key "Patrick"

Therefore, we need to handle the outcome where a key might not exist with ?:

p friends_age["Jane"]?    # => 31
p friends_age["Patrick"]?  # => nil

Another way to handle this would be to use the predicate function has_key?

p friends_age.has_key? "Jane"     # => true
p friends_age.has_key? "Patrick"  # => false

Updating Values

We can update a value quite simply:

friends["John"] = 43

Creating Hashes

Just like arrays, declaring an empty hash with no values and no type information is not possible:

my_hash = {}  # => Error: for empty hashes use {} of KeyType => ValueType

Instead we need to provide the type information:

my_hash = {} of String => Int32
my_hash_two = Hash(String, Int32).new

If you want to check if the hash is empty, you can use the empty? predicate:

p my_hash.empty? # => false

Examples of Different Syntax

There are different ways to declare key value pairs, have a look at the following declarations and their inferred types:

hash_a = {"a" => 1, "b" => 2, "c" => 3} # => Hash(String, Int32)
hash_b = {:a => 1, :b => 2, :c => 3}    # => Hash(Symbol, Int32)
hash_b2 = {a: 1, b: 2, c: 3}            # => NamedTuple(a: Int32, b: Int32, c: Int32)
hash_c = {"a": 1, "b": 2, "c": 3}       # => NamedTuple(a: Int32, b: Int32, c: Int32)

If a hash uses symbols as keys, you can access them like so:

fruits = {:watermelon => 4.99, :banana => 2.00}

p fruits[:watermelon]   # => 4.99
p "Keys: #{fruits.keys} Values: #{fruits.values}" # => "Key: [:watermelon, :banana] Value: [4.99, 2.0]"

Tuples

Tuples are immutable lists, and all the types of the elements are known at compile time.

See:

a_tuple = {42, 84, "John", 'C'}
p typeof(a_tuple) # => Tuple(Int32, Int32, String, Char)

Ranges

The range type can be used for representing an interval of values. The intervals could be last item inclusive or last item exclusive:

inclusive = 2..7
exclusive = 2...7

Examples

A range can be many different things, alphabet, time intervals etc. Here's the alphabet example:

a_to_z = 'a'..'z'       # => All letters between A to Z
aa_to_zz = "aa".."zz"   # => All two letter combinations between A to Z

Enumerable and Iterable

The range type implements Enurable and Iterable. These implementations provide useful methods that will let us treat ranges as data collections:

inclusive = 2..7
exclusive = 2...7

# Predicate methods:
p inclusive.includes? 3 # => true
p inclusive.covers? 7   # => true
p 3.in? inclusive       # => true

# Method for summing all the values:
p inclusive.sum # => 27

# A random element within the range:
p inclusive.sample # => 4

Object-Oriented Programming

Classes and Objects

Object-Oriented programming in Crystal is very similar to other OOP languages.

  1. Objects are associated with data and behaviour defined by instance variables and methods.
  2. Classes are blueprints that the objects are created from.

In Crystal, everything is an object and every object is an instance of some class.

You can get the class of an object by using .class method. See below:

p 'C'.class             # => Char : Class
p "John".class          # => String : Class
p false.class           # => Bool : Class
p nil.class             # => Nil : Class
p ["John", 42].class    # => Array(String | Int32) : Class

All the primitives being objects comes with the benefit of having useful methods attached to them. One common example would be the size method that's defined on the String class.

Creating Classes and Objects

In Crystal, we use CamelCase to name our classes. When you create a class you are also defining a new type.

# Creating a class:
class Bird
end

# Creating an object:
b = Bird.new
p typeof(b) # => Bird

Using A Constructor

The initialize method is used as a constructor:

class Dog
  def initialize(name : String, age : Int64)
    @name = name
    @age = age
  end
end

rex = Dog.new "Rex", 2
p typeof(rex) # => Dog

You can use a shorter syntax for instance variables. By prefixing the constructor parameters with @, we can make the compiler create instance variables of the same name automatically:

class Dog
  def initialize(@name : String, @age : Int64)
  end
end

Reading & Updating Instance Variables

We can use getters and setters to enable read/write behaviour on our instance variables. Both of these are macros that save us from the boilerplate of writing manual getters and setters.

class Bird
  getter name : String
  getter age : Int32
  setter age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def describe
    "This bird's name is #{name} and it is #{age} years old"
  end

  def to_string
    self.to_s
  end
end

jax = Bird.new "Jax", 3

p jax.name # => Jax
p jax.age  # => 3

jax.name = "John" # => We cannot mutate this value, because there's no setter defined
jax.age += 1
p jax.age # => 4

# Instance methods that are accessible from outside:
p jax.describe  # => This bird's name is Jax and it is 4 years old.
p jax.to_string # => "#<Bird:0x41412f>"

There's one more shorthand for situations where we use the macros getter and setter. Instead of using these two, we could use property. This will make it so that the instance variable will have both a getter and a setter:

class Elephant
  getter name : String
  property age : Int32

  def initialize(name, age)
    @name = name
    @age = age
  end
end

e = Elephant.new "Marcus", 19

p e.name # => "Marcus"
p e.age  # => 19

e.age += 1
p e.age # => 20

A Few Useful Methods

Take a look at the methods below:

class Person
end

p1 = Person.new
p2 = Person.new

p p1              # => #<Person:0x4342>
p p1.to_s         # => "#<Person:0x4342>"
p p1 == p2        # => false # This compares the reference in memory
p p1.same? p2     # => false # Same as above
p p1.nil?         # => false
p p1.is_a? Person # => true  # p1 is an instance of Person

Behind The Scenes For new And initialize

First let's create a class, notice the shorthand syntax compared to the examples before:

class Person
  getter first_name, last_name, age

  def initialize(@name : String, @last_name : String, age : Int32)
  end
end

john = Person.new("John", "Doe", 42)

pp john           # => #<Person:0x34234 ...>
pp john.object_id # => 4312425842

When new is called on class, we allocate memory for the class and then run initialize automatically. After initialization, the object is created and placed on the heap.

Calling the object_id method on the object returns it's memory address. This reference will be used to pass this object around.

Generic Types:

It's possible to define a class with a generic type to allow the initializer to accept different types. For this, use the following syntax:

class Person(T)
  getter name

  def initialize(@name : T)
  end
end

It's possible to define a class with the generic T type, and initia

Variable prefixes: @ and @@

For instance variables use the prefix @.

For class variables use the prefix @@.

Calling Methods On The Class Itself

Use the self prefix.

Copying Objects:

Shallow Copy: Use the dup method.

Shallow copy provides a copy that is different in memory but has the same fields as the original object.

Deep Copy: Use the clone method.

Deep copy on the other hand is for cloning.

Have a look at the following example for how copying works:

class Person
  property name
  property age

  def initialize(@name : String, @age : Int32)
  end

  def clone
    self
  end
end

john = Person.new("John", 42)

shallow_copy = john.dup
deep_copy = john.clone

pp john                   # => #<Person:0x7fd1d3604ea0 @age=42, @name="John">
pp john.object_id         # => 140539171196576
pp shallow_copy           # => #<Person:0x7fd1d3604900 @age=42, @name="John">
pp shallow_copy.object_id # => 140539171195136
pp deep_copy              # => #<Person:0x7fd1d3604ea0 @age=42, @name="John">
pp deep_copy.object_id    # => 140539171196576

pp john == shallow_copy             # => false
pp john.class == shallow_copy.class # => true

pp john == deep_copy                # => true
pp john.class == shallow_copy.class # => true

The finalize Method

The finalize method is essentially a hook for the garbage collector, it will be run when the object is GC'ed.

Inheritance

Inheritance is an important piece of OOP patterns. In simple terms, a class inheriting from another class means it'll get the parent classes methods, instance variables and it'll be able to add/overwrite existing ones.

Here's a basic example of inheritance:

class Animal
  property name : String
  property age : Int32

  def initialize(@name, @age)
  end
end

class Dog < Animal
  property domesticated = true
  property dexterity = 10
end

tex = Animal.new "Tex", 12
rex = Dog.new "Rex", 3

rex.domesticated = true
rex.dexterity = 8

# You can see below that the child class satisfies
# both the Parent and Child when is_a method is raised:

p tex.is_a? Animal # => true
p tex.is_a? Dog    # => false
p rex.is_a? Animal # => true
p rex.is_a? Dog    # => true

Inheritance Continued: Overriding

Overriding works very much the same way as it works with other OOP languages.

Basic example:

class Person
  property name : String

  def initialize(@name)
  end
end

class Employee < Person
  property salary = 0
end

tt = Employee.new "TT"

Reopening A Class

You might want to reopen a class for different reasons, Crystal allows this and the additions you make with the reopened class will be combined into a single class.

Have a look at the following example:

class Employee < Person
  def yearly_salary
    12 * @salary
  end
end

class SalesEmployee < Employee
  property bonus = 0

  def yearly_salary
    12 * @salary + @bonus
  end
end

john = Person.new "John"
jane = Employee.new "Jane"
rich = SalesEmployee.new "Rich"

jane.salary = 2000
p jane.yearly_salary # => 24000

rich.salary = 2000
rich.bonus = 1000
p rich.yearly_salary # => 25000

This example can be further enhanced by the usage of the super method where by calling super we get to use the super class functionality:

class HrEmployee < Employee
  property bonus = 0

  def yearly_salary
    super + @bonus
  end
end

herbert = HrEmployee.new "Herbert"
herbert.salary = 2000
herbert.bonus = 2000
p herbert.yearly_salary # => 26000

Abstract Classes

A great use for abstract classes are where you want to have a class but don't want to create objects based on the class. By defining an abstract class we can create a blueprint for subclasses.

Following example demonstrates inheriting from an abstract class but having different constructors:

abstract class Shape
end

# Circle inherits from Shape

class Circle < Shape
  def initialize(@radius : Float64)
  end
end

class Rectangle < Shape
  def initialize(@width : Float64, @height : Float64)
  end
end

crc = Circle.new(4)
rec = Rectangle.new(2, 4)

The following example is a more common pattern where we define an abstract method on the abstract class, thus making sure that the classes which implement from the abstract, implement the abstract methods. If they don't, you'll get a compilation error:

abstract class Shape
  abstract def area : Number
end

# Circle inherits from Shape

class Circle < Shape
  def initialize(@radius : Float64)
  end

  def area : Number
    MATH::PI * radius * 2
  end
end

crc = Circle.new(12)
p crc.area # => 323......

# The following class won't compile because it doesn't implement
# area method:

class Rectangle < Shape
  def initialize(@width : Float64, @height : Float64)
  end
end

Absract Classes

A great use for abstract classes are where you want to have a class but don't want to create objects based on the class. By defining an abstract class we can create a blueprint for subclasses.

Following example demonstrates inheriting from an abstract class but having different constructors:

abstract class Shape
end

# Circle inherits from Shape

class Circle < Shape
  def initialize(@radius : Float64)
  end
end

class Rectangle < Shape
  def initialize(@width : Float64, @height : Float64)
  end
end

crc = Circle.new(4)
rec = Rectangle.new(2, 4)

The following example is a more common pattern where we define an abstract method on the abstract class, thus making sure that the classes which implement from the abstract, implement the abstract methods. If they don't, you'll get a compilation error:

abstract class Shape
  abstract def area : Number
end

# Circle inherits from Shape

class Circle < Shape
  def initialize(@radius : Float64)
  end

  def area : Number
    MATH::PI * radius * 2
  end
end

crc = Circle.new(12)
p crc.area # => 323......

# The following class won't compile because it doesn't implement
# area method:

class Rectangle < Shape
  def initialize(@width : Float64, @height : Float64)
  end
end

Visibility

It's possible to restrict visibility and encapsulate code in the OOP tools Crystal provide.

The default behavior of an object is that it's visible under the namespace it's defined, but not to the outside. Unless you restrict the visibility of a method, it's public by default. You can use private or protected keywords to restrict this visibility.

An example of using a private method:

class Person
  def initialize(@name : String)
  end

  private def greet(message : String)
    p message
  end

  def greeter
    greet "Hi, #{@name}"
  end
end

class Employee < Person
  def greeter
    super
    p "Welcome to the office."
  end
end

p = Person.new("John")
p.greeter # => "Hi, John"

e = Employee.new("Mike")
e.greeter # => "Hi, Mike" "Welcome to the office"

An example of using a protected method:

class Person
  def initialize(@name : String)
  end

  protected def greet(message : String)
    p message
  end

  def greeter
    greet "Hi, #{@name}"
    self.greet "Hi, #{@name}"
  end
end

class Employee < Person
  def greeter
    person = Person.new("Jan")
    person.greet("Hi from the employee class") # => Works, because Employee is a person
  end
end

p = Person.new("John")
p.greeter # => "Hi, John"

e = Employee.new("Mike")
e.greeter # => "Hi, Mike" "Welcome to the office"

class Machine
  def greeter
    emp = Employee.new("David")
    emp.greet("Hi from the machine") # => Error: protected method 'greet' called for Person
  end
end

Modules

Modules are great for grouping methods and classes that are related under a namespace. You can extend modules with a class, and the methods of module will be available on the class that extends it.

module Stats
  def data
    {
      "strength"     => 10,
      "dexterity"    => 5,
      "intelligence" => 3,
    }
  end

  def stats
    data[self.stat_name]
  end
end

class CharacterStats
  # Include Stats as a mixin:
  include Stats
  getter stat_name : String

  def initialize(@stat_name)
  end
end

npc = CharacterStats.new "dexterity"
p npc.stats # => 5

Working with nilable types

Since Crystal supports union types, it’s possible to have methods whose returns are nilable.

Here’s a method with a return signature of (String | Nil) or in short String?.

def maybe_string : String?
  condition = true
  if condition
    "A string..."
  else
    nil
  end
end

And say we want to upcase the returned String:

val = maybe_string

p! val.upcase  # => Error: undefined method 'upcase' for Nil (compile-time type is (String | Nil))

The result would be an error because the compiler wouldn’t be able to guarantee that the return type is a String and not Nil. If the return type is in fact Nil, then it would not have an upcase method available.

To avoid this error, we can check for nil with the tools Crystal provides. Here’s at least three different ways we could do this:

def maybe_string : String?
  condition = true
  if condition
    "A string..."
  else
    nil
  end
end
val = maybe_string

# Method one - Check if `val` is truthy:
p! val.upcase if val

# Method two - Check if `val` is nil:
p! val.upcase unless val.nil?

# Method three - Check if `val` is nil by usinkg `try`:
p! val.try &.upcase
#OUTPUT:
val.upcase # => "A STRING..."
val.upcase # => "A STRING..."
val.try(&.upcase) # => "A STRING..."

We managed to call upcase in all three cases because maybe_string actually returned String and we satisfied the compiler by branching in a way that our code was called only when maybe_string returned a String.

  1. In the first case, we just checked if val was truthy, because it wouldn’t be truthy if val was Nil.
  2. In the second case we used a method provided by the compiler, .nil?1, this would return true if val was Nil.
  3. And finally on the third case, we’ve used another built-in method try2. try only executes the passed code block if val is not Nil.

Exception Handling

In Crystal, the keyword raise is used to raise exceptions. When raised, the exception will unwind the call stack and it'll hopefully be caught by the programmer somewhere higher up in the method chain. If it's not caught, the program will exit with an unhandled exception error.

If you bubble up an exception with raise and catch it, the regular execution of the program will continue.

Handling exceptions with begin - rescue blocks:

arr = [] of Int8
puts "Enter an Int8 value:"
while number = gets
  number = number.strip
  if number == "" || number == "stop"
    break
  end

  # Catching exceptions in the begin block:
  begin
    arr << number.to_i8
  rescue ex
    # Reacting to the exception:
    p ex.message
    puts "The integer you've entered is bigger than Int8 capacity"
    exit
  else
    # In case where everything is good:
    p "Int8 added to the array."
    p arr
  ensure
    # Ensure block executes no matter what:
    p "Clean up with ensure block..."
  end
end

It's also possible to use a shorter syntax alternative:

def add_i8(arr, number)
  arr << number.to_i8
rescue
  p ex.message
  puts "The integer you've entered is bigger than Int8 capacity"
end

Shorter variant with the addition of ensure:

def add_i8(arr, number)
  arr << number.to_i8
rescue
  p ex.message
  puts "The integer you've entered is bigger than Int8 capacity"
ensure
  p "clean up happened."
end

A note on performance:

Using predicates like to_i? or to_f? with if blocks is much faster and less resource intensive compared to exception handling because exception handling allocates more memory. If you choose to use begin-rescue blocks, try to keep the amount of code between begin and rescue minimal.

Idiomatic Patterns

An idiomatic pattern is to return nil from an inner function, and catch an outer function which raises an exception when it detects the nil return. This exception then can be caught by the caller site.

Here's an example:

# Private implementation
def say_hi(name : String) : String | Nil
  name.size > 2 ? "Hi, #{name}" : nil
end

# Public implementation
def say_hi_public(name : String)
  msg = say_hi name
  unless msg
    raise "say_hi error: Name too short"
  else
    msg
  end
end

# Caller site
begin
  p say_hi_public "Jo"
rescue error
  p error
end

Let's rewrite the previous example using shorter syntax where possible:

# Private implementation
def say_hi(name : String) : String?
  name.size > 2 ? "Hi, #{name}" : nil
end

# Public implementation:

def say_hi_public(name : String)
  raise "say_hi error: Name too short" unless say_hi name
end

# Caller site:
def caller_site
  say_hi_public("Jo") # <Exception:say_hi error: Name too short>
rescue ex
  p ex
end

caller_site

Recursion

A common example of recursion is the factorial calculation. You can see that in action below:

def factorial(n)
  n == 0 ? 1 : n * factorial(n - 1)
end

p factorial(6) # => 720

The same recursive function can be made type safe, and can include guards for negative integers:

def factorial(n : Int32) : Int32
  if n < 0
    raise "Error, n cannot be negative"
  end
  n == 0 ? 1 : n * factorial(n - 1)
end

begin
  p factorial(6)  # => 720
  p factorial(-6) # => "Error, n cannot be negative"
rescue ex
  p ex.message
end

As discussed in the exception handling section, the begin-rescue call is rather expensive. For a better performance, you can just guard against the problem input by a plain if statement and an exit:

def factorial(n : Int32) : Int32
  if n < 0
    p "Error, n cannot be negative!"
    exit
  end
  n == 0 ? 1 : n * factorial(n - 1)
end

p factorial(6)  # => 720
p factorial(-6) # => "Error, n cannot be negative"

Of course, most of the time the exit statement will be an overkill, so you can just simply return from a problemlamitc input in a recursive function, preventing cases like infinite recursion.

Crystal CLI

Crystal Play Command

You can run a local instance of Crystal playground to run code on the browser.

Run crystal play and visit localhost:8080 on your browser.

The Playground

In the playground, you can write your code on the left-hand side and the code will automatically (800ms delay by default) compile, producing line-by-line results and type information.

The Workbook

At the root of your project (where you ran crystal play) you can create a directory named playground. All the Markdown and Crystal files in this directory will be listed at localhost:8080/workbook. On this page, you'll be able to interact with code snippets that are fenced as playground in Markdown files. You can also view and interact with .cr files.

Notes:

  1. I had to install libssl-dev and libz-dev packages for crystal play to work.
  2. Tagging code fences as "playground" instead of Crystal is not a great solution. There might be a workaround.

Recipes

Getting User Input

Getting user input from the terminal is super simple:

puts "Enter a number:"
num = gets

puts "You've entered #{num}"

Example: Add user input to an array

arr = [] of Int8

p "Enter a number between -128 to 127:"
num = gets

# Let's imagine the user entered 48
# The type of the variable num will be:
p typeof(num) # => Compile-time type: (String | Nil)
p num.class   # => Run-time type: String

# Check to make sure num is not nil
if num
  arr << num.to_i8
end

p arr

Multiple numbers:

arr = [] of Int8

p "Enter a number between -128 to 127 and hit enter:"
num = gets

while num = gets
  arr << num.to_i8
end
p arr

A Basic Web Application

In this recipe, we’ll take a look at how we can get started with a simple web application with zero dependencies. The tools in the standard library covers most of the important parts of a web application other than a router. For that, we’ll have to write very rudimentary and naive router.

Let’s start with an ECR1 template and write a basic home page:

<!-- file: home.ecr -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Home</title>
</head>
<body>
   <h1>Welcome to the Home page!</h1>
</body>
</html>

And an about page:

<!-- file: about.ecr -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>About</title>
</head>
<body>
   <h1>About Us</h1>
   <p>We use Crystal at work.</p>
</body>
</html>

Let’s define our handlers as classes that include HTTP::Handler2:

# file: handlers.cr
require "http/server"

# HomeHandler is the controller for the route:
# `GET: /`
class HomeHandler
  include HTTP::Handler

  def call(context : HTTP::Server::Context)
    context.response.content_type = "text/html"
    context.response.status_code = 200
    # Respond with an ECR template:
    ECR.embed "home.ecr", context.response
  end
end

# AboutHandler is the controller for the route:
# `GET: /about`
class AboutHandler
  include HTTP::Handler

  def call(context : HTTP::Server::Context)
    context.response.content_type = "text/html"
    context.response.status_code = 200
    # Respond with an ECR template:
    ECR.embed "about.ecr", context.response
  end
end

# ErrorNotFoundHandler is the controller for the case:
# `Error: 404 Page Not Found`
class ErrorNotFoundHandler
  include HTTP::Handler

  def call(context : HTTP::Server::Context)
    context.response.content_type = "text/html"
    context.response.status_code = 404
    # Respond with a plain string:
    context.response.print "No such route as #{context.request.path}"
  end
end

# Router is a simple router that matches a handler based
# on the request path.
class Router
  include HTTP::Handler

  def route(path : String) : HTTP::Handler
    case path
    when "/"
      HomeHandler.new
    when "/about"
      AboutHandler.new
    else
      ErrorNotFoundHandler.new
    end
  end

  def call(context : HTTP::Server::Context)
    handler = route(context.request.path)
    handler.call(context)
  end
end

And finally, let’s create an array of handlers and add a few HTTP handlers from the standard library and our custom router and run the server:

# file: server.cr
require "http/server"
require "./handlers"

HOST = "127.0.0.1"
PORT = 3000

handlers = [] of HTTP::Handler
handlers << HTTP::LogHandler.new
handlers << HTTP::ErrorHandler.new
handlers << HTTP::CompressHandler.new
handlers << Router.new

server = HTTP::Server.new(handlers)
server.bind_tcp HOST, PORT

p "Server starting to listen on http://#{HOST}:#{PORT}"
server.listen

A tiny Makefile to run our example:

# file: Makefile
.PHONY: run
run:
    crystal run server.cr

You can now run the project with make run and your server should be listening. We can then try the following:

curl localhost:3000
#OUTPUT:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Home</title>
</head>
<body>
   <h1>Welcome to the Home page!</h1>
</body>
</html>

curl localhost:3000/about
#OUTPUT:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>About</title>
</head>
<body>
   <h1>About Us</h1>
   <p>We use Crystal at work.</p>
</body>
</html>

curl localhost:3000/gibberish
#OUTPUT:

No such route as /gibberish

Ecosystem

Awesome Crystal

Algorithms and Data structures

  • bisect - Inserting values into a sorted array
  • blurhash.cr - BlurHash implementation
  • crie - Compile-time Trie
  • CrOTP - HOTP and TOTP implementation for two factor authentication
  • crystal-linked-list - Implementation of Linked List
  • crystaledge - A pure Vector Math library
  • crystalg - A Generic Algorithm Library
  • crystalline - A collection of containers and algorithms
  • csuuid - A Chronologically Sortable UUID
  • edits.cr - Collection of edit distance algorithms
  • fzy - A Crystal port of awesome Fzy project fuzzy finder algorithm
  • Goban - A fast and efficient QR Code implementation
  • graphlb - Collection of graph datastructure and algorithms
  • haversine - An Implementation of the Haversine formula
  • kd_tree - An implementation of "K-Dimensional Tree" and "N-Nearest Neighbors"
  • ksuid.cr - K-Sortable Globally Unique IDs
  • markov - Build Markov Chains and run Markov Processes
  • multiset.cr - Implementation of a multiset
  • qr-code - QR Code generator
  • radix - Radix Tree implementation
  • s2_cells - S2 Geometry for spatial indexing
  • secure-remote-password - SRP-6a protocol for authentication over an insecure network
  • splay_tree_map - Splay Tree implementation that conforms to the Hash ducktype

Blockchain

  • Axentro - A custom blockchain platform
  • Cocol - A minimal blockchain testbed
  • secp256k1.cr - Elliptic curve used in the public-private-key cryptography

C bindings

Caching

CLI Builders

  • admiral - A robust DSL for writing command line interfaces
  • Athena Console - Allows for the creation of CLI based commands
  • clicr - A simple declarative command line interface builder
  • clim - Slim command line interface builder
  • Cling - A modular, non-macro-based command line interface library
  • commander - Command-line interface builder
  • Keimeno - A lightweight text user interface library in Crystal
  • OptionParser - command-line options processing (Crystal stdlib)
  • Phreak - A highly flexible Crystal CLI builder in the style of OptionParser

CLI Utils

  • climate - Tiny tool to make your CLI output 🌈 coloured
  • coin - Command-line application that performs currency conversion via the Fixer API
  • cride - A light CLI text editor/IDE
  • git-repository - A git cli wrapper querying and cloning remote repositories with minimal data transfer
  • hetzner-k3s - A CLI tool to quickly create and manage Kubernetes clusters in Hetzner Cloud
  • lff - Simple and straightforward large files finder utility in command line
  • meet - Start a jitsi meeting quickly from the comfort of your command line
  • oq - A performant, and portable jq wrapper to facilitate the consumption and output of formats other than JSON; using jq filters to transform the data
  • progress_bar.cr - A simple and customizable progress bar
  • tablo - A flexible terminal table generator
  • tallboy - Generate ASCII character tables with support for spanning cells over multiple columns

Code Analysis and Metrics

  • ameba - A static code analysis tool
  • linguist.cr - Using multiple ways to find programming language used in files, based on Github's Linguist

Compression

  • Crystar - Readers and writers of Tar archive format
  • Gzip - readers and writers of gzip format (Crystal stdlib)
  • polylines.cr — compression of series of coordinates
  • snappy - Snappy compression format reader/writer for Crystal
  • Zip - readers and writers of zip format (Crystal stdlib)
  • Zlib - readers and writers of zlib format (Crystal stdlib)
  • zstd.cr - Bindings for Zstandard compression library

Configuration

  • cr-dotenv - Loads .env file
  • Envy - Load environment variables from YAML
  • envyable - A simple YAML to ENV config loader
  • habitat - Type safe configuration for your classes and modules
  • totem - Load and parse a configuration in JSON, YAML, dotenv formats

Converters

  • base62.cr - Base62 encoder/decoder, well suited for url-shortening
  • crunits - Tool for converting units of measure (miles to kilometers, celsius to fahrenheit etc)
  • money - Handling money and currency conversion with ease (almost complete port of RubyMoney)
  • sass.cr - Compile SASS/SCSS to CSS (libsass binding)

Cryptography

  • cmac - Crystal implementation of Cipher-based Message Authentication Code (CMAC)
  • ed25519 - the Ed25519 elliptic curve public-key signature system described in [RFC 8032]
  • monocypher.cr - Crystal wrapper for the Monocypher crypto library
  • sodium.cr - Crystal wrapper for the libsodium crypto API

Data Formats

  • BinData - Binary data parser helper with an ASN.1 parser
  • config.cr - Easy to use configuration format parser
  • crinder - Class based json renderer
  • Crystalizer - (De)serialize any Crystal object; supporting JSON, YAML, and Byte formats out of the box
  • CSV - parsing and generating for comma-separated values (Crystal stdlib)
  • front_matter.cr - Separates a files front matter from its content
  • geoip2.cr - GeoIP2 reader
  • HAR - HAR (HTTP Archive) parser
  • INI - INI file parser (Crystal stdlib)
  • JSON - parsing and generating JSON documents (Crystal stdlib)
  • json-schema - convert JSON serializable classes into a JSON Schema representation
  • JSON::OnSteroids - handle and mutate JSON document easily
  • maxminddb.cr - MaxMindDB reader
  • toml.cr - TOML parser
  • XML - parsing and generating XML documents (Crystal stdlib)
  • YAML - parsing and generating YAML documents (Crystal stdlib)

Data Generators

  • faker - A library for generating fake data
  • hashids.cr - A library to generate YouTube-like ids from one or many numbers
  • prime - A prime number generator

Database Drivers/Clients

Database Tools

  • migrate - A simpler database migration tool with transactions

Debugging

  • backtracer.cr - Shard aiming to assist with parsing backtraces into a structured form
  • debug.cr - debug!(…) macro for pp-style debugging

Dependency Injection

  • Athena Dependency Injection - Robust dependency injection service container framework
  • Crystal-DI - Lightweight DI Container
  • HardWire - A compile-time non-intrusive dependency injection system
  • syringe - A simple and basic dependency injection shard for crystal

Email

Environment Management

Examples and funny stuff

Framework Components

  • Athena Event Dispatcher - A Mediator and Observer pattern event library
  • Athena Negotiation - Framework agnostic content negotiation library
  • device_detector - Shard for detect device by user agent string
  • Exception Page - An exceptional exception page for Crystal web libraries and frameworks
  • graphql - Type-safe GraphQL server implementation
  • graphql-crystal - GraphQL implementation
  • kemal-session - Session handler for Kemal
  • mochi - Authentication shard inspired by Devise supporting: Authenticable, Confirmable, Invitable & more
  • motion.cr - Object oriented frontend library for Amber
  • multi-auth - Standardized multi-provider OAuth2 authentication (inspired by omniauth)
  • praetorian - Minimalist authorization library inspired by Pundit
  • Shield - Comprehensive security for Lucky framework
  • shrine.cr - File Attachment toolkit for Crystal applications. Heavily inspired by Shrine for Ruby
  • tourmaline - Telegram bot framework with an API loosely based on telegraf.js

Game Development

GUI Development

HTML Builders

  • blueprint - Write reusable and testable HTML templates in plain Crystal
  • form_builder.cr - Dead simple HTML form builder for Crystal with built-in support for many popular UI libraries such as Bootstrap
  • to_html - The fastest HTML builder engine for Crystal
  • Water - A library for writing HTML in plain Crystal

HTML/XML Parsing

HTTP

Image processing

  • celestine - Create SVG images using a DSL
  • ffmpeg - FFmpeg bindings that works with StumpyPNG to extract frames
  • Pluto - A fast and convenient image processing library
  • stumpy_png - Read and write PNG images

Implementations/Compilers

  • charly - Charly Programming Language
  • cltk - A crystal port of the Ruby Language Toolkit
  • crisp - Lisp dialect implemented with Crystal
  • LinCAS-lang - A programming language for scientific computation
  • mint-lang - A refreshing programming language for the front-end web
  • myst-lang - A practical, dynamic language designed to be written and understood as easily and efficiently as possible
  • novika - A free-form, moldable, interpreted programming language
  • runic-lang - In-design toy language

Internationalization

  • crystal-i18n - An internationalization library inspired by Ruby-I18n
  • i18n.cr - Internationalization shard
  • Lens - A multiformat internationalization (i18n) shard for Crystal. Supports Gettext, Ruby YAML, etc.
  • Rosetta - A blazing fast internationalization (i18n) library with compile-time key lookup supporting YAML and JSON formats

Logging and monitoring

Machine Learning

  • ai4cr - Artificial Intelligence (based on https://github.com/SergioFierens/ai4r)
  • Cadmium - NLP library based heavily on natural
  • crystal-fann - FANN (Fast Artifical Neural Network) binding
  • mxnet.cr - Bindings for MXNet
  • shainet - SHAInet (Neural Network in pure crystal)

Markdown/Text Processors

  • markd - Yet another markdown parser built for speed, Compliant to CommonMark specification

Misc

  • aasm.cr - Easy to use finite state machine for Crystal classes
  • any_hash.cr - Recursive Hash with better JSON::Any included
  • anyolite - Full mruby interpreter with simple bindings, allowing for easy scripting support in projects
  • burocracia.cr - burocracia.cr the dependecyless shard to validate, generate and format Brazilian burocracias such as CPF, CNPJ and CEP
  • callbacks - Expressive callbacks module
  • circuit_breaker - Implementation of the circuit breaker pattern
  • CrSignals - Signals/slots notification library
  • crystal-binary_parser - Binary parser
  • crystal-web-framework-stars - Web frameworks for Crystal, most starred on Github
  • crz - Functional programming library
  • defined - macros for conditional compilation based on constant definitions, version requirements, or environment variable settings
  • emoji.cr - Emoji library
  • gphoto2-web.cr - Web API for libgphoto2
  • immutable - Implementation of thread-safe, persistent, immutable collections
  • iterm2 - Display images within the terminal using the ITerm2 Inline Images Protocol
  • monads - Monad implementation
  • observable - Implementation of the observer pattern
  • pinger - Ping IP addresses and DNS entries without requiring sudo
  • port_midi - Crystal C bindings for the PortMIDI cross-platform MIDI I/O library
  • retriable.cr - Simple DSL to retry failed code blocks
  • serf-handler.cr - Framework for building Serf handlers, with a suite of useful builtin capabilities
  • simple_retry - Simple tool for retrying failed code blocks
  • sslscan.cr - Crystal shard wrapping the rbsec/sslscan utility
  • version_tools - Version-dependent behaviour, specified at compile-time
  • wafalyzer - Web Application Firewall (WAF) Detector - shard + cli
  • zaru_crystal - Filename sanitization

Network Protocols

Networking

ORM/ODM Extensions

  • avram - A database wrapper for reading, writing, and migrating Postgres databases
  • clear - ORM specialized to PostgreSQL only but with advanced features
  • crecto - Database wrapper, based on Ecto
  • granite - ORM for Postgres, Mysql, Sqlite
  • jennifer.cr - Active Record pattern implementation with flexible query chainable builder and migration system
  • rethinkdb-orm - ORM for RethinkDB / RebirthDB

Package Management

  • shards - Dependency manager for the Crystal

Processes and Threads

  • await_async - Add keywords await & async in Crystal Lang
  • concurrent.cr - Simplified concurrency using streams/pipelines, waitgroups, semaphores, smores and more
  • neph - A modern command line job processor that can execute jobs concurrently
  • promise - A Promise implementation with type inference
  • werk - Dead simple task runner with concurrent support, ideal for local CI

Project Generators

  • crystal_lib - Automatic binding generator for native libraries
  • fez - A Kemal application generator
  • libgen - Automatic bindings generator configured using JSON/YAML files

Queues and Messaging

  • mosquito - Redis backed periodic and ad hoc job processing
  • NATS.io - NATS client
  • sidekiq.cr - Simple, efficient job processing

Routing

  • orion - A minimal, rails-esque routing library
  • router.cr - Minimum but powerful http router for HTTP::Server

Scheduling

  • crystime - Advanced time, calendar, schedule, and remind library
  • schedule.cr - Run periodic tasks
  • tasker - A high precision scheduler including timezone aware cron jobs

Science and Data analysis

  • alea - Repeatable sampling, CDF and other utilities to work with probability distributions
  • ishi - Graph plotting package with a small API and sensible defaults powered by gnuplot
  • linalg - Linear algebra library inspired by MATLAB and SciPy.linalg
  • num.cr - Numerical computing library supporting N-Dimensional data
  • predict.cr - Satellite prediction library using the sgp4 model
  • quartz - Modeling and simulation framework
  • hermes - Data Mapper pattern implementation for ElastiSearch

Serverless Computing

  • crystal_openfaas - Template to enable crystal as first class citizens in OpenFaaS
  • secrets-env - Extends ENV module to read values injected by docker / kubernetes secrets and other orchestration tools

System

  • baked_file_system - Virtual file system implementation
  • hardware - Get CPU, Memory and Network informations of the running OS and its processes

Task management

  • cake - Production-ready Make-like utility tool
  • sam - Another one Rake-like task manager with namespacing and arguments system

Template Engine

Testing

  • Athena Spec - Common/helpful Spec compliant testing utilities
  • crotest - A tiny and simple test framework
  • crytic - Mutation testing framework
  • hashr - A tiny class makes test on JSON response easier
  • LuckyFlow - Automated browser tests similar to Capybara
  • mass-spec - Web API testing library
  • microtest - Small opinionated testing library focusing on power asserts
  • minitest.cr - Library for unit tests and assertions
  • mocks.cr - Mocking library for Crystal
  • Spec - spec framework (Crystal stdlib)
  • spectator - Feature rich spec framework that uses the modern expect syntax
  • timecop.cr - Library for mocking with Time.now. Inspired by the timecop ruby gem
  • vcr - A HTTP capture and replay implementation for crystal
  • webdriver_pump - Page Object library. Inspired by Ruby's WatirPump
  • webmock.cr - Library for stubbing HTTP::Client requests

Third-party APIs

Validation

Web Frameworks

  • amber - Open source efficient and cohesive web application framework
  • Athena - A web framework comprised of reusable, independent components
  • grip - The microframework for writing powerful web applications
  • kemal - Lightning Fast, Super Simple web framework. Inspired by Sinatra
  • lucky - Catch bugs early, forget about most performance issues, and spend more time on code instead of debugging and writing tests
  • marten - A web framework that makes building web applications easy, productive, and fun
  • runcobo - An api framework with simple, intuitive and consistent DSL, using jbuilder to render json
  • Shivneri - Component based MVC web framework for crystal targeting good code structures, modularity & performance
  • spider-gazelle - A Rails esque web framework with a focus on speed and extensibility

Community

Unofficial

Resources

Official Documentation Translations

Services and Apps

  • carc.in - A web service that runs your code and displays the result
  • Crank - A Procfile-based application manager (like Foreman)
  • cry - Ability to execute crystal code in a fashion similar to Ruby's pry edit
  • Crystal [ANN] - Announce new project, blog post, version update or any other Crystal work
  • DeBot - IRC bot written in Crystal
  • icr - Interactive console for Crystal (like IRB for Ruby)
  • Invidious - Invidious is an alternative front-end to YouTube
  • mpngin - A URL shortener with simple stats
  • procodile - Run processes in the background (and foreground) on Mac & Linux from a Procfile (for production and/or development environments)
  • quicktype - Generate models and serializers from JSON, JSON Schema, GraphQL, and TypeScript
  • shards.info - Web service that lists all repositories on GitHub that have Crystal code in them. The sources are available on GitHub

Tools

DevOps

  • ansible-crystal - Ansible playbook for installing crystal
  • DPPM - An easy, universal way to install and manage applications as packages (mostly Linux)

Editor Plugins

LSP Language Server Protocol Implementations

  • crystalline - Crystalline is an implementation of the Language Server Protocol written in and for the Crystal Language
  • scry - Code analysis server for Crystal implementing the Language Server Protocol

Shell plugins

  • crun - Crystal Run : shebang wrapper for Crystal
  • crystal-zsh - .oh-my-zsh plugin

License

All of the original content that I've authored is licensed CC-BY-SA 4.0. Other sections might be copied and remixed from other open source documentation, they might be licensed differently, see Credits section.

You can find a copy of the CC-BY-SA 4.0 license below:

Attribution-ShareAlike 4.0 International

=======================================================================

Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.

Using Creative Commons Public Licenses

Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.

     Considerations for licensors: Our public licenses are
     intended for use by those authorized to give the public
     permission to use material in ways otherwise restricted by
     copyright and certain other rights. Our licenses are
     irrevocable. Licensors should read and understand the terms
     and conditions of the license they choose before applying it.
     Licensors should also secure all rights necessary before
     applying our licenses so that the public can reuse the
     material as expected. Licensors should clearly mark any
     material not subject to the license. This includes other CC-
     licensed material, or material used under an exception or
     limitation to copyright. More considerations for licensors:
    wiki.creativecommons.org/Considerations_for_licensors

     Considerations for the public: By using one of our public
     licenses, a licensor grants the public permission to use the
     licensed material under specified terms and conditions. If
     the licensor's permission is not necessary for any reason--for
     example, because of any applicable exception or limitation to
     copyright--then that use is not regulated by the license. Our
     licenses grant only permissions under copyright and certain
     other rights that a licensor has authority to grant. Use of
     the licensed material may still be restricted for other
     reasons, including because others have copyright or other
     rights in the material. A licensor may make special requests,
     such as asking that all changes be marked or described.
     Although not required by our licenses, you are encouraged to
     respect those requests where reasonable. More considerations
     for the public:
    wiki.creativecommons.org/Considerations_for_licensees

=======================================================================

Creative Commons Attribution-ShareAlike 4.0 International Public
License

By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.


Section 1 -- Definitions.

  a. Adapted Material means material subject to Copyright and Similar
     Rights that is derived from or based upon the Licensed Material
     and in which the Licensed Material is translated, altered,
     arranged, transformed, or otherwise modified in a manner requiring
     permission under the Copyright and Similar Rights held by the
     Licensor. For purposes of this Public License, where the Licensed
     Material is a musical work, performance, or sound recording,
     Adapted Material is always produced where the Licensed Material is
     synched in timed relation with a moving image.

  b. Adapter's License means the license You apply to Your Copyright
     and Similar Rights in Your contributions to Adapted Material in
     accordance with the terms and conditions of this Public License.

  c. BY-SA Compatible License means a license listed at
     creativecommons.org/compatiblelicenses, approved by Creative
     Commons as essentially the equivalent of this Public License.

  d. Copyright and Similar Rights means copyright and/or similar rights
     closely related to copyright including, without limitation,
     performance, broadcast, sound recording, and Sui Generis Database
     Rights, without regard to how the rights are labeled or
     categorized. For purposes of this Public License, the rights
     specified in Section 2(b)(1)-(2) are not Copyright and Similar
     Rights.

  e. Effective Technological Measures means those measures that, in the
     absence of proper authority, may not be circumvented under laws
     fulfilling obligations under Article 11 of the WIPO Copyright
     Treaty adopted on December 20, 1996, and/or similar international
     agreements.

  f. Exceptions and Limitations means fair use, fair dealing, and/or
     any other exception or limitation to Copyright and Similar Rights
     that applies to Your use of the Licensed Material.

  g. License Elements means the license attributes listed in the name
     of a Creative Commons Public License. The License Elements of this
     Public License are Attribution and ShareAlike.

  h. Licensed Material means the artistic or literary work, database,
     or other material to which the Licensor applied this Public
     License.

  i. Licensed Rights means the rights granted to You subject to the
     terms and conditions of this Public License, which are limited to
     all Copyright and Similar Rights that apply to Your use of the
     Licensed Material and that the Licensor has authority to license.

  j. Licensor means the individual(s) or entity(ies) granting rights
     under this Public License.

  k. Share means to provide material to the public by any means or
     process that requires permission under the Licensed Rights, such
     as reproduction, public display, public performance, distribution,
     dissemination, communication, or importation, and to make material
     available to the public including in ways that members of the
     public may access the material from a place and at a time
     individually chosen by them.

  l. Sui Generis Database Rights means rights other than copyright
     resulting from Directive 96/9/EC of the European Parliament and of
     the Council of 11 March 1996 on the legal protection of databases,
     as amended and/or succeeded, as well as other essentially
     equivalent rights anywhere in the world.

  m. You means the individual or entity exercising the Licensed Rights
     under this Public License. Your has a corresponding meaning.


Section 2 -- Scope.

  a. License grant.

       1. Subject to the terms and conditions of this Public License,
          the Licensor hereby grants You a worldwide, royalty-free,
          non-sublicensable, non-exclusive, irrevocable license to
          exercise the Licensed Rights in the Licensed Material to:

            a. reproduce and Share the Licensed Material, in whole or
               in part; and

            b. produce, reproduce, and Share Adapted Material.

       2. Exceptions and Limitations. For the avoidance of doubt, where
          Exceptions and Limitations apply to Your use, this Public
          License does not apply, and You do not need to comply with
          its terms and conditions.

       3. Term. The term of this Public License is specified in Section
          6(a).

       4. Media and formats; technical modifications allowed. The
          Licensor authorizes You to exercise the Licensed Rights in
          all media and formats whether now known or hereafter created,
          and to make technical modifications necessary to do so. The
          Licensor waives and/or agrees not to assert any right or
          authority to forbid You from making technical modifications
          necessary to exercise the Licensed Rights, including
          technical modifications necessary to circumvent Effective
          Technological Measures. For purposes of this Public License,
          simply making modifications authorized by this Section 2(a)
          (4) never produces Adapted Material.

       5. Downstream recipients.

            a. Offer from the Licensor -- Licensed Material. Every
               recipient of the Licensed Material automatically
               receives an offer from the Licensor to exercise the
               Licensed Rights under the terms and conditions of this
               Public License.

            b. Additional offer from the Licensor -- Adapted Material.
               Every recipient of Adapted Material from You
               automatically receives an offer from the Licensor to
               exercise the Licensed Rights in the Adapted Material
               under the conditions of the Adapter's License You apply.

            c. No downstream restrictions. You may not offer or impose
               any additional or different terms or conditions on, or
               apply any Effective Technological Measures to, the
               Licensed Material if doing so restricts exercise of the
               Licensed Rights by any recipient of the Licensed
               Material.

       6. No endorsement. Nothing in this Public License constitutes or
          may be construed as permission to assert or imply that You
          are, or that Your use of the Licensed Material is, connected
          with, or sponsored, endorsed, or granted official status by,
          the Licensor or others designated to receive attribution as
          provided in Section 3(a)(1)(A)(i).

  b. Other rights.

       1. Moral rights, such as the right of integrity, are not
          licensed under this Public License, nor are publicity,
          privacy, and/or other similar personality rights; however, to
          the extent possible, the Licensor waives and/or agrees not to
          assert any such rights held by the Licensor to the limited
          extent necessary to allow You to exercise the Licensed
          Rights, but not otherwise.

       2. Patent and trademark rights are not licensed under this
          Public License.

       3. To the extent possible, the Licensor waives any right to
          collect royalties from You for the exercise of the Licensed
          Rights, whether directly or through a collecting society
          under any voluntary or waivable statutory or compulsory
          licensing scheme. In all other cases the Licensor expressly
          reserves any right to collect such royalties.


Section 3 -- License Conditions.

Your exercise of the Licensed Rights is expressly made subject to the
following conditions.

  a. Attribution.

       1. If You Share the Licensed Material (including in modified
          form), You must:

            a. retain the following if it is supplied by the Licensor
               with the Licensed Material:

                 i. identification of the creator(s) of the Licensed
                    Material and any others designated to receive
                    attribution, in any reasonable manner requested by
                    the Licensor (including by pseudonym if
                    designated);

                ii. a copyright notice;

               iii. a notice that refers to this Public License;

                iv. a notice that refers to the disclaimer of
                    warranties;

                 v. a URI or hyperlink to the Licensed Material to the
                    extent reasonably practicable;

            b. indicate if You modified the Licensed Material and
               retain an indication of any previous modifications; and

            c. indicate the Licensed Material is licensed under this
               Public License, and include the text of, or the URI or
               hyperlink to, this Public License.

       2. You may satisfy the conditions in Section 3(a)(1) in any
          reasonable manner based on the medium, means, and context in
          which You Share the Licensed Material. For example, it may be
          reasonable to satisfy the conditions by providing a URI or
          hyperlink to a resource that includes the required
          information.

       3. If requested by the Licensor, You must remove any of the
          information required by Section 3(a)(1)(A) to the extent
          reasonably practicable.

  b. ShareAlike.

     In addition to the conditions in Section 3(a), if You Share
     Adapted Material You produce, the following conditions also apply.

       1. The Adapter's License You apply must be a Creative Commons
          license with the same License Elements, this version or
          later, or a BY-SA Compatible License.

       2. You must include the text of, or the URI or hyperlink to, the
          Adapter's License You apply. You may satisfy this condition
          in any reasonable manner based on the medium, means, and
          context in which You Share Adapted Material.

       3. You may not offer or impose any additional or different terms
          or conditions on, or apply any Effective Technological
          Measures to, Adapted Material that restrict exercise of the
          rights granted under the Adapter's License You apply.


Section 4 -- Sui Generis Database Rights.

Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:

  a. for the avoidance of doubt, Section 2(a)(1) grants You the right
     to extract, reuse, reproduce, and Share all or a substantial
     portion of the contents of the database;

  b. if You include all or a substantial portion of the database
     contents in a database in which You have Sui Generis Database
     Rights, then the database in which You have Sui Generis Database
     Rights (but not its individual contents) is Adapted Material,

     including for purposes of Section 3(b); and
  c. You must comply with the conditions in Section 3(a) if You Share
     all or a substantial portion of the contents of the database.

For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.


Section 5 -- Disclaimer of Warranties and Limitation of Liability.

  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.

  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.

  c. The disclaimer of warranties and limitation of liability provided
     above shall be interpreted in a manner that, to the extent
     possible, most closely approximates an absolute disclaimer and
     waiver of all liability.


Section 6 -- Term and Termination.

  a. This Public License applies for the term of the Copyright and
     Similar Rights licensed here. However, if You fail to comply with
     this Public License, then Your rights under this Public License
     terminate automatically.

  b. Where Your right to use the Licensed Material has terminated under
     Section 6(a), it reinstates:

       1. automatically as of the date the violation is cured, provided
          it is cured within 30 days of Your discovery of the
          violation; or

       2. upon express reinstatement by the Licensor.

     For the avoidance of doubt, this Section 6(b) does not affect any
     right the Licensor may have to seek remedies for Your violations
     of this Public License.

  c. For the avoidance of doubt, the Licensor may also offer the
     Licensed Material under separate terms or conditions or stop
     distributing the Licensed Material at any time; however, doing so
     will not terminate this Public License.

  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
     License.


Section 7 -- Other Terms and Conditions.

  a. The Licensor shall not be bound by any additional or different
     terms or conditions communicated by You unless expressly agreed.

  b. Any arrangements, understandings, or agreements regarding the
     Licensed Material not stated herein are separate from and
     independent of the terms and conditions of this Public License.


Section 8 -- Interpretation.

  a. For the avoidance of doubt, this Public License does not, and
     shall not be interpreted to, reduce, limit, restrict, or impose
     conditions on any use of the Licensed Material that could lawfully
     be made without permission under this Public License.

  b. To the extent possible, if any provision of this Public License is
     deemed unenforceable, it shall be automatically reformed to the
     minimum extent necessary to make it enforceable. If the provision
     cannot be reformed, it shall be severed from this Public License
     without affecting the enforceability of the remaining terms and
     conditions.

  c. No term or condition of this Public License will be waived and no
     failure to comply consented to unless expressly agreed to by the
     Licensor.

  d. Nothing in this Public License constitutes or may be interpreted
     as a limitation upon, or waiver of, any privileges and immunities
     that apply to the Licensor or You, including from the legal
     processes of any jurisdiction or authority.


=======================================================================

Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.

Creative Commons may be contacted at creativecommons.org.

Credits

  • Crystal Book (Official Reference/Docs) - CC0
  • Awesome Crystal - MIT