Crystal is lacking in popularity and the adoption rate is still low. I’m a ruby developer and we need features provided by crystal in our, now larger, codebase, but it’s hard to justify rewriting to Crystal all the business logic that already works. Even extracting parts, that will benefit the most, is hard to justify because Crystal infrastructure and libraries are way less mature than ruby.
That’s why I think that ability to embed Crystal code in ruby projects will significantly benefit Crystal popularity and adoption rate. It will do the same function that Sorbet Compiler: An experimental, ahead-of-time compiler for Ruby · Sorbet do but in a much better and cleaner way
IMHO: it should be just a gem that contains all required glue logic in ruby and Crystal. It should invoke the compiler (or even have it embedded) with appropriate parameters to compile the code to the ruby extension and load the extension. It should just search for *.cr files inside a project and compile them on boot or on require
from ruby code
AOT option should also be available.
It should not be fully smooth or transparent, it should be easy to use and understand. It should not try to provide guarantees that are hard to implement and maintain. There is not much need to make Ruby->Crystal and Crystal->Ruby type conversions 100% smooth and compatible. e.g. when a ruby string is passed to Crystal function, it may be just copied to Crystal string and the original can’t be modified in Crystal.
Ruby classes can be represented in Crystal code as generic RbObject class and use method_missing macro to invoke ruby methods. RBS definitions may be used to generate more specific wrappers, but I doubt that it is worth the effort to implement and maintain.
Conversions for the most popular types from the Ruby standard library should be implemented.
# in ruby:
class SomeRubyClass
def some_method(param)
param + 42
end
end
CrystalClass.new.rb_method(SomeRubyClass.new)
# in crystal
@[ExposeClass]
class CrystalClass
def rb_method(in : RbObject)
in.some_method(23)
end
end
It’ll be hard to expose all Crystal classes and methods to ruby. As far as I understand, JIT compilation will be required to generate code for methods with unknown parameter types.
For simplicity, Crystal classes, that should be exposed to ruby code, should be annotated, and exposed methods should either have all argument types defined or be annotated with possible argument types. As all exposed methods may only receive and return exposed types, most popular types from Crystal standard library should be exposed or conversions to ruby types should be implemented.
@[ExposeClass]
class SampleClass
# exposed, no params
def do_smth
1
end
# exposed, param type is defined
def add42(num : Int32)
num+42
end
# exposed, param type is defined in the annotation
@[ExposeMethod(num : Float)]
def add69(num)
num+69
end
end
# in ruby
require 'sample_class'
s = SampleClass.new
s.add42(1)
Other things to be done:
Compile to ruby extension. It should be possible to load one or multiple extensions compiled from crystal code. As far as I understand, distributed gem should load Crystal context (code, that is now executed before Crystal main) on boot and load the extension later during boot or on demand
GC interoperability - No idea how to achieve that. Maybe, both GCs may be used simultaneously and objects may just be marked “static”, while they are owned by wrapper objects of the other language. Or maybe crystal can use ruby GC
Make Threads, and Fibers interoperable, handle ruby GIL