The Crystal programming language combines Ruby-like syntax with a really powerful compiler. As a result, it’s fun to write, fast to run, and hard to screw up!
My Crystal experience so far:
- danott mentioned it in our Slack a few weeks ago
- I read the great Crystal docs
- I cobbled together a lisp (barely)
I’d say it’s a combination of:
- a more-stable-Ruby (like Elixir, but without Erlang)
- a developer-friendly, life-embetter-ing type system (like Elm, but … not JavaScript)
- a real compiler! (like C, but fun to read and write)
Um, what else could you want?! (See last paragraph 😛)
Crystal Syntax
Crystal brings the best of Ruby:
- Concise literals, just like Ruby (take it for granted until you use regexps in Python 🙀)
- Great OO support, classes & modules just like Ruby
- Attractive syntax thanks to blocks, operator overloading and optional parens
- consistent, predictable standard library (like Ruby)
Plus, some improvements over Ruby:
- Method overloading
- Python-like keyword args: must have default value, may be passed as kwargs or positional args (I could go either way on this since Ruby 2.1, but it beats
options={}
) - More robust Proc literals, reminded me of Elixir
- Convention:
?
methods return maybe-nil types, while their counterparts raise on nil - First-class enums & tuples
- Immutable strings, like Ruby 3 will have (?)
For completeness, you lose some things from Ruby:
- Runtime code creation, like
define_method
& friends - Runtime code evaluation, like
eval
& friends
Crystal offers a powerful macro system that makes up for the loss of runtime metaprogramming. Unlike C preprossing, Crystal macros are awesome. You basically define functions which are called at compile-time, then generate code with liquid-like syntax.
Crystal Typing
Inferring Types
Crystal infers types from your code, so these are OK:
1 2 3 4 5 6 |
|
When types mix, Crystal automatically unions them. It will ensure any usages of the variable in question are valid for both types. For example:
1 2 3 4 5 6 7 8 9 10 11 |
|
There are some times you need to define types to help the compiler. For example, there aren’t any values here to tell the compiler what to expect:
1 2 3 |
|
Goodbye, NoMethodErrors
If you’re like me, you hate this:
1
|
|
Something somehow became nil. 😢
Instead, Crystal reads your code, and if there’s somewhere a value could be nil, it throws a compile error:
1 2 3 4 |
|
You have two options:
- Add an explicit not-nil check (
if object.is_a?(String) ...
) so the compiler knows it will be safe - Refactor so the value won’t be nil
Of course, the first one seems better at the start, but I hope to get better at the second one 😁.
What’s Missing?
Crystal really shows its youth. Its shortcomings all fall in that vein:
- Poorly documented, which isn’t so bad if you’re coming from Ruby
- Few projects out there (I think the package repository is a free Heroku app)
- Standard library has some kinks, they say it is still changing
One example of a standard library kink is the handling of break
, next
and return
in blocks. If you want to exit a block early, you have to choose one of those three. The problem is that, to choose the right one, you have to know whether the method captures the block into a proc or simply yields values to it. It’s a drag to have to know a method’s implementation to call it! (IRL, I didn’t run into this and I suspect it would be easy enough to work around it.)
Now What?
I really liked Crystal and I hope I can work with it more!