Mastodon Icon GitHub Icon LinkedIn Icon RSS Icon

Will Crystal be the "sweet spot" I am looking for?

Header image for Will Crystal be the "sweet spot" I am looking for?

There is a vast space for “modern non-system compiled languages”. It is a space where most of the new languages rise (and die), and in which I am constantly attracted.

Even if I am quite comfortable with Rust (and Rust did remarkable improvements in ergonomic1), I always have the feeling that I am driving a super expensive and powerful sport car just to go buy groceries down the road.

For this reason, I always keep an eye open for a language in the sweet spot between a Python script and Rust.

In particular, I look for languages with:

  1. A sound type system designed by people who did not sleep under a rock in the last 30 year. In particular, if types are nullable, it is a big nope.
  2. A set of standard tooling. In particularly an opinionated dependency management.
  3. Easy concurrency (e.g., via green threads or whatever).

But let’s stop wandering around. Back to the main topic.

On March 22nd, 2021, I read about the release of Crystal 1.0. I already talked about Crystal some years ago, and I said that we needed to keep an eye on it.

If you do not know it, let’s just recap. Crystal is a general-purpose, object-oriented programming language whose syntax is inspired by Ruby. It is statically typed, and it is compiled into native machine code (using LLVM).

So, since it abides to all my requirements, why don’t we take a more in-depth look?

Crystal: The Good

Basically, a Compiled Ruby

Crystal as a really advanced type inference. Even if it is statically typed, it looks like plain old Ruby. For most of my exploration I forgot to even add types. Crystal can almost always infer the right type (or the right union type) and behave accordingly.

Crystal’s Standard Library is also very similar to Ruby’s one. As a consequence, you can copy Ruby snippets and paste them into your Crystal application, and they may work (with minor adjustments).

However, it doesn’t have the same pitfalls. For instance, types cannot be nillable. If a variable can be nil it must say it (e.g., declaring its type with the union type String | Nil).

Easy Concurrency

Crystal has a nice convenient threading mechanism based on fibers. A fiber is a lightweight thread managed by Crystal runtime (i.e., what we usually call green threads).

To make a comparison with a better known language, that is the same mechanism of Go’s goroutines.

spawn do
  loop do
    puts "Na"

sleep 1.second
puts "Batman"

You use spawn to spawn a new green thread and if you want to communicate between threads you use channels.

channel = Channel(Nil).new

spawn do
  puts "Before send"
  puts "After send"

puts "Before receive"
puts "After receive"

Pretty standard, but powerful, stuff.


The fact that Crystal is finally 1.0 means that is finally a viable choice for something more serious. After years of stabilization, we have a language interface that does not change every two months.

Compared to other languages we will discuss in the future, this is a big pro.

Crystal: The Goodish

Decent Tooling

Among the (only two) good things that Go popularized among developers there is integrated tooling. It is impossible for a modern language to not have built-in tools such as formatters, test runners, benchmarks, packaging and more.

Crystal keeps this promise. Out of the box, Crystal includes a test runner (they are called specs in the official documentation), a code formatter and some other tools useful for IDEs and plugins (for instance, crystal tool hierarchy that shows the hierarchy of all types in a script2).

At the end they do their job, but there is still work to do in this compartment. Other languages/runtimes, such as Deno, have way more stuff to offer.

Very Powerful Macro

Macro are goods if you are willing to seldom use them. They can really obfuscate the code and makes things weird.

But if you really must, Crystal’s macro are very powerful. It can literally manipulate the AST at compile-time.

macro define_method(name, content)
  def {{name}}
    {% if content == 1 %}
    {% elsif content == 2 %}
    {% else %}
    {% end %}

For instance, in this example, the macro write different code depending on the parameter of the macro invocation. And there is much more.

You know the deal: With great power comes great responsibility. So, this is a good thing. If in good hands.

“Killer” Frameworks

There are already two interesting web frameworks: Amber and Lucky. This kind of languages (especially one inspired by Ruby) thrive or die depending on their web frameworks.

After a quick look to both, they seem pretty complete and powerful (Amber is explicitly inspired by Ruby on Rails).

On the other hand, Crystal adoption is still pretty low. A quick search on GitHub shows me only 97 repositories with more than 100 stars (771 with more than 10 stars). It’s something, but there is still a lot of road to walk.

How much Amber or Lucky will gain traction (and how much of this traction will propagate to the language ecosystem) is still uncertain, but they will help.

Crystal: The Baddish

The usual disclaimer. I just want to clarify that I am talking about my personal preferences. These are universal language flaws, they are just friction points with my way of programming.

Basically, a Compiled Ruby

This is not an error. I really put the same good point as a badish point. The fact is that I can hardly fit with Ruby’s syntax. It is mostly alien to me and, therefore, jumping into Crystal will give me an extra challenge.

There are people that love Ruby’s syntax. Unfortunately, I am not one of them.

Compile Time

To compile the little concurrency code example above, Crystal took 1.2 seconds.

crystal build src/  1,12s user 0,51s system 31% cpu 5,229 total

In general, trying with bigger projects, I can say that there is still work to do on compilation time. For what I need to do usually, compile times are not a dealbreaker. But I know that for someone they are.

Windows is not natively supported

That’s a serious point. I know Crystal developers are working on it and that they did not want to delay 1.0 further to wait for Windows. Yet, I already know what happen when languages do not offer Windows support soon enough.

Windows is still a widespread desktop SO. A language without Windows support does not encourage adoption (even if you are Apple and you are called Swift, you still became a pretty purpose-specific language).

I hope Windows support will not be far away. Crystal needs it to grow.


Crystal was, and it is, an interesting language. Just to recap:


  • It will be very familiar to Ruby developers.
  • Statically type checked and very good type inference.
  • Concurrency via green threads (à la Go)
  • FFI with C by design.
  • Good performances.
  • No nulls as every good modern boys and girls.
  • Mighty macros.


  • Tooling and IDE support still very, very young.
  • Mighty macros (yep).
  • Compilation time could be better.
  • Still no native Windows support.

At the end, as a personal consideration, I would say that I am not in love with it. There was no spark. At least for now. It is a good language but still not good enough to make me drop what I use and to go deep in it.

And what do you think? Have you tried it? It is the best thing ever, and I missed that? Do you love it? Do you hate it?

In the meanwhile my exploration will continue. Let’s just say it will continue with a sharp change of direction.

Photo by Sharon McCutcheon on Unsplash

  1. Seriously, if you only tried Rust more than two years ago, you should try it again. ↩︎

  2. Even for a small program this produces a massive output. So, I guess it is for machines. ↩︎

comments powered by Disqus