Because it’s compiled, in general the same strategies that might apply to another compiled language like C or C++ would apply to Crystal too.
One option: dynamic libraries. These can be defined using a usual C ABI and loaded via
dlopen, for example. However, this would require a more low-level, C ABI-based plugin system which might not be what you want, since you’d probably need to pass around pointers and stuff and use lower-level constructs than what you’d have available in native Crystal-land. (I’ve not actually attempted this, so there might be some details missing here, but I think that’s the gist. For example,
lib bindings in Crystal may or may not work with libraries loaded via
dlopen, I’m not sure.)
Another strategy is the one you’ve alluded to, which would be to embed another language into Crystal that can dynamically load and interpret code that interacts with your program via some API you’ve exposed to that language’s runtime. This is definitely more effort up front as there are not a ton of existing scripting language runtimes with bindings for Crystal, but could offer a more friendly, high-level experience for users. It’s an approach that I’m personally more interested in doing for that reason. For example, one of my long-term plans is to build an
mruby binding for Crystal so that Ruby can be used for a plugin-like interface to a Crystal program. The biggest caveat with this approach is your program would either need to dynamically link and depend on some libraries for that language runtime or you’d need to statically link, but most embeddable languages should support both.