The Programming Language

Happy birthday, Crystal!

04 Sep 2013 by asterite

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

Primitive types map to native machine types.

true    # Bool
1       # Int32
1_u64   # UInt64
1.5     # Float64
1.5_f32 # Float32
'a'     # Char

ASCII Strings

They come in many flavors, like in Ruby, and they also support interpolation.

a = "World"
b = "Hello #{a}" #=> "Hello World"

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).

Union types

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.

if some_condition
  a = 1
  a = 1.5

# Here a can be an Int32 or Float64

a.abs  # Ok, both Int32 and Float64 define the 'abs' method without arguments
a.succ # Error, Float64 doesn't have a 'succ' method

You can use “is_a?” to check for a type:

if a.is_a?(Int32)
  a.succ # Ok, here a can only be an Int32

You can even use “responds_to?”:

if a.responds_to?(:succ)
  a.succ # Ok


In Crystal methods can be overloaded. The overloads come from the number of arguments, type restrictions and yieldness of a method.

# foo 1
def foo(x, y)
  x + y

# foo 2
def foo(x)

# foo 3
def foo(x : Float)

def foo(x)

foo 1, 1      # Invokes foo 1
foo 1         # Invokes foo 2
foo 1.5       # Invokes foo 3
foo(1) { }    # Invokes foo 4

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.

class Foo
  # We prefer getter, setter and property over
  # attr_reader, attr_writer and attr_accessor
  getter :value

  # Note the @value at the argument: this is similar to Coffeescript
  # and we think it's a nice syntax addition.
  def initialize(@value)

foo =
foo.value.abs # Ok

# At this point @value is an Int32

foo2 ='a')

# Because of the last line, @value is now an Int32 or Char.
# Char doesn't have an 'abs' method, so a compile time error is issued.

If you really need different Foo classes with different types for @value, you can use a generic class:

class Foo(T)
  getter :value

  def initialize(@value : T)

foo =    # T is inferred to be an Int32, and foo is a Foo(Int32)
foo.value.abs       # Ok

foo2 ='a') # T is inferred to be a Char, and foo2 is a Foo(Char)
foo2.value.ord            # Ok

# You can also explicitly specify the generic type variable
foo3 = Foo(String).new("hello")

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.

a = [1, 2, 3]            # a is an Array(Int32)
b = [1, 1.5, 'a']        # b is an Array(Int32 | Float64 | Char)
c = [] of String         # c is an Array(String), same as doing Array(String).new

d = {1 => 2, 3 => 4}     # d is a Hash(Int32, Int32)
e = {} of String => Bool # e is a Hash(String, Bool), same as doing Hash(String, Bool).new

And yes, Array and Hash are completely implemented in Crystal. This makes it very easy for anyone to collaborate on those classes.

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:

lib LibSDL("SDL")
  INIT_TIMER       = 0x00000001_u32
  INIT_AUDIO       = 0x00000010_u32
  # ...
  struct Rect
    x, y : Int16
    w, h : UInt16
  # ...
  union Event
    type : UInt8
    key : KeyboardEvent
  # ...
  fun init = SDL_Init(flags : UInt32) : Int32

value = LibSDL.init(LibSDL.INIT_TIMER)


You can allocate memory and interface with C by having Pointers as a type in the language.

values = Pointer(Int32).malloc(10) # Ask for 10 ints

Regular expressions

Regular expressions are implemented, for now, with C bindings to the PCRE library. Again, Regexp is entirely written in Crystal.

"foobarbaz" =~ /(.+)bar(.+)/ #=> 0
$1                           #=> "foo"
$2                           #=> "baz


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).

fun my_c_function(x : Int32) : Int32
  "Yay, I can use string interpolation and call it #{x} times from C"


This is an experimental feature of the language where you can generate source code from AST nodes.

macro generate_method(name, value)
  def #{name}

generate_method :foo, 1

puts foo # Prints: 1

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.

def foo
  # -1 becomes the default scope where methods
  # are looked up in the given block

foo do |x|
  # Invokes "abs" on -1
  puts abs + x #=> 3

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:

require "spec"

describe "Array" do
  describe "index" do
    it "performs without a block" do
      a = [1, 2, 3]
      a.index(3).should eq(2)
      a.index(4).should eq(-1)

    it "performs with a block" do
      a = [1, 2, 3]
      a.index { |i| i > 1 }.should eq(1)
      a.index { |i| i > 3 }.should eq(-1)

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.

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 :-)

comments powered by Disqus