Considering that Crystal is heavily influenced by Ruby, and subsequently frequently compared against each other, I want to, without going into much detail, introduce some of the ways that these two languages are different when it comes to some advanced language features.

Metaprogramming

Among the advanced language features of Crystal and Ruby, there is metaprogramming support. This particular language feature in Ruby is one of the many reasons that made Ruby on Rails so successful (via the dynamic ORM methods of ActiveRecord). In metaprogramming, there is one notable specific feature that makes this language feature so powerful, and that is the ability to create new methods dynamically (at runtime or compile time). Let’s take a look how you accomplish this in both languages…

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env ruby

class Dog
  def bark
    puts "woof!"
  end
end

dog = Dog.new
dog.bark

dog.class.define_method(:name) { puts "Matsu!" }
dog.name

The key line in the example above is line 12, where we modify the class data structure of the object by defining a new instance method called name.

To read more about the metaprogramming features in Ruby, go here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env crystal

class Dog
  def bark
    puts "woof!"
  end

  macro define_method(method_name, content)
    def {{method_name.id}}
      {{content}}
    end
  end

  def yield_with_self
    with self yield
  end
end

Dog.define_method name, { puts "Matsu" }

dog = Dog.new
dog.bark
dog.yield_with_self { name }

A few differences with Crystal:

  • Unlike in Ruby, where you can alter an object’s data structure at runtime, say by adding a new method, as a result of Crystal being a compile language, as opposed to an interpreted language like Ruby is, you cannot alter an objec’ts data structure at runtime
  • Use the Macro data structure to create AST nodes to produce code at compile time
  • Then, in order to use said produced code, while keeping the scope in mind, call on the produced code, like in line 19 where we call the define_method method to create a new method at the class level (as opposed to at the object level)
  • To use any method created at runtime, we need to tell the object to search for those methods within the context of itself, like we do in line 23 by using the yield_with_self method
  • Last, notice in all of this that basic metaprogramming is not as easy or intuative as that in Ruby

To read more about the metaprogramming features in Crystal, go here.

Concurrency

Without going into any depth…

In (recent versions of) Ruby, concurrency is achieved via Fiber or Ractor.

In Crystal, concurrency is achieved via Fiber and Channels.

C-Bingings

Without going into any depth…

In Ruby, to bind to an external library you can do it manually like so or use third-party tools to help (gems) like ffi.

In Crystal, you can bind to C libraries like so.

Conclusion

While both languages have similar advanced features (and of course there are advanced features in each that the other doesn’t have, but that is for another post), they vary in how they are used and/or how they work. Said variance is a result of a number of reasons, like the interpreted vs compiled nature of each, or the dynamically typed vs static typed nature of each, etc. Regardless, I find it immensenly interesting and powerful how you can have these two languages to use for you advantage for the right job.

That is all, thanks for reading.