Kevin Marsh is a web developer from Toledo, OH with a focus on simplicity and usability, an eye for design, and insatiable curiosity.
After its 0.24.1 release a week ago I decided to give Crystal a whirl. It’s been around since 2014 but is gaining traction.
I started my programming journey hacking on PHP in 2001 and grew into a developer with Ruby and its clean syntax. I’ve dabbled in Go but didn’t care for the syntax. I’ve dabbled in Elixir but had a hard time getting my app deployed with the right Erlang/OTP dependencies. Crystal ticks most of the boxes I care about:
As its slogan says: “Fast as C, slick as Ruby.”
It also has a mature Sinatra-like web mini-framework called Kemal and an incubating-by-Rails-consultancy-juggernaut-Thoughtbot framework called Lucky which is shaping up nicely.
Installing Crystal on Mac is pretty easy with Homebrew: brew install crystal-lang
.
Here’s a simple Hello World app:
puts "Hello World"
Run with crystal run hello-world.cr
or build a binary with crystal build hello-world.cr
.
The syntax is identical to Ruby in most simple cases, but it’s not Ruby. For example, it can have types:
class Person
def initialize(name : String)
@name = name
@age = 0
end
def name
@name
end
def age
@age
end
end
kevin = Person.new("Kevin")
kevin.name #=> "Kevin"
I coded up a quick Sinatra “Hello World” app (which I can do mostly from memory) and a similar one in Kemal to give you a flavor of what it’s like to get started in Crystal.
require "sinatra"
get "/" do
"Hello World!"
end
Now let’s run and benchmark it:
% RACK_ENV=production ruby sinatra.rb # run
% wrk -c16 -t16 -d30s http://localhost:4567/ # benchmark
Running 30s test @ http://localhost:4567/
16 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 6.41ms 7.87ms 178.16ms 91.30%
Req/Sec 177.17 38.21 303.00 71.07%
63540 requests in 30.09s, 11.21MB read
Requests/sec: 2111.50
Transfer/sec: 381.50KB
require "kemal"
get "/" do
"Hello World!"
end
Kemal.run
(Looks familiar, eh?) Let’s (compile!), run, and benchmark:
% crystal build kemal.cr --release # compile
% ./kemal # run
% wrk -c16 -t16 -d30s http://localhost:3000/ # benchmark
Running 30s test @ http://localhost:3000/
16 threads and 16 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.69ms 9.52ms 223.64ms 94.47%
Req/Sec 1.34k 291.21 1.89k 71.22%
639215 requests in 30.04s, 73.76MB read
Requests/sec: 21280.90
Transfer/sec: 2.46MB
About an order-of-magnitude faster! (Don’t you just love a good microbenchmark?) Plus we didn’t have to sacrifice any of the clean, familiar syntax of Ruby.
As I dove deeper, I learned Crystal is (mostly) implemented in Crystal, especially its standard library. (Kind of like Rubinius, remember that?) And its a modern one with built-in JSON parsing and generation support and a nice HTTP client, for example.
Since things are just ramping up the barrier of entry to contributing to Crystal and its fledgling libraries is pretty low. Especially since you don’t have to write C to contribute.
I’m skeptical about Crystal’s long term future, and probably won’t use it for any client apps at the moment, but I am digging into it for side projects and starting to advocate for it in small areas where is makes sense in the hopes that it may one day get the support it needs to grow.