close icon
Crystal

The Highs & Lows of Crystal - an Introduction to Crystal Lang

Crystal Language claims to be as fast as C and as easy to learn as Ruby. Here, I put it to the test and give my 2 cents!

August 23, 2017

Crystal is a statically-typed, compiled, systems programming language with the aims of being as fast as c/c++, while having a syntax as readable as Ruby. This article is an introduction to the Crystal Language, through the eyes of a polyglot programmer. Being a former programmer of both C and Ruby, I have been able to explore the ins-and-outs of Crystal with an objective mindset and give an unbiased opinion on its features; from it's low-level primitives to its familiar syntax, and much in-between.

I first came across Crystal when I saw @sferik giving a talk on it in Poland back in 2015. Video here. It was a great talk, and sparked my interest to Crystal right there and then. When I initially checked out Crystal, I thought it looked awesome but I was too busy with all the other languages I was using on a daily basis to be able to focus my time properly on it.

Alongside being too busy, I couldn't really see why I'd use Crystal instead of using C/Erlang/Go/Ruby - languages that I already knew. What I can say with confidence now though; is that whilst those languages may all be able to achieve the same end-goal they're all better in their own, different ways entirely.

When I want to build distributed apps, like my Fist/Bump Heartbeat Monitor - I use Erlang/Elixir. When I want to build an API backend I use Golang. When I want to spend the day with my brains scattered, and most probably in tears - I use C. For readibility and demonstrations, I use Ruby. When it comes to writing low-level systems such as daemons and obtuse Kernels, while it would be most performant to turn to C - it'd also take me a LONG time to achieve relatively little and the aforementioned tears would be very-likely flowing. This is where Crystal comes in.

Having a syntax very similar to Ruby means that the familiarity of Crystal is incredibly enticing. Since the world is obsessed with web apps now, let's take a look at the code required to build a minimal web server in both Crystal and Ruby.

In Ruby, using Sinatra, the code is as follows:

require "sinatra"
 
set :logging, false
 
get "/" do
  content_type "text/plain"
  "Hello, Auth0!"
end

Now in Crystal, check out the equivalent code:

require "kemal"
 
logging false
 
get "/" do |ctx|
  ctx.response.content_type = "text/plain"
  "Hello, Auth0!"
end
 
Kemal.run

The fact that you can use Ruby syntax highlighting natively for Crystal says everything! Coming from Ruby to Crystal is a remarkably easy adaptation. The fact is; one copy and paste code from Ruby to Crystal and 90% of the time it will run with no errors. The creators of Crystal understand that Ruby is undoubtedly the most visually appealing language, and therefore built Crystal to take as much influence as possible from a design perspective. You can even run Crystal programs using the Ruby shell command and vice versa since the syntax is valid for both languages!

Binding C

One of the big selling points for Crystal is the ease with which you can interface with C libraries. "Crystal allows you to bind to existing C libraries without writing a single line in C. Additionally, it provides some conveniences like out and to_unsafe so writing bindings is as painless as possible."

Let's build a simple script in C that says "hi!". We'll then write a Crystal app to bind to our C library. This is a great starting point for anyone who wants to know about binding C in Crystal.

First off, let's create a project with Crystal's scaffolding tool (I'll cover this feature later). Run:

$ crystal init app sayhi_c

Then head into the directory sayhi_c/src/sayhi_c and let's create a file sayhi.c with the following contents:

#include <stdio.h>

void hi(const char * name){
  printf("Hi %s!\n", name);
}

Now we need to compile our C file into an object. On Ubuntu or Mac using gcc we can run:

$ gcc -c sayhi.c -o sayhi.o

Using the -o flags allow us to create an Object filetype. Once we've got our Object file, we can bind it from within our Crystal app. Open up our sayhi_c.cr file, and have it reflect the following:

require "./sayhi_c/*"

@[Link(ldflags: "#{__DIR__}/sayhi_c/sayhi.o")]

lib Say
  fun hi(name : LibC::Char*) : Void
end


Say.hi("Auth0")

I'll mention now that there are no implicit type conversions except to_unsafe - explained here when invoking a C function: you must pass the exact type that is expected.

Also worth noting at this point is that since we have built our C file into an object file, we can include it in the project directory and link from there. When we want to link dynamic libraries or installed C packages, we can just link them without including a path.

So, if we build our project file and run it, we get the following:

$ crystal build --release src/sayhi_c.cr

$ ./sayhi_c

 > Hi Auth0!

It's really easy to bind to C in Crystal, and is definitely one of the features that attracts me most to the language. I'm really looking forward to writing a C binding for a useful library and being able to utilise this functionality in production!

Concurrency Primitives

One of my favourite parts of Golang is the goroutine threading system. Working in the Database industry, I got a real passion for concurrency & parallelism and when looking to a new language, one of the first things I explore are the concurrency primitives. In Crystal, we can use the Spawn functionality in a very similar way to Goroutines in Golang, core.async in Clojure, or the lightweight threading in Elixir/Erlang.

For a simple test, I wrote two quick scripts to test the Spawn functionality in Crystal alongside Ruby. We all know that Ruby is not a great language for threading, so I'm interested to see how much better Crystal is in small experiments. Let's take the following example in Ruby:

1000.times.map do
  Thread.new do
    puts "Hello?"
  end
end.each(&:join)

Running this from terminal I got the following results:

$ time ruby spawntest.rb

real    0m0.288s
user    0m0.132s
sys    0m0.116s

I ran this little script on one of my ancient laptops that runs only 2gb of RAM and a terrible, terrible processor. Now, porting this script to Crystal, we can write:

channel = Channel(String).new
1000.times do
  spawn {
    channel.send "Hello?"
  }
  puts channel.receive
end

Running this script with the crystal command, I got the following results:

$ time crystal spawntest.cr

real    0m1.129s
user    0m0.952s
sys    0m0.276s

Hmmmm, very interesting indeed! Well, seen as Crystal is a compiled language and meant to be used to build small binaries that are easily distributed, it'd be a good idea to compile this small script and use that data instead! I compiled the script using the --release flag - this tells the Crystal compiler to optimise the bytecode.

$ crystal build --release spawntest.cr

$ time ./spawntest

real    0m0.008s
user    0m0.004s
sys        0m0.000s

As you can see, this result is markedly different. Using the --release flag when building the Crystal executable cuts out a lot of bloating and optimises the executable to be as efficient as possible. Obviously, the above test is a very naive use of the Spawn functionality, and unfortunately, I haven't had the opportunity to test in a load-heavy production environment. But soon I fully intend to, and I'll write another article benchmarking this in detail when I have a good usecase and get the chance to!

Built-in Tooling in Crystal

One of the things I like most about Crystal is the excellent built-in tooling available. When I look at new languages, especially relatively immature languages; it's always very reassuring when the language has extensive built-in tooling available to help developers stay productive & happy! In Crystal, there are a bunch of tools that make hacking around in the language super fun, but also help us to stay on the right track with semantics etc.

Testing

If you're coming from a Ruby/Rails background, I think you'll be very happy with the built-in testing framework that ships with Crystal. It's rather reminiscent of RSpec, and will be really easy to use for anyone coming from a similar background. Even if you're not from a Ruby/Rails background, it's a great testing tool and is super effective.

Using the Greeter demo app from the Crystal docs, we could write our Specs as follows:

require "spec"
require "../lib/greeter" # demo greeter class

describe Greeter do
  describe "#shout" do
    it "returns upcased string" do
      Greeter.new.shout('hello auth0').should eq "HELLO AUTH0"
    end
  end

  describe ".hello" do
    it "returns a static Hello string" do
      Greeter.hello.should eq "Hello"
    end
  end
end

As you can see, this spec should look very familiar to any Rubyists, and is becoming the more preferred syntax for application testing across many languages - there being RSpec clone libraries across most languages now!

Project Scaffold

Much the same as Elixir having the Mix manager, and Erlang the Rebar manager, Crystal has it's own built-in project scaffolder & package manager. I'd recommend using this at all times to ensure sematics are followed. We can use it with the following:

$ crystal init lib my_cool_lib
      create  my_cool_lib/.gitignore
      create  my_cool_lib/LICENSE
      create  my_cool_lib/README.md
      create  my_cool_lib/.travis.yml
      create  my_cool_lib/shard.yml
      create  my_cool_lib/src/my_cool_lib.cr
      create  my_cool_lib/src/my_cool_lib/version.cr
      create  my_cool_lib/spec/spec_helper.cr
      create  my_cool_lib/spec/my_cool_lib_spec.cr
Initialized empty Git repository in ~/my_cool_lib/.git/

Sharding

No - not creating Database Shards (luckily)! Shards are Crystal's packages distributed in the same way as Ruby Gems, Elixir Libs or Golang packages. Each application we create contains a file in the root directory named shard.yml. This file contains project details and external dependencies. The shard.yml file in my sayhi_c app above looks like this:

name: sayhi_c
version: 0.1.0

authors:
  - Robin Percy <robin@percy.pw>

targets:
  sayhi_c:
    main: src/sayhi_c.cr

crystal: 0.22.0

license: MIT

The app I built has no dependencies to use, but if we want to include external packages we can do so by adding them at the bottom of the file:

dependencies:
  github:
    github: felipeelias/crystal-github
    version: ~> 0.1.0

Documentation & Formatting

Crystal has a great built-in tool for generating documentation and formatting files. The documentation that is generated is excellent - built-in html/css and almost instantly ready to deploy.

To generate documentation, from the project root directory we can simply run:

$ crystal doc

This will create a docs directory, with a doc/index.html entry point. All files inside the root src directory of the project from which we ran the command will be considered.

Alongside this, the built-in Formatter tool is a great feature of the language. We can run the formatter over our project by running:

$ crystal tool format

We can use this tool to unify code styles and to submit documentation improvements to Crystal itself. The formatter is also very fast, so very little time is lost if you format the entire project's codebase instead of just a single file.

Both of these features are very cool, and highly useful!

What I don't like

Like anything in this world, Crystal can't possibly be perfect! There are two very small issues that I find with it...

As a Polyglot programmer, I've had to learn a bunch of different programming paradigms. While this isn't a fault in Crystal, the fact that it's object orientated is pretty much the only thing I'm not too keen on in Crystal. Other than that; being a relatively young & immature language, there's often a lack of documentation available when you want to do something incredibly specific.

Seeing this as an opportunity instead of a foible - it's actually kind of cool, because this means we can write documentation ourselves and hack sample apps together to become early adopters and decent contributors in the Crystal community!

Aside - Auth0 & JWTs in Crystal

Update: - I have written about securing a Crystal web app with Auth0 & JWT's here.

At the moment, there is no Crystal-Auth0 library to use for end-to-end application securing. However, there is a JWT library available for Crystal already here.

One thing to note here is that the Crystal-JWT library does not yet support the RS256 algorithm, which is the preffered algorithm and only supports the HS256 algorithm. When setting up your application in the Auth0 control panel, make sure to select the HS256 algorithm to reflect this.

In my next series of articles, I will be writing specifically about using Auth0 in a NON-jwt context, and I'll make sure I demonstrate this in Crystal!

Of course, if you're looking to secure a Crystal-based web app, you can always simply use the Auth0 Centralised Login. The Centralised Login will allow you to have immediate drop-in user management functionality.

Conclusion

I really rather like it!

Although relatively immature, Crystal is a promising language with a growing Dev community surrounding it. In my previous article about Auth0 Lock / Iris Image Recognition, I mentioned the fact that it'd be better to use the pHash / Blockhash libraries for a production environment. If I was to build that system, I would most definitely use Crystal to bind to those C libraries. I know that I'd be getting fantastically close-to-C speeds, and with the ease and joy of writing Crystal!

I am very much looking forward to seeing where this language goes. I think the adoption rate will rapidly increase and I'm excited to see startups using it in production systems. I am currently experimenting in building a Crystal library for the Auth0 API. I will write another article on building an API client in Crystal when I'm finished.

I do hope this article has inspired you to give Crystal a try, and look forward to hearing your feedback if/when you do! If you need any help and want to ask questions, reach out to me via email and @rbin on twitter, I'm happy to help!

Handy Resources

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon