Chapter 1: An Introduction to Crystal
Crystal is a safe, performant, general-purpose, and object-oriented language. It was heavily inspired by Ruby's syntax and Go's and Erlang's runtimes, enabling a programmer to be very productive and expressive while creating programs that run efficiently on modern computers.
Crystal has a robust type system and can compile to native programs. Consequently, most programming errors and mistakes can be identified at compile time, giving you, among other things, null safety. Having types doesn't mean you have to write them everywhere, however. Crystal relies on its unique type interference system to identify the types of almost every variable in the program. Rare are the situations where the programmer has to write an explicit type somewhere. But when you do, union types, generics, and metaprogramming help a lot.
Metaprogramming is a technique where a structured view of the written program is accessed and modified by the program itself, producing new code. This is a place where Ruby shines with all its dynamism and built-in reflection model, and so does Crystal, in its own way. Crystal is capable of modifying and generating code during compilation time with macros and a zero-cost static reflection model. It feels like a dynamic language in every way, but it will compile the program down to pure and fast machine code.
Code written in Crystal is expressive and safe, but it's also fast – really fast. Once built, it goes head to head with other low-level languages such as C, C++, or Rust. It beats pretty much any dynamic language and some compiled languages too. Although Crystal is a high-level language, it can consume C libraries with no overhead, the lingua franca of system programming.
You can use Crystal today. After 10 years of intense development and testing, a stable and production-ready version was released in early 2021. Alongside it, a complete set of libraries (called "shards") are available, including web frameworks, database drivers, data formats, network protocols, and machine learning.
This chapter will introduce a brief history of the Crystal language and present some of its characteristics regarding performance and expressiveness. After that, it will bring you up to speed by explaining how to create and run your first Crystal program. Finally, you will learn about some of the challenges for the future of the language.
In particular, we will cover the following topics:
- A bit of history
- Exploring Crystal's expressiveness
- Crystal programs are also FAST
- Creating our first program
- Setting up the environment
This should get you started on what Crystal is, understanding why it should be used, and learning how to execute your first program. This context is essential for learning how to program in Crystal, going from small snippets to fully functional and production-ready applications.
Technical requirements
As part of this chapter, you will install the Crystal compiler on your machine and write some code with it. For this, you will need the following:
- A Linux, Mac, or Windows computer. In the case of a Windows computer, the Windows Subsystem for Linux (WSL) needs to be enabled.
- A text editor such as Visual Studio Code or Sublime Text. Any will do, but these two have good Crystal plugins ready to use.
You can fetch all source code used in this chapter from the book's GitHub repository at https://github.com/PacktPublishing/Crystal-Programming/tree/main/Chapter01.
A bit of history
Crystal was created in mid 2011 at Manas Technology Solutions (https://manas.tech/), an Argentinian consulting company that worked a lot with creating Ruby on the Rails applications at that time. Ruby is an enjoyable language to work with but has always been questioned for its lacking performance. Crystal came to life when Ary Borenszweig, Brian Cardiff, and Juan Wajnerman started experimenting with the concept of a new language similar to Ruby. It would be a statically typed, safe, and compiled language with pretty much the same elegant syntax as Ruby but taking advantage of global type inference to remove runtime dynamism. Much has changed since then, but these core concepts remain the same.
The result? Today, Crystal is a stable and production-ready, 10-year-old language with over 500 contributors and a growing community. The team behind it successfully implemented a language with a fast concurrent runtime and a unique type inference system that looks at the entire program in one go while retaining Ruby's best features.
The initial motiving factor for the creators was performance. They enjoyed programming in Ruby and using Ruby's vast ecosystem, but the performance wasn't there. Ruby has improved a lot since then, but even today, there is a sensible gap compared to other dynamic languages such as Python or JavaScript.
It began with a simple idea – what if we could have the same expressiveness as Ruby, infer the types of all variables and arguments based on the call sites, and then generate native machine code similar to the C language? They began prototyping it as a side project in 2011, and it worked. Early on, it was adopted as a Manas project, allowing the trio to work on it during paid hours.
Crystal has been developed in the open since its very beginning in a public repository on GitHub at https://github.com/crystal-lang/crystal. It brought a community of users, contributors, and also sponsors banking on Crystal's success. The initial interest came from the Ruby community, but it quickly expanded beyond that. You can see in the following figure the growth in people interested in Crystal, measured by the number of GitHub "stars" on the main repository.

Figure 1.1 – The steady growth of GitHub stars
At the time of writing, the latest version is 1.2.2, and it can be installed from Crystal's official website, at https://crystal-lang.org/.
Much inspiration came from Ruby, but Crystal evolved into a different language. It kept the best pieces of Ruby but changed, improved, and removed some of its legacies. Neither language aim to be compatible with the other.
Understanding this history gives you the perspective to follow what motivated Crystal to be created and to evolve into what it is today. Crystal has grown to be very performant but also very expressive. Now, let's see what empowers this expressiveness.
Exploring Crystal's expressiveness
It is often said that Crystal is a language for humans and computers. This is because Crystal strives for a balance of being a surprisingly enjoyable language for programmers while also being very performant for machines. One cannot go without the other, and in Crystal, most abstractions come with no performance penalties. It has features and idioms such as the following:
- Object-oriented programming: Everything is an object. Even classes themselves are objects, that is, instances of the
Class
. Primitive types are objects and have methods, too, and every class can be reopened and extended as needed. In addition, Crystal has inheritance, method/operator overloading, modules, and generics. - Static-typed: All variables have a known type at compile time. Most of them are deduced by the compiler and not explicitly written by the programmer. This means the compiler can catch errors such as calling methods that are not defined or trying to use a value that could be null (or
nil
in Crystal) at that time. Variables can be a combination of multiple types, enabling the programmer to write dynamic-looking code. - Blocks: Whenever you call a method on an object, you can pass in a block of code. This block can then be called from the method's implementation with the
yield
keyword. This idiom allows all sorts of iterations and control flow manipulation and is widespread among Ruby developers. Crystal also has closures, which can be used when blocks don't fit. - Garbage collection: Objects are stored in a heap, and their memory is automatically reclaimed when they are no longer in use. There are also objects created from a struct, allocated in the stack frame of the currently executing method, and they cease to exist as soon as the method finishes. Thus, the programmer doesn't have to deal with manual memory management.
- Metaprogramming: Although Crystal isn't a dynamic language, it can frequently behave as if it were, due to its powerful compile-time metaprogramming. The programmer can use macros and annotations, together with information about all existing types (static reflection) to generate or mutate code. This enables many dynamic-looking idioms and patterns.
- Concurrent programming: A Crystal program can spawn new fibers (lightweight threads) to execute blocking code, coordinating with channels. Asynchronous programming becomes easy to reason and follow. This model was heavily inspired by Go and other concurrent languages such as Erlang.
- Cross-platform: Programs created with Crystal can run on Linux, macOS, and FreeBSD, targeting x86 or ARM (both 32-bit and 64-bit). This includes the new Apple Silicon chips. Support for Windows is experimental, it isn't ready just yet. The compiler can also produce small static binaries on each platform without dependencies for ease of distribution.
- Runtime safety: Crystal is a safe language – this means there are no undefined behaviors and hidden crashes such as accessing an array outside its bounds, accessing properties on
null
, or accessing objects after they have already been freed. Instead, these become either runtime exceptions, compile-time errors, or can't happen due to runtime protections. The programmer has the option of weaving safety by using explicitly unsafe features of the language when necessary. - Low-level programming: Although Crystal is safe, using unsafe features is always an option. Things such as working with raw pointers, calling into native C libraries, or even using assembly directly are available to the brave. Many common C libraries have safe wrappers around them ready to use, allowing them to use their features from a Crystal program.
At first glance, Crystal is very similar to Ruby, and many syntactic primitives are the same. But Crystal took its own road, taking inspiration from many other modern languages such as Go, Rust, Julia, Elixir, Erlang, C#, Swift, and Python. As a result, it keeps most of the good parts of Ruby's slick syntax while providing changes to core aspects, such as metaprogramming and concurrency.
Crystal programs are also FAST
From its very start, Crystal was designed to be fast. It follows the same principles as other fast languages such as C. The compiler can analyze the source code to know every variable's exact type and memory layout before execution. Then, it can produce a fast and optimized native executable without having to guess anything during runtime. This process is commonly known as ahead-of-time compilation.
Crystal's compiler is built upon LLVM, the same compiler infrastructure that powers Rust, Clang, and Apple's Swift. As a result, Crystal benefits from the same level of optimizations available to these languages, making it well suited for computationally intensive applications such as machine learning, image processing, or data crushing.
But not all applications are CPU-bound. Most of the time, there are other resources at stake, such as network communications or a local disk. Those are collectively known as I/O. Crystal has a concurrency model similar to Go's goroutines or Erlang's processes, where multiple operations can be performed behind an event loop without blocking the process or delegating too much work to the operating system. This model is ideal for applications such as web services or file manipulation tools.
Using an efficient language such as Crystal will help you reduce hardware costs and improve perceived responsiveness from your users. In addition, it means you can run smaller and fewer instances of your application to address the same processing volume.
Let's take a look at a simple implementation of the selection sort algorithm written in Crystal:
def selection_sort(arr) # For each element index... arr.each_index do |i| # Find the smallest element after it min = (i...arr.size).min_by { |j| arr[j] } # Swap positions with the smallest element arr[i], arr[min] = arr[min], arr[i] end end # Produce a reversed list of 30k elements list = (1..30000).to_a.reverse # Sort it and then print its head and tail selection_sort(list) p list[0...10] p list[-10..-1]
This example already shows some neat things about Crystal:
- First of all, it is relatively small. The main algorithm has a total of four lines.
- It's expressive. You can iterate over lists with specialized blocks or use ranges.
- There isn't a single type notation. Instead, the compiler deduces every type, including the method argument.
Surprisingly, this same code is also valid in Ruby. Taking advantage of that, if we take this file and run it as ruby selection_sort.cr
(note that Ruby doesn't care about file extensions), it will take about 30 seconds to finish. On the other hand, executing this program after it has been compiled with Crystal in optimized mode takes about 0.45 seconds, 60x less. Of course, this difference isn't the same for any program. It varies depending on what kind of workload you are dealing with. It's also important to note that Crystal takes time to analyze, compile, optionally optimize and produce a native executable.
The following graph shows a comparison of this selection sort algorithm written for a variety of languages. Here, you can see that Crystal competes near the top, losing to C and coming very close to Go. It is important to note that Crystal is a safe language: it has full exception handling support, it tracks bounds on arrays to avoid unsafe access, and it checks for overflow on integer math operations. C, on the other hand, is an unsafe language and won't check any of that. Having safety comes at a slight performance cost, but Crystal remains very competitive despite that:

Figure 1.2 – A comparison of a simple selection sort implementation among different languages
Note
Comparing different languages and runtimes in a synthetic benchmark such as this isn't representative of real-world performance. Proper performance comparisons require a problem more realistic than selection sort and a broad coding review from experts on each language. Still, different problems might have very different performance characteristics. So, consider benchmarking for your use case. As a reference for a comprehensive benchmark, consider looking into the TechEmpower Web Framework Benchmarks (https://www.techempower.com/benchmarks).
A web server comparison
Crystal isn't only great for doing computation on small cases but also performs well on larger applications such as web services. The language includes a rich standard library with a bit of everything, and you will learn about some of its components in Chapter 4, Exploring Crystal via Writing a Command-Line Interface. For example, you can build a simple HTTP server, such as this:
require "http/server" server = HTTP::Server.new do |context| context.response.content_type = "text/plain" context.response.print "Hello world, got #{context .request.path}!" end puts "Listening on http://127.0.0.1:8080" server.listen(8080)
The first line, require "http/server"
, imports a dependency from the standard library, which becomes available as HTTP::Server
. It then creates the server with some code to handle each request and starts it on port 8080
. This is a simple example, so it has no routing.
Let's compare this against some other languages to see how well it performs. But, again, this isn't a complex real-world scenario, just a quick comparative benchmark:

Figure 1.3 – A comparison of the request per second rate of simple HTTP servers among different languages
Here we see that Crystal is well ahead of many other popular languages (very close to Rust and Go) while also being very high-level and developer-friendly to code. Many languages achieve performance by using low level code, but it doesn't have to cost expressiveness or expose abstractions. Crystal code is simple to read and evolve. The same trend happens in other kinds of applications as well, not only web servers or microbenchmarks.
Now, let's get hands-on with using Crystal.
Setting up the environment
Let's prepare ourselves to create and run Crystal applications, which we will begin in the Creating our first program section. For this, the two most important things you will need are a text editor and the Crystal compiler:
- Text editor: Any code editor will get the job done, but using one with good plugins for Crystal will make life much easier. Visual Studio Code or Sublime Text are recommended. You can find more details about the editor setup in Appendix A.
- Crystal compiler: Please follow the installation instructions on Crystal's website at https://crystal-lang.org/install/.
After installing a text editor and the compiler, you should have a working Crystal installation! Let's check it: open up your terminal and type the following: crystal eval "puts 1 + 1"
:
s

Figure 1.4 – Evaluating 1 + 1 using Crystal
This command will compile and execute the puts 1 + 1
Crystal code, which writes the result of this computation back to the console. If you see 2
then all is set and we can move forward to writing actual Crystal code.
Creating our first program
Now let's experiment with creating our first program using Crystal. This is the basis for how you will write and execute code for the remainder of this book. Here is our first example:
who = "World" puts "Hello, " + who + "!"
After that, perform the following steps:
- Save this on a file called
hello.cr
. - Run it with
crystal run hello.cr
on your terminal. Note the output. - Try changing the
who
variable to something else and running again.
There is no boilerplate code such as creating a static class or a "main" function. There is also no need to import anything from the standard library for this basic example. Instead, you can just start coding right away! This is good for quick scripting but also makes applications simpler.
Note that the who
variable doesn't need to be declared, defined, or have an explicit type. This is all deduced for you.
Calling a method in Crystal doesn't require parentheses. You can see puts
there; it's just a method call and could have been written as puts("Hello, " + who + "!")
.
String concatenation can be done with the + operator. It's just a method defined on strings, and you'll learn how to define your own in later chapters.
Let's try something else, by reading a name inputted by the user:
def get_name print "What's your name? " read_line end puts "Hello, " + get_name + "!"
After that, we'll do this:
- Save the above code on a file called "
hello_name.cr
". - Run it with
crystal run hello_name.cr
on your terminal. - It will ask you for your name; type it and press Enter.
- Now, run it again and type a different name. Note the output changing.
In this example, you created a get_name
method that interacts with the user to obtain a name. This method calls two other methods, print
and read_line
. Note that as calling a method doesn't require parentheses, a method call without arguments looks precisely like a variable. That's fine. Also, a method always returns its last expression. In this case, the result of get_name
is the result of read_line
.
This is still simple, but will get you started on writing more complex code later on. Here, you can already see some console interaction and the use of methods for code reusability. Next let's see how you can make a native executable out of this code.
Creating an executable
When you need to ship your application, either to your end user's computer or to a production server, it isn't ideal to send the source code directly. Instead a better approach is to compile the code down to a native binary executable. Those are more performant, hard to reverse-engineer, and simpler to use.
So far, you have been using crystal run hello.cr
to execute your programs. But Crystal has a compiler, and it should also produce native executables. This is possible with another command; try crystal build hello.cr
.
As you will see, this won't run your code. Instead, it will create a "hello
" file (without an extension), which is a truly native executable for your computer. You can run this executable with ./hello
.
In fact, crystal run hello.cr
works mostly as a shorthand for crystal build hello.cr && ./hello
.
You can also use crystal build --release hello.cr
to produce an optimized executable. This will take longer, but will apply several code transformations to make your program run faster. For more details on how to deploy a final version of your application, take a look at Appendix B, The Future of Crystal.
Summary
Crystal delivers very well on performance, stability, and usability. It is a complete language with a growing community and ecosystem that can be used in production today. Crystal is highly innovative and has all the components of a successful programming language.
Knowing how to create and run Crystal programs will be fundamental in the following chapters, as there will be many code examples for you to try.
Now that you know about Crystal's origins and the significant characteristics of the language (namely its expressiveness and performance), let's move forward to learn the basics of programming in Crystal and get you started and productive in the language.