I love Ruby. There…I said it. I am a Ruby fan. Ruby is expressive, powerful and elegant. It’s core mantra of “Achieving more with less” and it’s freedom and flexibility make it, in my opinion, one of the best languages out there. But there are a couple of downsides:

  • Ruby is interpreted
  • Ruby doesn’t force you to handle nil values
  • Ruby doesn’t like C interop (you can do it…but it’s ugly as hell)

So recently, I discovered a language that really is “Fast as C, slick as Ruby”:

This post is a bit different from the others in my Summer Series. I don’t really have an end result I want to achieve here other than to get to know the language, so see this more as a showcase than a tutorial or something like that…

What is Crystal? 🔮

Well…Crystal is a compiled, object-oriented, duck-typed programming language that aims to have the elegance of Ruby but with the speed of something like C. It first appeared in 2014 and, as of today, is still in pre-release stage which definitely makes it “new technology”. In this post, I’ll try to teach myself the core concepts of Crystal, write some Crystal code and try to really get to know the language.

So let’s start exploring!

Installing the compiler

Unfortunately, Crystal doesn’t work natively on Windows yet. Therefore, we have to make use of WSL. I am using Ubuntu 18.04 as my WSL instance, where Crystal is supported out of the box.

curl -sSL https://dist.crystal-lang.org/apt/setup.sh | sudo bash
sudo apt install crystal

In addition to that, the language reference recommends installing some other tools. I won’t be installing all of them, instead I just picked 4 packages I think I’ll need at some point.

sudo apt install libssl-dev      # for using OpenSSL
sudo apt install libyaml-dev     # for using YAML
sudo apt install libgmp-dev      # for using Big numbers
sudo apt install libreadline-dev # for using Readline

Hello world!

Let’s start with a simple “Hello world!”. In Crystal, you print with the puts statement. Which means that a “Hello world!” is as simple as:

puts "Hello world!"

Crystal also supports string interpolation, so we can do something like:

puts "Hello, world! The time is #{Time.now}!"

Now that we have that figured out, let’s move on to something more interesting.

OOP

Classes in Crystal are declared like so:

class Animal
end

In Crystal, an object has a type and responds to some methods (the only way to interact with objects in Crystal). That means that for every property we want to have in our class, we need to declare a function to access these properties. The information of these properties is stored in instance variables, prefixed with @. Also, constructors are always a method with the name initialize.

class Animal
  def initialize(name : String, age : Int8)
    @name = name
    @age = age
  end
end

Technically, we could totally access these instance variables directly like so:

alex = Animal.new("Alex", 2)
puts alex.@name #=> Alex

But to my understanding this is to be avoided (although I couldn’t find out why). Therefore, we need to introduce getters.

class Animal
  def initialize(name : String, age : Int8)
    @name = name
    @age = age
  end

  def name
    @name #Return is implicit
  end

  def age
    @age
  end
end

So now we can call name.

alex = Animal.new("Alex", 2)
puts alex.name #=> Alex

The example above can be compressed by declaring the instance variables directly in the initialize parameters. Also, we can use macros from the Crystal standard library to make dealing with properties way easier.

class Animal
  property age : Int8 #We can modify this
  getter name : String #We can't modify this

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

Using this class could look like so:

alex = Animal.new("Alex", 2)
puts alex.name #=> Alex

puts alex.age #=> 2
alex.age = 10
puts alex.age #=> 10

We could also define methods for this class. When calling methods, one can omit the braces.

def to_json
  "{\"name\": \"#{name}\",\"age\": #{age}}"
end
puts alex.to_json

Inheritance

Next, let’s try some inheritance. To do so, I changed our Animal from a class to an abstract class and introduced an abstract method we’ll make use of.

abstract class Animal
  property age : Int8
  getter name : String

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

  abstract def do_talk

  def talk
    "#{name} says '#{do_talk}'"
  end
end

We can now inherit from this class like so:

class Dog < Animal
  def do_talk
    "Woof!"
  end
end

class Cat < Animal
  def do_talk
    "Miau"
  end
end

And use these classes like so:

john = Dog.new "John", 2
puts john.talk #=> John says 'Woof!'

mia = Cat.new "Mia", 4
puts mia.talk #=> Mia says 'Miau'

Blocks

This language feature allows for some really nice code. Basically, you can pass on a block to a function. The cool thing is: those blocks are not lambdas! They’re actually being inserted into the function that you’re calling. Now…I don’t exactly know how this black magic works…but I do quite love it.

We don’t actually need to give the compiler any specifics about the block because it can figure out most things on its own.

def executeBlock
  yield "the other side"
end

executeBlock do |x|
  puts "Hello from #{x}"
end

It’s a bit complicated to wrap your head around this at first, but basically, this compiles to:

x = "the other side"
puts "Hello from #{x}"

The standard library does include a times method but let’s see if we can write our own…

def count(i)
  c = 0
  while c < i
    yield c
    c += 1
  end
end

count 5 do |i|
  puts "The current number is: #{i}"
end

And this would basically compile to:

c = 0
while c < 5
  puts "The current number is: #{c}"
  c += 1
end

Union types

Crystal also supports something called a “union type”. This allows for a variable to have multiple datatypes at the same time. The cool thing about this is that you don’t even have to type out these union types. The compiler will pick these up for you (but that also means that the compiler will force you to deal with said types).

a = 10
b = "Hello"

c = rand < 0.5 ? a : b

Now, in every other (statically typed) language we obviously expect this to not even compile. But in Crystal, it does. Let’s append a typeof.

puts typeof(c) #=> (Int32 | String)

なに!? 🤯

What kind of black magic is this?? Well…not so much black magic but a really smart compiler, actually…typeof returns a union type because the compiler figured out that rand < 0.5 ? a : b could’ve resulted in an Int32 or a String!

This is nice and all…but not really what the feature was meant for. This language feature is way more useful in a scenario like this:

def getDataOrNil
  rand < 0.5 ? {firstname: "John", lastname: "Doe"} : nil
end

john = getDataOrNil
john_firstname, john_lastname = john[:firstname], john[:lastname]

Trying to compile this yields the following error message:

Which means that we have to deal with the possible null value like so:

if john != nil
  john = john.as(NamedTuple(firstname: String, lastname: String))
  john_firstname, john_lastname = john[:firstname], john[:lastname]
    
  puts john_firstname
  puts john_lastname
else
  puts "No data found!"
end

Simple HTTP server

Now that we have all of that (with all of that I mean blocks and union types), more or less, figured out, we can try building a simple HTTP server with Crystals built-in HTTP library. I tried to construct a simple HTTP server that just does a simple echo.

require "http/server"

server = HTTP::Server.new do |ctx|
  ctx.response.content_type = "text/plain"
  ctx.response.print(ctx.request.body != nil ? ctx.request.body.as(IO).gets : "Hello!")
  #ctx.request.body can be nil so we have to manually handle this here
end

address = server.bind_tcp 8080
server.listen

Concurrency model

So the concurrency model of Crystal is truly something else. At first, it might look a bit complicated but really it’s quite easy (easier than Rusts concurrency model anyways…) and incredibly powerful to the point where I don’t recall seeing something as powerful in any other modern language (except Rust and Go, of course). Unfortunately, Crystal does not yet have a model to manage parallelism (parallelism ≠ concurrency).


Note ⚠️

A concurrent system is one that can be in charge of many tasks, although not necessarily it is executing them at the same time. You can think of yourself being in the kitchen cooking: you chop an onion, put it to fry, and while it’s being fried you chop a tomato, but you are not doing all of those things at the same time: you distribute your time between those tasks. Parallelism would be to stir fry onions with one hand while with the other one you chop a tomato.

From the Crystal language reference


require "http/client"

spawn do
  response = HTTP::Client.get "http://www.example.com"
  response.body.lines.each do |line|
    puts line
  end
end

spawn do
  5.times do
    puts "Hello!"
  end
end

sleep

Here we spawn two fibers (a fiber is a lightweight execution unit). The first issues an HTTP Get request, the second one prints “Hello!” five times. These fibers are put into an event queue. When the first fiber reaches HTTP::Client.get, it knows that it has to wait for a response. Therefore, the event queue proceeds to the second fiber and prints “Hello!” five times.

We could also spawn a call directly.

5.times do
  spawn puts "Hi!"
end

sleep

Here, we create five fibers that will all just call puts "Hi!".

Of course, these fibers can also communicate with the main thread via a channel.

channel = Channel(String).new

spawn do
  channel.send("Hello")
  sleep 0.5 #Delay the channel from sending "World!"
  channel.send("World!")
end

value = channel.receive
puts value #=> Hello

value = channel.receive #This blocks the main thread and wait untils something is sent on this channel
puts value #=> World!

Conclusion

I freaking LOVE this language. Sure, it’s a bit on the immature side. Many things will change, there’s not a lot of IDE support, the documentation only gets like a 6/10 and some language features are really hard to wrap your head around. But the performance, development speed and “fun factor” of Crystal are incredible. I will definitely use Crystal a lot more in the future and with a bit of maturing, it for sure is a candidate for my next favorite programming language.

And as always…code can be found on GitHub.