Advanced: Crystal vs Ruby
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.