An Introduction to Crystal: Fast as C, Slick as Ruby

Development

Reading Time: 6 minutes

I’m a Rubyist. I love Ruby, the community, the productivity, and so many other things about it. For more than four years now, I’ve written Ruby professionally, and I’d really like to keep it that way. But I’m also aware that languages and tools are destined to be replaced.

Ruby is awesome, but it is not necessarily known for its speed. Sometimes it’s not the right tool for demanding applications. And we have to wait a while for Ruby 3×3 to happen.

So I’m gonna ask you a question:

To be honest, I’ve always dreamed of something like that and wondered why it didn’t exist. Then I found Crystal. I still remember it clearly: It was July 2015, I was reading /r/programming, and I saw something like “Crystal: Fast as C, Slick as Ruby.”

So I went to the site, downloaded Crystal, and ran my first Crystal (actually Ruby) program:

crystal hello.rb
p "Hello World"

And it worked!

Hello World

The happiest Hello World of all times.

Enter Crystal

Let’s take a closer look at some of Crystal’s goals:

  • Have a syntax similar to Ruby (but compatibility with Ruby is not a goal).
  • Statically type-checked but without having to specify the type of variables or method arguments.
  • Have compile-time evaluation and generation of code, to avoid boilerplate code.
  • Compile to efficient native code.

There are some important points that we need to underline here. First and most important:

Have a syntax similar to Ruby (but compatibility with Ruby is not a goal).

This is pretty self explanatory. Crystal is not Ruby. It can’t run Rails.

Statically type-checked but without having to specify the type of variables or method arguments.

Unlike Ruby, Crystal is a typed language, but most of the time it’s not required to specify types. Take this, for example:

def greet(name, age)
  "I'm #{name}, #{age} years old."
end

greet "Serdar", 27 # I'm Serdar, 27 years old.

So when do we use types, and what they are useful for?

def add(x : Number, y : Number)
  x + y
end

# Ok
add 1, 2 # Ok

# Error: no overload matches 'add' with types Bool, Bool
add true, false

Great, this is a compile-time error. We restrict the method to only accept the types of x,y as a Number. In Ruby, this would be a runtime error, a.k.a., a disaster. Yay for Crystal!

Have compile-time evaluation and generation of code, to avoid boilerplate code.

Macros, anyone? Ruby is famous for its metaprogramming capabilities. Crystal uses macros to achieve that while reducing boilerplate code. This example is taken from Kemal, an awesome web framework for Crystal.

HTTP_METHODS = %w(get post put patch delete options)

{% for method in HTTP_METHODS %}
  def {{method.id}}(path, &block : HTTP::Server::Context -> _)
   Kemal::RouteHandler::INSTANCE.add_route({{method}}.upcase, path, &block)
  end
{% end %}

Here’s how the DSL declaration is done in Kemal, looping through the HTTP_METHODS array to define a method for each HTTP verb. By the way, macros are evaluated at compile-time, meaning that they have no performance penalty.

Compile to efficient native code.

Crystal is a compiled language. I’m not gonna dive into the advantages of having a compiler, but I can easily say that it gives you a lot of optimizations for free. In addition, when a Crystal program is compiled, it’s an efficient single file, native code. It’s super convenient and easy to run/deploy.

Here’s how you compile your Crystal program.

crystal build program.cr

it produces a single, executable, native binary with the same name that you can run with:

./program

Awesome!

Crystal’s Fantastic Standard Library

Crystal comes with a great standard library and tools. It has all the stuff you need to build modern applications. CSV, YAML, JSON, HTTP, and even WebSocket are bundled with Crystal itself, making it super simple to start building something.

Need a web server? No problem!

# server.cr
require "http/server"

server = HTTP::Server.new(8080) do |context|
  context.response.content_type = "text/plain"
  context.response.print "Hello world, got #{context.request.path}!"
end

puts "Listening on http://0.0.0.0:8080"
server.listen

It’s just 5 LOC to build a functional web server: crystal server.cr. Go to localhost:8080.

crystal-http-server

The crystal command itself is also really useful. It even has a built-in code formatter that you can use: crystal tool format your_app.cr.

The most amazing command is the crystal play. It’s basically a playground to quickly run some Crystal code and get instant feedback. Just run it and go to localhost:8080.

crystal-play-gif

Crystal’s Performance

Crystal has a unique goal of being as slick as Ruby but with performance.

I love benchmarks, but remember, benchmarks should be taken with a grain of salt.

This is a naive Fibonacci implementation for Crystal (it’s also valid Ruby):

# fib.cr
def fib(n)
  if n <= 1
    1
  else
    fib(n - 1) + fib(n - 2)
  end
end

puts fib(42)

Let’s run it and see how long it takes!

time crystal fib.cr
433494437
crystal fib.cr  2.45s user 0.33s system 98% cpu 2.833 total

Since this is also valid Ruby, let’s run it with Ruby this time

time ruby fib.cr
433494437
ruby fib.cr  38.49s user 0.12s system 99% cpu 38.718 total

Crystal took 2.833 seconds to complete. Ruby took 38.718 seconds to complete. Pretty cool. We get 20x performance for free. What if we compile our program with optimizations turned on?

crystal build --release fib.cr
time ./fib
433494437
./fib  1.11s user 0.00s system 99% cpu 1.113 total

crystal-vs-ruby-benchmark

1.113 seconds. Now we’re nearly 35 times faster than Ruby. Isn’t that cool? This is what I am talking about! The Crystal compiler uses LLVM to do really good optimizations.

If you’re really into benchmarks (like I am), you should check out the crystal-benchmarks-game repository.

Remember: The cake is a lie, and so are benchmarks. You won’t have 35x increase in performance all the time, but you can expect 5x or more in complex applications, more if it’s CPU intensive.

Sign up for a free Codeship Account

Concurrency in Crystal

In Crystal, we use the keyword spawn to make something work in the background (a.k.a., async) without blocking the main execution. To achieve this, spawn creates a lightweight thread called Fiber. Fibers are very cheap to create, and their execution is managed internally by the process. You can easily create tens of thousands of Fibers on a single core.

Okay, we can use spawn to make stuff work in the background, but how do we send/receive something from a Fiber? That’s where Channels come into play. If you’re familiar with Channels from Go, then you’ll feel right at home.

Fibers can execute and keep sending messages through the Channels. Execution control is yielded to whoever is expecting to receive from the same Channels. Once one of them receives and executes, control is sent back to the Scheduler to allow other spawned Fibers to execute. They can keep “pinging” and “ponging” like this.

Speaking of ping-pong, you have this snippet from the “Go by Example” site:

package main
import "fmt"

func ping(pings chan<- string, msg string) {
    pings <- msg
}

func pong(pings <-chan string, pongs chan<- string) {
    msg := <-pings
    pongs <- msg
}

func main() {
    pings := make(chan string, 1)
    pongs := make(chan string, 1)
    ping(pings, "passed message")
    pong(pings, pongs)
    fmt.Println(<-pongs)
}

And the Crystal version:

def ping(pings, message)
  pings.send message
end

def pong(pings, pongs)
  message = pings.receive
  pongs.send message
end

pings = Channel(String).new
pongs = Channel(String).new
spawn ping pings, "passed message"
spawn pong pings, pongs
puts pongs.receive # => "passed message"

The Crystal version feels more natural and easy to read for me. I think most Rubyists will feel the same.

Crystal: Not Just for Rubyists

Crystal is a simple, easy-to-learn, high-performant, general programming language that uniquely combines all of these without any compromise. It’s not just for Rubyists!

So what can you build with Crystal? Games, graphic renderers, low-level agents, web applications, and much more. It’s really up to you what your next shiny project will be in Crystal! If you’d like to explore some projects built with Crystal, check out <crystalshards.xyz>. It’s a great place to discover Crystal projects.

Bonus: If you can’t find a name for your next project, try the name generator!

Resources

If you’ve made it this far, you might be asking, “Where do I go next?” Of course, the official Crystal book is a great place to start learning. Crystal for Rubyists is a free book to bootstrap your Crystal journey, and Crystal has an active Gitter room for communication. See you there!

Posts you may also find interesting:

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • Antonio Medina

    Excelent introduction to Crystal Language.

    Thanx for share!!!

  • kimshibal

    which ship should i jump? this or elixir?

    • Larry Weya

      I’m already on elixir, its definitely not as fast as c but it builds on the great foundation of the Erlang VM so you get things like a distributed computing framework. If I was to move from elixir I would go with Haskell.

    • X4lldux

      Depends what you are building. Erlang VM is great for building fault-tolerant, distributed (duh! you cant have fault tolerance without distributed systems), highly available and concurrent system; like phone switches – when was the last time you heard “sorry, your phone is disabled due to scheduled maintenance”? It has 30 years of experience in those conditions. It is true that many current web systems look similarly to those past telephone networks, so many problems are already solved.

      If you’re building something which is CPU bound, that needs raw performance go for Crystal… or OCaml, or Go, or Haskell, or Rust, or Nim.

      • ElectronCharge

        You forgot the elephant in the room for high-performance code: Swift.

        • Benjiro

          No windows support. Lackluster Linux support ( Needed IBM to write them half the basic code … Sockets are a good example ). Sure, Swift ran on Linux but without IBM it was useless. Its made by Apple, for Apple and it shows.

    • frostymarvelous

      Guess I’m not the only one in a fix.

  • Hassaan Markhiani

    Comparing Ruby and Crystal using Fibonacci with tail recursion tells a different story. I added Elixir for fun.

    • sdogruyol

      Have you tried compiling and running the Crystal program with

      crystal build –release fib_tail.cr && ./fib_tail

      it’ll be much fair and performant to do so.

      • Hassaan Markhiani

        Fair point. I was trying to point out that when optimized the performance is a bit more comparable.

        • Asked around in #elixir-lang@freenode and to be fair to Elixir, it should be compiled and run within iex with :timer.tc to get rid of VM start times which apparently is ~0.3-0.4s

          • Hassaan Markhiani

            Running on another computer, so I’m posting the Elixir timings from the command line again.

          • Thanks, 0.000552s for elixir tail seems more reasonable. :P

    • What Elixir code did you use for this?

  • Gilles Marek

    At least 2 links are broken in this article (Crystal and /r/programming)

  • Great post!

    Small note: Kemal link is not working within the post, i guess it’s because of relative href:
    https://blog.codeship.com/an-introduction-to-crystal-fast-as-c-slick-as-ruby/kemalcr.com

  • sdogruyol

    You can use crystalshards.xyz to browse the most popular and recent Crystal shards. You can also search for any existing project.

  • I’ve been writing Elixir for about 2 months now and I love it. Crystal has always been in the back of my mind for about a year and a half now. Is it ready for prime-time?

    One question, how easy is it to build a binary that “just runs” anywhere? With Go I could compile to a binary that just worked with no runtime installation needed. Does Crystal have this? If so, I could definitely see myself adding Crystal to my toolshed. Elixir for the main workload and Crystal for the heavy lifting/brute force.

  • Pingback: Red vs Crystal | Fotomix's Weblog()

  • wesen87

    Official crystal blog sports the same example: https://crystal-lang.org/2016/07/15/fibonacci-benchmark.html
    But there it’s explained that you are simply cheating by taking a smaller integer type, and that this will fail startíng with fib(49)…

  • Oleiade

    I find Crystal very appealing, although I’m already a Go power-user. But I find the “Fast as C” statement a bit too much: Crystal is still a garbage collected language after all… It is very fast, yes, but it still has to pause here and there and clean the mess after you ;-)

  • Vikram

    So does it do multi-threading?

  • genericid

    > In addition, when a Crystal program is compiled, it’s an efficient single file, native code. It’s super convenient and easy to run/deploy.

    Except, Crystal neither runs on Windows and neither can produce Windows native executables.

    • Tyrone Wilson

      And…….. :P

      • genericid

        Operating System | Total Market Share

        Windows | 90.85%
        Mac | 6.92%
        Linux | 2.23%

        https://www.netmarketshare.com/operating-system-market-share.aspx?qprid=8&qpcustomd=0

        • Tyrone Wilson

          Oh cool, I assume you adjusted the numbers for developers that already know ruby (crystal’s target audience)?

          • genericid

            Didn’t get that, can you elaborate?

          • Tyrone Wilson

            Meaning of the developers that use ruby, what is the distribution of OS rather than a global distribution of OS.

            So for just any type of user you might have a distribution like you mentioned but within the ruby community I would imagine something more like

            OSX 70%
            Linux 25%
            Windows 5%

            Hence if you are going to write a language that is targeted at ruby devs, you could ignore the 5% on windows if it is a technical burden.

          • genericid

            I am writing code for users, not for a specific dev environment.

            Not everyone will use Ruby (or Crystal) for writing web applications running on Linux.

            I also use Ruby for scripts aimed at Windows users, or small Windows GUI apps, hence me and a great number of other developers need to take into account the capability to deploy native Windows executables, or at least to have an interpreter running on Windows.

            Crystal doesn’t even provide the latter!
            Thus the following sentence:

            > when a Crystal program is compiled, it’s an efficient single file, native code. It’s super convenient and easy to run/deploy.

            should either be expanded with “on *nix platform only”, or it will just sounds like bragging.

          • Tyrone Wilson

            Fair enough.

          • Bino

            So if you are doing code for the market share of desktop apps, make in a lang that run on Windows, but the Servers for Web and any other stuff run on in mostly POSIX

        • Jim Phillips

          Sure, PCs.. Now, What’s the Total Market Share for web application servers? Because people don’t generally write desktop apps in Ruby either.

          • genericid

            A web server is a desktop app running on a desktop O.S., did you know?

          • Jim Phillips

            Put the crack pipe down and back away please.

          • genericid
          • genericid

            The article is called “fast as c, slick as ruby” so it’s like as if it implies that Crystal is the holy graal of the programming languages, ubiquitous and capable of running everywhere at high speed (like C), with the least effort from the developer (with a nice Ruby syntax). Well, if you want to be that, then first requisite before anything else is you need to be cross-platform.
            Otherwise, go titling the article “100 time faster than PHP, slick as Ruby” and then see how much traction this has on the community…
            That’s all.

            Also, it’s nice to see I’m not alone:
            https://spin.atomicobject.com/2017/03/24/ruby-to-crystal-test/

    • Vamsi Deepak Ampolu

      Find yourself a docker container, use ubuntu for windows. Worst excuse ever

      • genericid

        Yeah, “hey final user, in order to run my app you just need to run an entire Operating System inside a Virtual Machine which you’ll need to install on your system, easy as 1-2-3!”.

  • peter marien

    We will take a look once Windows is supported.

  • Could you expand (maybe in a follow up article!) on the biggest incompatibility issues you’ve ran into with Crystal?

    I’ve been thinking about using Crystal for the background workers at PluckHQ.com and ChartURL.com but we do a lot of very complex regex matching, have lots of classes, and we make lots of REST API calls etc… so I’d hate to get half way down the path and find out there’s some fundamental thing missing.

    Great article. Thanks!


    Ryan Angilly
    GM @ PluckHQ.com
    GM @ ChartURL.com

  • OlegM

    Doesn’t run on Windows at all…good luck with this project guys :)

  • Thanks for posting this, I really enjoyed learning about Crystal! However, the nitpicker in me has to point out this is NOT a correct implementation of the fibonacci sequence. Correct version would be:

    def fib(n)
    if n <= 2
    1
    else
    fib(n – 1) + fib(n – 2)
    end
    end

    # 1, 1, 2, 3, 5, …

  • Pingback: Crystalの紹介:Cのように速く、Rubyのように滑らか | FAworksブログ()