The Singleton Pattern in Crystal Language

I recently started learning the Crystal Language. As a long-time Ruby developer, this language readily caught my attention as a worthy candidate for building applications that needed very fast processing times. One of the first things I kept finding myself in need of was a simple implementation of the Singleton Pattern. Surprisingly, this was harder to achieve than I thought it would be, so let’s talk about that.

The Challenge

I am working on a new web application that is written using the Lucky Framework. If you’re a Rubist coming from a Ruby on Rails background, this is a great framework to begin learning Crystal to build a web application. As touted on the website, “batteries included” and one thing I can say about my so-far short venture into the land of Crystal Language, this was a much appreciated thumbs up in Lucky’s favor.

Two of the biggest hurdles for a Rubyist learning Crystal is learning to let go of the “Hash is King” mantra and learning to deal with static, compile-time typing of variables. A third, lesser-impactful challenge, for the Ruby developer is how to continue to solve problems that are easily solved with meta-programming (and without Ruby’s duck-typing strength). In the place of Ruby’s much-heralded meta-programming capabilities, we have Crystal’s macros that are basically inline expansion of code blocks much like you find in C/C++.

Ruby makes singleton’s implementation so trivial, it often begs to be abused as developers throw together a Singleton solution in their haste and skip right on past thinking properly through the problem-space of variable scoping and where “a thing” should live in the global space. But that’s besides the point in this article. I simply wish to show how to implement Singleton in Crystal vs. Ruby and one place it makes sense to have a Singleton instance is with setting up a controlled way to access external servers and endpoints where you just need to initialize/configure/connect once and thereafter make use of the access provided.

The Ruby Way

Like anything else in Ruby, there are a multitude of ways to go about the Singleton pattern’s implementation, but I think the simplest, most straightforward way is with a Module, which cannot be instantiated and

1
2
3
4
5
6
7
8
module Logger
  def self.log(msg)
    @@log ||= File.open("log.txt", "a")
    @@log.puts(msg)
  end
end

Logger.log('Ruby Rocks!')

Crystal was not so Simple!

Now, first thing you might think, “Crystal is Ruby-like — That oughta work, too…” So let’s try it:

1
2
3
4
5
6
7
8
module Logger
  def self.log(msg)
    @@log ||= File.open("log.txt", "a")
    @@log.puts(msg)
  end
end

Logger.log("Crystal Rocks!")

output

>> crystal crystal_logger.cr
Error in crystal_logger.cr:8: instantiating 'Logger:Module#log(String)'

Logger.log("Crystal Rocks!")
       ^~~

in crystal_logger.cr:4: undefined method 'puts' for Nil (compile-time type is (File | Nil))

    @@log.puts(msg)
          ^~~~

As you can see, that didn’t work. I ran up against Crystal’s excellent type-casting safety checking and the script fails to even compile. While Crystal supports the ||= operator, the compiler basically treats it as either being Nil or of the object type you’re expecting with this very common Ruby pattern.

The solution to this compile-time error is fairly surprising to me as a Rubyist. Utilize the ||= assignment in its own method call like this:

1
2
3
4
5
6
7
8
9
10
11
12
module Logger
  def self.instance
    @@log ||= File.open("log.txt", "a")
  end

  def self.log(msg)
    instance.puts(msg)
    instance.flush
  end
end

Logger.log("Crystal Rocks!")

Breaking it out like this allows the Crystal compiler to resolve the return-type of the instance class method, so we’re golden when we call it from the log method. The compiler knows the return type is always File. This behavior of the type-resolution wasn’t obvious to me at first, nor am I clear why the compiler cannot resolve this sort of assignment within a method’s call.

NOTE:

One thing I realized when logging to a file opened without a block call like this is that Crystal doesn't take care of flushing file contents before program terminates, so this is another small difference newcomers must be on the lookout for.

This wasn’t the first time I encountered small differences in otherwise identical code for Cyrstal vs. Ruby and certainly isn’t the last time. But it gets easier with time as one learns the differences between the behavior of the two languages. One thing I noticed is that my mindset evolved from, “I’m writing compilable Ruby” to “I can think through a problem-space like its Ruby, but I’m writing Crystal.” That may seem like a subtle distinction, but it makes all the difference in how one approaches crafting the solution and debugging when things don’t go quite to expectation.

Using the Singleton Pattern in Lucky Framework

Now that we have figured out how to effectively utilize the Singleton pattern in Crystal, the next challenge is to set up an approach I was happy with for maintaining such in the Lucky Framework. One thing not tackled above was how to apply settings from environment or *.yml file and where to stash the files in general for external server integration. Here, I tackle adding Redis to the project in such a way that all I needed to do was call redis.get anywhere in the code base and I would be confident I’m using a properly configured Redis instance that takes it’s host and port values from the environment.

A home for Server Endpoints

While I’m loving the whole ‘batteries included’ nature of Lucky and it’s Railsy feel, it’s not Rails and so conventions aren’t rigidly defined. That means a bit of extra work up-front to think about general organization of the project’s source. After some reflection and since I will have four or five external server integrations, I decided to place these all under the project’s src/servers folder, a new folder I introduced to the project and provide global access to each server in a conventional way for the project itself. To set this up, the first thing to do after creating the src/servers folder is to add an entry to the src/app.cr file to include any files in that folder:

1
2
3
4
5
6
7
8
require "./shards"

# Load the asset manifest in public/mix-manifest.json
Lucky::AssetHelpers.load_manifest

require "./servers/*" # <= inserted this!
require "./models/base_model"
# ...

The RedisServer Module

Lucky comes with a built-in library called Habitat for injecting settings into any class. Here’s how it looks in the src/servers/redis_server.cr file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class RedisServer
  Habitat.create do
    setting port : Int32
    setting host : String, example: "127.0.0.1"
  end

  getter redis : Redis

  def initialize
    @redis = Redis.new(host: settings.host, port: settings.port)
  end

  def self.instance
    @@instance ||= RedisServer.new
  end  
end

def redis
  RedisServer.instance.redis
end

Here, notice the Single pattern as implemented through the class instance method similarly to described in previous section. In addition, we have Habitat defining the settings we can change at boot time. Habitat really makes it trivial to add settings to any Crystal class and does so in a way that is type-safe and very clean extension of the class object.

The one final piece of the puzzle to enabling us to call redis anywhere in the code is the global method redis defined at the bottom of the file. Of course, we could just call RedisServer.redis throughout the code, but I felt this was a nice touch and reduces amount of typing I’d be keying enough to make it worthwhile.

Configuring the Redis Settings

Lastly, the Redis instance itself can take settings from the environment and this suggests a config/redis.cr file in the conventional location for such files:

RedisServer.configure do |settings|
  if Lucky::Env.production?
    settings.host = ENV["REDIS_HOST"]? || "redis"
  else
    settings.host = ENV["REDIS_HOST"]? || "localhost"
  end
  settings.port = ENV["REDIS_PORT"]? ? ENV["REDIS_PORT"].to_i : 6379
end

Conclusion

All-in-all, I’m having a blast learning Crystal Language and the Lucky Framework and it’s been well worth the effort. Crystal is amazingly fast compared to Ruby and I am finding it suits the bill for solving some performance issues Ruby simply cannot hope to tackle any time soon. I love the fact that I can still “think in Ruby” but definitely and cognizant of the fact that there are gotchas all along the way that remind me, “this isn’t Ruby.” However, I can say with even more conviction, “This ain’t C either, but I can write new Crystal code a lot faster than I can in C.”