Extracting singleton functionality into modules

Hi there,
I am new to Crystal, but a long time Ruby fan.

In Ruby, we have the Singleton module.

I tried to implement the same thing in Crystal.
I started with writing a class with the singleton property.
The class works just fine.

  class Klass
    private SINGLETON = ::Mutex.new
    @@instance : self?

    def self.instance
      @@instance || SINGLETON.synchronize do
        @@instance ||= new

However, when I tried extracting the functionality into a module, I’ll get a compilation error.
Could you show me how to do it properly? Or if this is possible at all?

One thing I like about Ruby is people always make a trade-off on type-safety with convenience and usabilities.

I do it like this:

module Singleton
  macro included
    private SINGLETON_LOCK = Mutex.new

    def self.instance : self
      @@instance || SINGLETON_LOCK.synchronize { @@instance ||= new }

    macro method_added(m)
      \{% if m.name == "initialize" %}
        private def initialize
      \{% end %}

Constants are guaranteed to be initialized only once, so instead of a class variable, you could just use a constant (being a signleton it won’t need to be reassigned anyway) and don’t need to do mutex synchronization. The runtime already takes care of that.

So the implementation of a singleton is actually quite trivial. IMO there’s no need for a generalized module to include this, when it’s just three loc:

class Foo
  INSTANCE = new

  private def initialize

OFC you can still inherit this type, or call #dup/#clone to get a copy. If you want you can also restrict this. But I don’t think that’s strictly necessary. The important thing about a singleton is to not expose a public API for directly creating a new instance (.new).
Another alternative to a singleton instance is simply using a module.

module FooSingleton  
  class_property foo = true


Btw. there are already quite a few discussions on singletons: https://github.com/crystal-lang/crystal/pull/1399, https://github.com/crystal-lang/crystal/issues/8846, https://github.com/crystal-lang/crystal/issues/718

1 Like

Thank you
I learned something from both answers.

However, using constant might not fit the bill in some situations.
For example, in my case, the initialization is a binding to C object initialization; the operation of the C library is a side effect of class initialization.
The constant approach won’t work because the side-effects would always occur.
In contrast, singleton instance can be lazily evaluated.

Someone already made the point in the issue.
Now the Mutex is already in the core. Do you guys still work on the singleton module?