authors/marksiemers.jpg Mark Siemers 1 week and 6 days ago

Top 5 Reasons for Ruby-ists to Use Crystal

This is a guest post by Mark Siemers, who kindly volunteered to suggest a series of Crystal blog posts. Expect more to come, or - even better - contact us about writing your own post.

1. Extremely low learning curve

Think of some languages that have become popular in the last 5-10 years. What comes to mind? Elixir, Go, Rust perhaps? They all have performance advantages over Ruby but are more difficult to learn and master.

What if you could get the performance gain with a much easier learning curve?

How easy? Let’s take a look at some code.

Q: Which of the following modules are written in Ruby? Which in Crystal?

module Year
  def self.leap?(year)
    year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
  end
end
module Hamming
  def self.distance(a,b)
    a.chars.zip(b.chars).count{|first, second| first != second }
  end
end

A: Trick question - it’s both. The modules above will work in Ruby or Crystal. How cool is that?

See more examples of code similarities here.

This doesn’t mean that all Ruby code will work in Crystal (and vice-versa), but you can do an awful lot with Crystal immediately and be productive on day one, even minute one.

How does Crystal (a strongly-typed & compiled language) act like Ruby (a dynamic & duck-typed language)? Crystal’s compiler uses a combination of two powerful techniques: type inference and union types. This allows the compiler to read your ruby-like code and figure out (infer) the correct types to use.

Beyond the similarities, Crystal offers some core advantages over Ruby. Advantages like…

2. Compile time checks & method overloading

Does it feel hacky when you write is_a? or respond_to? to make sure your code doesn’t break in Ruby? Do you ever worry about all the places you did NOT put in those checks? Are those all bugs waiting to happen?

Crystal is a compiled language and checks all your method inputs and outputs at compile time. If any types don’t line-up, they will be caught before runtime.

Let’s revisit the Year::leap? example from above. In Ruby, what happens when the input isn’t an integer?

Year.leap?("2016") #=> false
Year.leap?(Date.new(2016, 1, 1)) #=> undefined method `%' for #<Date: 2016-01-01 ... >

For a String we get the wrong answer, for a Date we get a runtime exception. Fixing things in Ruby would require at least one is_a? statement:

module Year
  def self.leap?(input)
    if input.is_a? Integer
      input % 400 == 0 || (input % 100 != 0 && input % 4 == 0)
    elsif input.is_a? Date
      input.leap?
    else
      raise ArgumentError.new("must pass an Integer or Date.")
    end
  end
end

Does that method look good to you? We still have a chance at a runtime exception, just with a more helpful error message.

In Crystal, we have the option of explicitly typing our inputs (and outputs). We can change the method signature to self.leap?(year : Int) and we are guaranteed to have an integer as input.

We get helpful messages at compile time, rather than runtime:

Year.leap?("2016")
Error in line 10: no overload matches 'Year.leap?' with type String
Overloads are:
 - Year.leap?(year : Int)

If we want to add support for Time (think DateTime in Ruby) in our module, we can overload Year::leap?:

module Year
  def self.leap?(year : Int)
    year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
  end

  def self.leap?(time : Time)
    self.leap?(time.year)
  end
end

Like Ruby, method overloading allows flexibility with inputs but without the guesswork of duck-typing. Compile time checks prevent type mismatch errors from making it to production.

Speaking of production, how about…

3. Blazing fast performance

Another advantage of compilation is speed and optimization. When comparing the performance of Ruby to Crystal, often, it can be stated in orders of magnitude rather than percentages.

In one example, summing random numbers in crystal can be 10 orders of magnitude faster than Ruby (~ 37 million percent faster). This is due to compiler optimizations and the ability to use primitive data types in Crystal. This does come with the risk of integer overflow for large numbers (See Ary’s explanation).

Crystal’s built-in HTTP server has been able to handle over 2 million requests per second in benchmark testing. And many of the web frameworks are consistently delivering sub-millisecond response times for web applications.

Which brings us to the next point…

4. The web framework you want is already here

Love the completeness of Rails (or even Elixir’s Phoenix)? You’ll feel right at home with the Amber framework.

Is the simplicity and easy customization of Sinatra more your thing? You’ll find that simplicity with Kemal.

Do you want a full-stack framework that leverages compile time checks for strong params, HTTP verbs, and database queries? It’s your Lucky day.

During January, each of these web frameworks will be highlighted in their own dedicated post. Check back with the Crystal blog to find out more.

5. Crystal is written in Crystal! It’s easy to understand and contribute to the language

Have you ever read Ruby’s source code? Tried to figure out how some Enumerable methods work?

Ruby’s Implementation of Enumerable#all?

static VALUE
enum_all(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
    return memo->v1;
}

How long does it take to figure out what that code is doing? If you’ve never worked with C code, probably a really long time.

Compare that to Crystal’s implementation of Enumerable#all?

def all?
  each { |e| return false unless yield e }
  true
end

How long did that take to figure out? If you know Ruby or Crystal, probably a matter of seconds.

With this in mind, consider that 98.4% of Crystal is written in Crystal and only 0.3% is written in C++.

Ruby is written 30.6% in C and 64.8% in Ruby.

In fact, only one file in crystal is written in C++, so as long as your aren’t changing LLVM extensions whatever you’re looking for in the crystal language is virtually guaranteed to be written in crystal.

Reading, understanding, and contributing to Crystal is easier than just about any language you can find.

Where to start?

If you’re looking to try out the Crystal programming language, here are some good resources to get started:

This is a guest post by Mark Siemers, who kindly volunteered to suggest a series of Crystal blog posts. Expect more to come, or - even better - contact us about writing your own post.

1. Extremely low learning curve

Think of some languages that have become popular in the last 5-10 years. What comes to mind? Elixir, Go, Rust perhaps? They all have performance advantages over Ruby but are more difficult to learn and master.

What if you could get the performance gain with a much easier learning curve?

How easy? Let’s take a look at some code.

Q: Which of the following modules are written in Ruby? Which in Crystal?

module Year
  def self.leap?(year)
    year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
  end
end
module Hamming
  def self.distance(a,b)
    a.chars.zip(b.chars).count{|first, second| first != second }
  end
end

A: Trick question - it’s both. The modules above will work in Ruby or Crystal. How cool is that?

See more examples of code similarities here.

This doesn’t mean that all Ruby code will work in Crystal (and vice-versa), but you can do an awful lot with Crystal immediately and be productive on day one, even minute one.

How does Crystal (a strongly-typed & compiled language) act like Ruby (a dynamic & duck-typed language)? Crystal’s compiler uses a combination of two powerful techniques: type inference and union types. This allows the compiler to read your ruby-like code and figure out (infer) the correct types to use.

Beyond the similarities, Crystal offers some core advantages over Ruby. Advantages like…

2. Compile time checks & method overloading

Does it feel hacky when you write is_a? or respond_to? to make sure your code doesn’t break in Ruby? Do you ever worry about all the places you did NOT put in those checks? Are those all bugs waiting to happen?

Crystal is a compiled language and checks all your method inputs and outputs at compile time. If any types don’t line-up, they will be caught before runtime.

Let’s revisit the Year::leap? example from above. In Ruby, what happens when the input isn’t an integer?

Year.leap?("2016") #=> false
Year.leap?(Date.new(2016, 1, 1)) #=> undefined method `%' for #<Date: 2016-01-01 ... >

For a String we get the wrong answer, for a Date we get a runtime exception. Fixing things in Ruby would require at least one is_a? statement:

module Year
  def self.leap?(input)
    if input.is_a? Integer
      input % 400 == 0 || (input % 100 != 0 && input % 4 == 0)
    elsif input.is_a? Date
      input.leap?
    else
      raise ArgumentError.new("must pass an Integer or Date.")
    end
  end
end

Does that method look good to you? We still have a chance at a runtime exception, just with a more helpful error message.

In Crystal, we have the option of explicitly typing our inputs (and outputs). We can change the method signature to self.leap?(year : Int) and we are guaranteed to have an integer as input.

We get helpful messages at compile time, rather than runtime:

Year.leap?("2016")
Error in line 10: no overload matches 'Year.leap?' with type String
Overloads are:
 - Year.leap?(year : Int)

If we want to add support for Time (think DateTime in Ruby) in our module, we can overload Year::leap?:

module Year
  def self.leap?(year : Int)
    year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
  end

  def self.leap?(time : Time)
    self.leap?(time.year)
  end
end

Like Ruby, method overloading allows flexibility with inputs but without the guesswork of duck-typing. Compile time checks prevent type mismatch errors from making it to production.

Speaking of production, how about…

3. Blazing fast performance

Another advantage of compilation is speed and optimization. When comparing the performance of Ruby to Crystal, often, it can be stated in orders of magnitude rather than percentages.

In one example, summing random numbers in crystal can be 10 orders of magnitude faster than Ruby (~ 37 million percent faster). This is due to compiler optimizations and the ability to use primitive data types in Crystal. This does come with the risk of integer overflow for large numbers (See Ary’s explanation).

Crystal’s built-in HTTP server has been able to handle over 2 million requests per second in benchmark testing. And many of the web frameworks are consistently delivering sub-millisecond response times for web applications.

Which brings us to the next point…

4. The web framework you want is already here

Love the completeness of Rails (or even Elixir’s Phoenix)? You’ll feel right at home with the Amber framework.

Is the simplicity and easy customization of Sinatra more your thing? You’ll find that simplicity with Kemal.

Do you want a full-stack framework that leverages compile time checks for strong params, HTTP verbs, and database queries? It’s your Lucky day.

During January, each of these web frameworks will be highlighted in their own dedicated post. Check back with the Crystal blog to find out more.

5. Crystal is written in Crystal! It’s easy to understand and contribute to the language

Have you ever read Ruby’s source code? Tried to figure out how some Enumerable methods work?

Ruby’s Implementation of Enumerable#all?

static VALUE
enum_all(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
    return memo->v1;
}

How long does it take to figure out what that code is doing? If you’ve never worked with C code, probably a really long time.

Compare that to Crystal’s implementation of Enumerable#all?

def all?
  each { |e| return false unless yield e }
  true
end

How long did that take to figure out? If you know Ruby or Crystal, probably a matter of seconds.

With this in mind, consider that 98.4% of Crystal is written in Crystal and only 0.3% is written in C++.

Ruby is written 30.6% in C and 64.8% in Ruby.

In fact, only one file in crystal is written in C++, so as long as your aren’t changing LLVM extensions whatever you’re looking for in the crystal language is virtually guaranteed to be written in crystal.

Reading, understanding, and contributing to Crystal is easier than just about any language you can find.

Where to start?

If you’re looking to try out the Crystal programming language, here are some good resources to get started:

comments powered by Disqus