We’re all used to having dynamically-loaded shared libraries for C and C++ programs. The effectiveness of shared libraries depends on having an API with well-defined types. This is good for C, which allows only one type per argument, and C++, which allows some type flexibility based on abstract types and inheritance.
Crystal, however, is not like this (see my introduction to Crystal). The duck-typing in Crystal is somewhat similar to C++ templates, but without the ugly syntax. A function like this:
def add(a, b) a + b end
… can take any type for a and b, as long as they implement the + method. The code compiled and the return value will be different depending on the types provided. Because Crystal has global type inference, you need to compile the entire program, including the library, every time. That allows types to be inferred through the library code and its return to the caller.
So, shared libraries don’t make much sense in this context. They work because they have a tightly-typed API, which normal Crystal programs wouldn’t, and they assume that modules are independent of each other, which breaks down with modern language features like duck typing and type inference.
The cost is that we have somewhat more memory usage than we might for the equivalent C program. The benefit is that we use less programmer time on development and debugging, and we get increased code correctness because any incompatible type (including nil) is flagged as an error. Ultimately, memory is much cheaper than programmers.
There is also a potential performance increase over C, because there will be less need for the automatic type conversion with which C math operations are liberally salted, and code will be optimized for the type. But any real speed advantage over C has not yet been demonstrated.
Where optimization beyond what Crystal can provide is necessary, we can turn to assembly and Crystal’s foreign function interface. In general, the need for this would be rare.