Crystal - the Ruby you've never heard of
Tags: ruby, crystal, compiled
Published: 2017-09-18 12:19:29

---
title: "Crystal - the Ruby you've never heard of"
published: true
description: A look into a fresh, new and promising language
tags: ruby, crystal, compiled
cover_image: https://thepracticaldev.s3.amazonaws.com/i/1nw50j5l755h5d2mbsmm.png
---


# What?
[Crystal](https://crystal-lang.org/) is a new, elegant, multi-paradigm programming language that is productive and fast. It has ~~Ruby's~~ a Ruby-inspired syntax and compiles to native code. It is actually unreal how similar to Ruby this language looks like.
This language combines efficient code with developer productivity, adds full OOP, a great concurrency model and a compiler that holds your hand.

This article is meant to give you a short overview, a direct performance comparison to Ruby and show some things that set it apart. It is advised you know at least some Ruby before continuing on reading.


# Starting with the fun stuff - a performant example
Let's actually get a feel as to how performant Crystal is.
I wrote an [AA Tree](https://en.wikipedia.org/wiki/AA_tree) in both [Crystal](https://github.com/Enether/crystal-aa-tree/blob/master/AA_Tree.cr) and [Ruby](https://github.com/Enether/crystal-aa-tree/blob/ruby-conversion/AA_Tree.rb).
*Note: Code quality might not be top-notch. Some lines of Crystal code are intentionally written more explicitly*

We are going to be running this code to benchmark each implementation:
```ruby
elements_count = ARGV[0].to_i  # first command line argument
root = AANode.new(value: elements_count, level: 1)
tree = AATree.new(root)

start = Time.now

elements_count.times do |num|
  raise Exception.new("Tree should not contain #{num}") if tree.contains?(num)
  tree.add(num)
  raise Exception.new("Tree should contain #{num}") unless tree.contains?(num)
end

elements_count.times do |num|
  raise Exception.new("Tree should contain #{num}") unless tree.contains?(num)
  tree.remove(num)
  raise Exception.new("Tree should not contain #{num}") if tree.contains?(num)
end

puts "Time it took: #{Time.now - start} seconds."
```
What this essentially does is it adds numbers to our tree (which sorts them internally) and then removes each one, one by one. We also check if the tree contains the given number twice per addition/deletion.

The code snippet above is actually Crystal code.
Like I said, these languages are identical at first glance. Rewriting the code from Crystal to Ruby took me a total of [50 line changes](https://github.com/Enether/crystal-aa-tree/pull/1/files) for a 360 line file. *[27 if you were greedy](https://github.com/Enether/crystal-aa-tree/pull/2/files)*
It is worth noting that those changes are simply removing `.as()` method calls and type annotations.


#### Okay, they look identical but how much faster is Crystal?

Let's build the executable and start testing
```shell
> enether$ crystal build AA_Tree.cr -o crystal_tree --release
```
```shell
# 100 elements
> enether$ ./crystal_tree 100
Time it took: 0.0006560 seconds.
> enether$ ruby AA_Tree.rb 100
Time it took: 0.00172 seconds.

# 10K elements
> enether$ ./crystal_tree 10000
Time it took: 0.0044000 seconds.
> enether$ ruby AA_Tree.rb 10000
Time it took: 0.288619 seconds.

# 100K elements
> enether$ ./crystal_tree 100000
Time it took: 0.0498230 seconds.
> enether$ ruby AA_Tree.rb 100000
Time it took: 3.414404 seconds.

# 1 million elements
> enether$ ./crystal_tree 1000000
Time it took: 0.5007820 seconds.
> enether$ ruby AA_Tree.rb 1000000
Time it took: 39.370083 seconds.

# 10 million elements
> enether$ ./crystal_tree 100000000
Time it took: 5.6283920 seconds.
> enether$ ruby AA_Tree.rb 100000000
# Still running
```
As you can see, it runs laps around Ruby and proves to be ~80 times faster if we were to judge by our 1 million elements example.


# Quirks and differences to Ruby
Despite the similarities, there are substantial differences to Ruby, here we will highlight the most obvious and interesting ones.



## Types, type checking and type unions

The most apparent difference is that Crystal uses and mostly enforces types for variables. It has great type inference - if you do not explicitly define the type of a variable the compiler figures it out itself.
The way this language does typing is a sort of mix between static and dynamic typing. It allows you to change a variable's type
```crystal
a = "Hello"
puts typeof(a) # => String
a = 42
puts typeof(a) # => Int32
```
but it also allows you to enforce a variable's type
```crystal
a : String = "Hello"  # a should be a string and only a string!
a = 42 # error:  type must be String, not (Int32 | String)
```

### Type Unions
Were you wondering what the `(Int32 | String)` type was in the error message above?
This is a so-called type union, which is a set of multiple types.
If we were to enforce `a` to be a union of `Int32` and `String`, the compiler would allow us to assign either type to that variable as it knows to expect both.
```crystal
a : (Int32 | String) = 42
a = "Hello"
# Completely okay

# But if we were to try to assign another type to it
a = true # => type must be (Int32 | String), not (Bool | Int32 | String)
```

### Type Inference and Type Checking
The compiler can figure out the type of a variable himself in most cases. The type inference algorithm is specifically built to work when the type of the variable is obvious to a human reader and does not dig too deep into figuring out the specific type.

In the cases where multiple conditions are plausible, the compiler puts a union type on the variable. Crystal code **won't compile** if the possible types do not support a given method invoked on them.
```crystal
if rand() > 0.5
  a = "String"
  puts typeof(a) # => String
else
  a = 42
  puts typeof(a) # => Int32
end
puts typeof(a) # => (String | Int32)
puts a.camelcase  # => undefined method 'camelcase' for Int32 (compile-time type is (Int32 | String))
```
This is the way the compiler protects you from silly mistakes with mismatched types, something that is really common in dynamic languages. Its like having your very own programming assistant!

The compiler is smart enough to figure out when a variable is obviously from a given type
```crystal
if rand() > 0.5
  a = "String"
elsif rand() > 0.75
  a = 42
else
  a = nil
end
puts typeof(a) # => (String | Int32 | Nil)
unless a.nil?
  # a is not nil for sure
  puts typeof(a)  # => (String | Int32)
end
if a.is_a?(String)
  puts typeof(a)  # => String
end
```

There are ways to ensure the compiler that the appropriate type is set. 
```crystal
puts a.as(String).camelcase
```
This checks that the `a` variable is a string and if it is not, it throws an error.

### Enforcing types
As we said, we have the option to enforce a variable's type or let it be whatever.
This holds true for a method's parameters as well. Let's define two methods:
```crystal
def generic_receiver(item)
  puts "Received #{item}!"
end

def string_receiver(item : String)
  puts "Received string #{item}!"
end
```
I assume you can already imagine what'll happen with the following code:
```crystal
generic_receiver(1)
generic_receiver("Hello")
generic_receiver(1.5)
generic_receiver(true)
string_receiver("Hello")
string_receiver(1)  # error!
```
It is usually good practice to not enforce a variable, as it leads to more generic code.

## Concurrency
Its concurrent model is inspired by that of Go, namely [CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes) (Communication Sequential Processing).
It uses lightweight threads (called fibers) whose execution is managed by the runtime scheduler, not the operating system. Communication between said threads is done through channels, which can either be unbuffered or buffered.

<img src="https://i.imgur.com/cwjUVtg.png" />
*Pictured: A lot of fibers who communicate between each other through channels*

Crystal currently runs in a single thread but their roadmap intends to implement multithreading. This means that it currently has no support for parallelism (except for process forking), but that is subject to change.
Because at this moment there's only a single thread executing your code, accessing and modifying a variable in different fibers will work just fine. However, once multiple threads is introduced in the language, it might break. That's why the recommended mechanism to communicate data is through channels.


## Metaprogramming
Crystal has good support for metaprogramming through macros. A macro is something that pastes code into the file during compilation.

Let's define our own version of Ruby's `attr_writer`
```crystal
macro attr_writer(name, type)
    def {{name}}=({{name.id}} : {{type}})
        @{{name}} = {{name.id}}
    end
end
```

Calling `attr_writer foo, Int32` will evaluate to
```crystal
def foo(foo : Int32)
 @foo = foo
end
```

```crystal
class Greeter
    attr_writer hello_msg, String

    def hello_msg
        @hello_msg
    end
end


gr = Greeter.new
gr.hello_msg = "Hello World"
puts gr.hello_msg # => Hello World
gr.hello_msg = 11 # => no overload matches 'Greeter#hello_msg=' with type Int32
```
Crystal macros support iteration and conditionals and can access constants.
```crystal
MAX_LENGTH = 3
macro define_short_methods(names)
  {% for name, index in names %}
    {% if name.id.size <= MAX_LENGTH %}
        def {{name.id}}
          {{index}}
        end
    {% end %}
  {% end %}
end

define_short_methods [foo, bar, hello]
puts foo # => 0
puts bar # => 1
# puts hello => undefined local variable or method 'hello'
```

# Miscellaneous
Crystal has taken a lot of cool features from other languages and provides various syntax sugar that is oh so sweet!

### Initializing class instance variables directly in a method
```crystal
def initialize(@name, @age, @gender, @nationality)
```
is equal to
```crystal
def initialize(name, age, gender, nationality)
  @name = name
  @age = age
  @gender = gender
  @nationality = nationality
end
```

### Implicit object notation
Switch statements support invoking methods on the giving object without repeatedly specifying its name.
```crystal
case string_of_the_gods
when .size > 10
  puts "Long string"
when .size == 5
  puts "Normal String"
when .size < 5
  puts "Short String"
end

case {1, 1}
when {.even?, .odd?}
  # Matches if value1.even? && value2.odd?
end
```

### External keyword arguments
My personal favorite - Crystal allows you to name a function's parameters one way for the outside world and one way for the method's body
```crystal
def increment(number, by value)
  number + value
end
increment(10, by: 10)
```

## Compiler
As you saw earlier, this language is compiled to an executable. Regardless, it still has something like a REPL which proves to be similar to our beloved `irb` - https://github.com/crystal-community/icr
You can also directly run a file without having to compile it and then run it, via the `crystal` command.
```shell
> enether$ crystal AA_Tree.cr 200000
Time it took: 0.536102 seconds.
```
This runs a little bit slower because we do not make use of the optimizations that the `--release` build flag brings with itself.

## C Bindings
There is a way to write a performant library in Crystal which you can run in your Ruby code. The way you do this is to bind Crystal to C, which allows you to use it from Ruby.
I did not delve too deep into this but apparently it is easy and you can do it without writing a [single line of C](https://crystal-lang.org/docs/syntax_and_semantics/c_bindings/). That is awesome!


# Conclusion

If you write Ruby, picking up Crystal is natural and can quickly find yourself writing performance-critical software in it. I believe it has a lot of potential and can yield a lot of benefits to our community but also to non-ruby programmers, as the syntax is just too easy to pass up. It is a joy to write and it runs blazingly fast, that is an unique combination which very few languages can boast with.
I hope I've sparked your interest by these short examples! I strongly encourage you to take a look at the language for yourself and notify me if I've missed something.

Here are some resources to read further up on:
[Google Group](https://groups.google.com/forum/#!forum/crystal-lang)
[Gitter Chat](https://gitter.im/crystal-lang/crystal)
[IRC](https://webchat.freenode.net/?channels=%23crystal-lang)
[Subreddit](https://www.reddit.com/r/crystal_programming/)
[Newsletter](https://www.reddit.com/r/crystal_programming/)