A first look at Crystal as a Gopher
Tags: crystal, ruby, go, codenewbie
Published: 2020-10-02 21:06:15

---
title: A first look at Crystal as a Gopher
published: true
description: 
tags: #crystal, #ruby, #go, #codenewbie
---

[Crystal](https://crystal-lang.org) is a fairly new language, which started in 2014, and it's got some cool features:

🏷 Static types
🐹 Go-like concurrency
🚀 Fast binaries as a compiled language
💎 and Ruby's satisfying syntax

Coming from Go, which I've been doing at work for five years, and hearing about Crystal having these features, I had to give that a try! So I'd like to show you around Crystal from a Gopher's eyes!

## 🐣 Starting a project

The first step after downloading Crystal, of course, was to actually start a project. For that, there's a command, `crystal init`, with two subcommands, `app` for making a program in Crystal, and `lib` for making a code library. So I started my app by running:

```
crystal init app first-app
```

Something I noticed right away is how much stuff you get out of just that command. You get things like:

* A `shard.yml` file, which from a first look seems similar to `package.json` in Node as a central manifest for the project.
* A license file for people using your software, which by default is the permissive MIT license.
* A well-templated README file.
* A sensible .gitignore to keep Git from keeping track of files like dependencies you imported.
* A `src` directory to put your code in.
* and even a `spec` directory to write automated tests for your code.

I really like having this all set up because all these things are things you'll want in a professional project, and having that all scaffolded means less agonizing about where to put everything.

## 🏗 Building a project

We've got a Crystal file ready to go over at `src/first-app.cr`:

```ruby
# TODO: Write documentation for `First::App`
module First::App
  VERSION = "0.1.0"

  # TODO: Put your code here
end
```

In Ruby, the word for "print" is "puts" (put S), so let's try that function printing out a hello world program.

```diff
  module First::App
    VERSION = "0.1.0"

-   # TODO: Put your code here
+   puts "Suplol, world!"
  end
```

We can then run the program through the crystal command by running `crystal run src/first-app.cr`, similar to how if we have a program in Go, we can run it with `go run main.go`.

```
Suplol, world!
```

Notice, by the way, that there's no main function like languages like Go and C have. Instead, we run our call to `puts` as "main code", as explained [here](https://crystal-lang.org/reference/syntax_and_semantics/the_program.html).

But instead of just just running one Crystal file, let's make it into a binary. Inside a Go package, we'd do something like `go build` to build a binary for a package, or `go install` to install that resulting binary. In Crystal, you can build from one file with `crystal build`, but since we're in a project, let's try building the project with `shards`, Crystal's package manager (similar to npm/Yarn in JavaScript). Run this:

```
shards build
```

Now you should see a `bin` directory, so you can run `./bin/first-app` to print the message.

You can see more of what `shards` can do [here](https://crystal-lang.org/reference/the_shards_command/).

## 👀 Checking out the standard library

Similar to Go, Crystal has an enormous [standard library](https://crystal-lang.org/api/0.35.1/index.html) that gives you a lot to work with, without even installing any dependencies! There's code for working with strings and I/O, a JSON serialization module, and an HTTP server that works right out of the box.

So let's try making an app that prints out a string in magenta if it contains the word "sloth", since sloths love hibiscus flowers. First thing we need to do is figure out if a string contains that word. In Go, to make a function for checking if a string is slothful, we would use the `strings` package, like this:

```go
package main

import (
    "strings"
)

func isSlothful(s string) bool {
    return strings.Contains(s, "sloth")
}
```

Scrolling through the navigator in the Crystal docs, we can see that there is a section titled [String](https://crystal-lang.org/api/0.35.1/String.html). But it's not a package the way Go's strings package is, it's a **type**. And in Crystal, every type, even a string, can have methods. So effectively, in Crystal, the strings package is defined as methods on the String type!

The String page reads similar to a Godoc for the string package, showing us around a package and its methods. And `String` has an impressive number of methods! The one we want is called [includes?](https://crystal-lang.org/api/0.35.1/String.html#includes?(search:Char%7CString)-instance-method), which works like `strings.Contains` in Go.

```ruby
def includes?(search : Char | String)
```

The function signature, `Char | String` is a "union type" or "either-or type" indicates that a string can take in either a character like 'A', or a whole string like 'sloth'. I find types like that convenient so the same function can work with similar types.

To give it a try, let's define our own `is_slothful` function. Go back to `first-app.cr`, and add this method to the `First::App` module:

```ruby
  def self.is_slothful?(s: String)
    s.includes? "sloth"
  end
```

We're putting the self prefix on the function we're defining to make `is_slothful?` a [class method](https://crystal-lang.org/reference/syntax_and_semantics/class_methods.html) of our `First::App` module.

Also notice that we didn't need a return statement, or parentheses around the arguments to `includes?`. This is just like in Ruby; parentheses on function calls are only needed to resolve ambiguity, and if there's no return in a function, the return value is what the last statement evaluates to.

To try this out, replace `puts "Suplol, world!"` with `puts is_slothful? "Suplol, world!"`.

Then, re-compile with `shared build`, and when you run the binary, "false" should be printed. Change it to "Suplol, slothful world!" and "true" should be printed.

## 📋 Adding test coverage

One of my favorite things about Go is that the Go command line program has a `test` subcommand, so you can make automated tests without any dependencies. And Crystal does that too, with `crystal spec`, which runs all the tests in the `spec` directory. And lucky us, since we made our project with `crystal init`, we already have a spec directory made. Go to `spec/first-app_spec.cr`, and you can see a simple suite of tests:

```ruby
require "./spec_helper"

describe First::App do
  # TODO: Write tests

  it "works" do
    false.should eq(true)
  end
end
```

If you run this with `crystal spec`, then the test will fail since false does not equal true.

Let's try this out on our new `is_slothful?` method. If we were in Go, the test would look something like this:

```go
func TestIsSlothful(t *testing.T) {
    if !isSlothful("Suplol, slothful world!") {
        t.Error(`"Suplol, slothful world!" was considered non slothful`)
    }
}
```

In Crystal, we don't have a `testing.T`; instead, we use `it` blocks to define test cases, and we write assertions of what we expect to be true, like in Ruby's RSPec and JavaScript's Jest. So a test for is_slothful would look like this:

```ruby
  it "detects when a string is slothful" do
    First::App.is_slothful?("Suplol, world!").should be_false
    First::App.is_slothful?("Suplol, slothful world!").should be_true
  end
```

Get rid of the `it works` block from earlier and run `crystal spec`, and our tests should pass! But let's add one more example to this test case:

```ruby
First::App.is_slothful?("Sloths for the win!").should be_true
```

Because we're looking for the string "sloths" with a lowercase s, our code isn't considering this capital S "Sloths for the win!" string to be slothful. Let's fix that!

Looking in the String package's docs, there is a method for making a string all-lowercase:

```ruby
def downcase(options : Unicode::CaseOptions = :none) : String
```

We can convert a string to all-lowercase with this method, and we even can optionally pass in a `CaseOptions` to indicate which rules for capital and lowercase letters to use (like treating an I with and without a dot differently if you're working with text in Turkic languages). Let's use this downcase method our `is_slothful` method:

```diff
  def self.is_slothful?(s : String)
-   s.includes? "sloth"
+   s.downcase.includes? "sloth"
  end
```

Run `crystal spec` and the tests should now pass!

## 🌺 Bringing in the magenta!

Now, we've got our function working to test whether a string is slothful, so let's recolor a string if it is slothful! If we were recoloring text in Go, there isn't a standard library package for recoloring text, so we could either give our strings ANSI escape codes to colorize our text, or import Fatih Arslan's `color` package like this:

```go
func printIfSlothful(s string) {
    if isSlothsul(s) {
        color.Magenta(s)
    } else {
        fmt.Println(s)
    }
}
```

In Crystal, colorizing functionality is actually a module right in its standard library, in the [colorize module](https://crystal-lang.org/api/0.35.1/Colorize.html)! By importing it, it adds a `colorize` method to the Object type, which **every type** inherits from! That means numbers, strings, and more complex types all can be displayed in multiple colors using this method!

Let's import that, and try this out on a `print_if_slothful` method in our `First::App` module. Up top in first-app.cr, add this line:

```ruby
require 'colorize'
```

Then, let's make that method with this code:

```ruby
  def self.magenta_if_slothful(s : String)
    if is_slothful? s
      s.colorize(:magenta)
    else
      s
    end
  end
```

If our string is not slothful, then we just return the string as-is, to be printed with the terminal's default text color. But if it is slothful, then we use `s.colorize` to return that string re-colored, and to pick a color, we're using a Crystal [symbol](https://crystal-lang.org/reference/syntax_and_semantics/literals/symbol.html), which is sort of like a string, and intended to be a unique name.

Now let's try this out. Remove the `puts` call that was there before, and add these lines of code:

```ruby
  puts magenta_if_slothful "Suplol world!"
  puts magenta_if_slothful "Suplol slothful world!"
  puts magenta_if_slothful "Sloths rule!"
```

Run `shards build` and then run the binary it made, and you should get the first two line printed in your terminal's default color, and the last two lines printed in magenta! 🦥🌺

So far trying out Crystal, while I haven't done a ton with the Ruby family of languages before, I like this language so far, and find that a lot of what I know from Go and Ruby as a whole carries over well to this new language. We've barely scratched the surface, but if you're a Gopher looking for a fun new language to try, I recommend taking Crystal for a spin!