Years ago a number of developers had the collective thought experiment “what if we took Ruby-like syntax and wrote a fast-as-C, general-purpose, typed language that (like C/C++) compiles to native binaries on any platform but keeps high level goodies like a rich standard library, full fibers support, etc?” Fast forward a few years and this dream has manifested itself in the Crystal programming language.
No more trade-offs: “Fast like C, slick like Ruby”
The main draw of Crystal is that it appears to have shattered the barrier between syntactically sweet interpreted/dynamic languages like Ruby and Python — which are loved for their readability and ease-of-use — and the raw horsepower of C/C++ and low-level systems languages. Until now, you always had the choice between writing a lot of hard-to-read, hard-to-debug, complicated, but extremely fast C/C++ code, and writing a short 5-liner in Ruby/Python that does what you want, but wastes memory and speed. In other words, we have always had to choose between performance and syntax. Since it is much easier to develop an MVP in Ruby/Python than it is in C/C++, for the past 18+ years SAAS companies large and small have simply accepted dynamic languages as a necessary evil and have taken on these extra performance hits as an unavoidable cost of doing business. With Crystal, however, you can do all the stuff you were doing in Ruby/Python but with the speed and memory footprint of a native C/C++ binary. And did I mention your code will be beautiful and you can still use dynamic methods sort of?
Why Crystal? 11 Reasons
- Performance. As I mention earlier, Crystal is a statically compiled language based on the venerable LLVM framework, and will go toe-to-toe with C/C++ and Rust any day of the week. Crystal is “speed without semicolons”. Just look at some of the latest benchmarks: https://github.com/tbrand/which_is_the_fastest
- Syntax. Like many interpreted languages, Crystal will let you do pretty much whatever you want in as few lines of code as possible, and in a way that is readable and aesthetically pleasing. For this reason, Crystal is sometimes advertised as similar to Rust but easier to read and write.
- Static Typing. Crystal is a statically typed language, meaning every variable has one or more potential types and these need to be figured out at compile time and accounted for. Ruby/Python are dynamically typed, so it is very difficult to reason about and optimize Ruby/Python code. By enforcing static typing, Crystal rules out a huge number of type related bugs and paves the way for optimizations and static compilation. Even better, the Crystal compiler only requires you to explicitly specify types when it is syntactically ambiguous what the type of a variable should be, meaning that often you don’t even need to deal with types directly and can proceed like you normally would in Ruby/Python-land.
- Macros. In C/C++ land it is extremely difficult to do things statically without writing some confusing macros using very antiquated syntax. Crystal lets you leverage a large percentage of the language when writing macros, so you can do crazy powerful things that normally would be impossible in a statically compiled language like having a library class written by person A reason statically about and change its code based on the contents of an implementer class written by person B at compile-time.
- You can still dynamically define methods*. Many people are afraid to use Crystal because they can’t dynamically define methods as it is a compiled language, but macros let you do just that at compile time. I would argue that 99% of the time, your dynamic method definition could be replaced with a compile-time macro in as many lines of code. It is extremely rare that there is a legitimate use-case for dynamically defined methods — we just aren’t used to languages that have the ability to do what always should have been possible to do at compile time. For more information see method_missing in the docs.
- Nil/Null-related errors impossible. The Crystal compiler treats Nil as a type and enforces explicit nil checking before interacting with an object that could be nil. This means that those pesky errors where you try to access a method or property on an object that turns out to be nil (and thus does not have that method or property) are physically impossible since the compiler will throw an error.
- Shards. Much like the gems ecosystem in Ruby and packages in other languages (e.g. Node), Crystal has shards. They are super easy to install and maintain, and get compiled directly into your binary executable via static linking. That’s right, even if you use 100+ shards, you will end up with one monolithic executable.
- Meteoric rise in popularity. Now that the language is more mature, people are flocking to Crystal which means more shards and faster implementation of new features and bug fixes. Recently Crystal shocked the world when it rose from 60th place to 32nd place in the Tiobe index in a mere month. No other modern programming language has experienced such a high rate of growth over such a short period of time.
- Cross-platform support. At the time of writing, Crystal has first-class support on Linux and OSX, with first-class windows support mere weeks away. This makes it perfectly suited for writing cross-platform tools, utilities and even desktop applications. I expect that within a year an Electron clone based on Chromium will emerge for Crystal, in addition to the existing cross-platform Qt-based (qt5.cr) and Libui-based (libui.cr) user interface libraries. Cross-platform support also makes it easier to reach developers on every platform.
- Web frameworks. Amber, the most popular Crystal-based web framework, has been designed from the ground up to be similar to Rails, but in a quintessentially Crystal way. It is also an order of magnitude faster than Rails, and measures page load times in microseconds rather than milliseconds. Similarly Kemal.cr (similar to Sinatra from Ruby) and newcomer Lucky also provide a solid start
Why Crystal and not some other language?
Nothing else puts all of these ingredients together (compile-time macros, static typing, C-like speed, Ruby-like syntax, gem-like package ecosystem, native binary compilation, fibers, and cross-platform support). In this regard, Crystal is at least as deserving of attention as Rust, which has seen a massive amount of popularity lately and is the closest language to Crystal in terms of the role it is meant to fill as a fast C++ replacement with high level language features and syntax. I don’t think Crystal will or should replace Rust (or Ruby for that matter)— that isn’t the point of this article and all three of these languages have their respective places. I do think that Crystal could be the next Rust in terms of popularity, and I think it deserves the attention.
And finally, some limitations
The five main limitations in the current version of Crystal at the time of writing are:
- Windows support not yet completed (but will be very soon)
- No parallelism unless you link against C code that does the parallelism (concurrency is fully supported by the fibers model, however). That said Go-style parallelism is coming very soon and is already working in a test branch.
- There are still small breaking changes every once in a while as they have not yet reached an official 1.0. That said there are plenty of companies using Crystal in production.
- Because Crystal is statically compiled, it is i̶m̶p̶o̶s̶s̶i̶b̶l̶e̶ very difficult to have a true REPL, though the Amber team have tackled this issue with the introduction of the
amber execcommand-line option, which will execute Crystal code in the specified environment, and even allows you to open up
nanoor your favorite text editor to edit and re-run said code (or pass it as a command line argument).
Consider using Crystal for your next project!