Yesterday Crystal became one year old. Yay! :-)
A lot of things happened since the moment we started this rather ambitious project, and there is still a lot more to do.
Although its syntax is very similar to Ruby, there are many differences, and every day the distance is growing bigger.
Here’s a summary of what we have in the language right now.
Efficient code generation
Crystal is not interpreted. It doesn’t have a virtual machine. The code is compiled to native machine code by using LLVM.
You don’t specify the types of variables, instance variables or method arguments, like is usually done in statically compiled languages. Instead, Crystal tries to be as smart as possible and infers the types for you.
Primitive types map to native machine types.
They come in many flavors, like in Ruby, and they also support interpolation.
We still need to decide what’s the best way to deal with different encodings, so this is just a temporary implementation.
Did you know that String is implemented in Crystal itself? There’s just very small magic to make it have the size and pointer to the chars buffers, but everything else is built on top of that.
At runtime, each symbol is represented by a unique integer. A table of integer to string is built for implementing Symbol#to_s (but there’s no way right now to do String#intern).
You don’t need to specify the type of a variable. If it is assigned multiple types, it will have those types at compile-time. At run-time it will have only one.
You can use “is_a?” to check for a type:
You can even use “responds_to?”:
In Crystal methods can be overloaded. The overloads come from the number of arguments, type restrictions and yieldness of a method.
Contrast this with having to check at runtime the number of arguments of the method, whether a block was given, or which are the types of arguments: we believe this is much more readable and efficient.
Also, there’s no “wrong number of arguments” exception thrown at runtime: in Crystal it’s a compile time error.
The last bit, overloading based on whether a method yields or not, will probably change and needs to be re-thought.
No need to specify the types of instance variables, but all types assigned to an instance variable will make that variable have a union type.
If you really need different Foo classes with different types for @value, you can use a generic class:
Array and Hash are generic classes too, but they can also be constructed using literals. When elements are specified, the generic type variables are inferred. If no element is specified, you have to tell Crystal the generic type variables.
We really wanted to avoid having to specify type variables. In fact, we wanted to avoid having to differentiate between generic and non-generic classes. We spent a long time (maybe three months?) trying to make it work efficiently but we couln’t. Maybe it doesn’t have an efficient solution. At least we didn’t find anyone who was able to do it. Generic types are a small sacrifice, but in return we get much faster compile times.
Of course, modules are also present in Crystal, and they can also be generic.
For now, blocks can’t be saved to a variable or passed to another method. That means, we still lack closures. It’s not an easy thing to do if we want a smart type inference, so we need some time to think it over.
Bindings to C
You can declare bindings to C in Crystal, no need to use C, make wrappers or use another language. For example, this is part of the SDL binding:
You can allocate memory and interface with C by having Pointers as a type in the language.
Regular expressions are implemented, for now, with C bindings to the PCRE library. Again, Regexp is entirely written in Crystal.
Once again, implemented in Crystal.
You can raise and rescue exceptions. They are implemented using libunwind. We are still lacking line numbers and filenames in the stacktraces.
Exporting C functions
You can declare functions to be exported to C, so you can compile Crystal code and use it in C (although there’s still no compiler flag to generate an object file, but it should be easy to implement).
This is an experimental feature of the language where you can generate source code from AST nodes.
The macros getter, setter and property are implemented in a similar way, but we’ve been thinking of a more powerful and simpler way to achieve the same thing so this feature might disappear.
Yield with scope
Similar to yield, but changes the implicit scope of the block.
This allows writing powerful DSLs with zero overhead: no allocations or closures are involved.
Something similar can be achieved in Ruby with instance_eval(&block), but for now we find it easier to implement it this way, and maybe easier to use.
We’be built a very small clone of RSpec and we are using it to test the standard library as well as the new compiler. Here’s a sample spec for the Array class:
So Crystal makes it extremely easy to write tests, and at the same time gives you type safety. The best of both worlds.
There are a lot more things to implement and we have many ideas to try out.
- We still need a Garbage Collector, and we want an efficient, concurrent one.
- We want to have concurrency primitives, similar to Erlang or Go.
- We want to have better metaprogramming.
- We might have structs, not only for C bindings, to allow writing efficient wrappers and allocating less memory.
- We want tuples, named tuples and named arguments.
But the thing we want most right now is to have the compiler written in Crystal. Once we do this, we won’t need Ruby anymore. We won’t need to maintain two implementations of the compiler. Compilation times will be reduced drmatically (we hope!).
And also, it’s very likely that the language will grow a lot because of this, and we will have learned a lot of what it feels like to program in Crystal and what needs to be improved (debugging and profiling come to our minds). Dog-fooding, they call it :-)