Overcoming the absence of global variables

Sometimes it seems handy to have an object with some configuration data, accessible globally. Since Crystal doesn’t have global variables anymore, my attempt was to utilize the forward_missing_to macro like this:

struct Conf
  property foo : String?
  property bar : String?
  # ...many more properties...
  property zzz : String?
end

module Config
  extend self
  @@config = Conf.new
  forward_missing_to @@config
end

Config.foo = "foo"

But it doesn’t work: undefined method 'foo=' for Config:Module

So, the only option I see at the moment, is to use a constant:

CONFIG = Conf.new
CONFIG.foo = "foo"
p CONFIG.foo #=> "foo"

But using a constant feels semantically wrong. Is there a more elegant way?

Try like

module Config
  class_getter config : Conf = Conf.new
end

Thank you for the suggestion, but calling Config.config.foo seems a bit superfluous.

So, why do you feel bad using a constant, but not a module name?

record Conf, a : Int32
Config = Conf.new 66
p Config.a

BTW, in Ruby every class/module name is just a regular constant.

Only because constant feels like something that should not change (unlike variable). But using a constant seems to be the best way here, thank you.

I understand the need to save a few characters, but I don’t think we should keep designing Crystal for this purpose. I now thing having[quote=“sudo-nice, post:3, topic:915, full:true”]
Thank you for the suggestion, but calling Config.config.foo seems a bit superfluous.
[/quote]

You could do Config.instance.foo instead. I understand it’s more typing than Config.foo but I don’t think that’s necessarily bad or an issue. We should stop designing the language around reducing the number of characters typed.

Also, you can manually define the forwarding methods if forward_missing_to doesn’t work.

It’s not about saving a few characters, but about redundancy. One can write:

Config.some_config_name

or

Config.instance.name

and the first one feels clearer (despite being longer).
What meaning does instance serve in Config.instance.name? None. We use it just because we have to.

Currently, I see using a constant as the only option here.

You mentioned you want this:

struct Conf
  property foo : String?
  property bar : String?
  # ...many more properties...
  property zzz : String?
end

module Config
  extend self
  @@config = Conf.new
  forward_missing_to @@config
end

Config.foo = "foo"

Why not write it like this?

module Config
  class_property foo : String?
  class_property bar : String?
  # ...many more properties...
  class_property zzz : String?
end

Config.foo = "foo"

?

2 Likes

That’s a very good suggestion, thank you.
I tried to use a struct because it makes it easy to switch to a config file:

struct Conf
  include YAML::Serializable
  # ...
end

Guys, I think you are steeping over the fact that module Config is also a constant, just bit less direct one. Sematically there is no difference between module Config and Config = Conf.new or Config.instance or anything else starting with a capital letter.

If you want an honest variable, you should either make it explicitely visible from all the relevant parts of your program, probably carrying it around in your method signatures, or make it implicitely visible everywhere using global constant namespace, at which point module Config is probably worse than Config = Conf.new, because you are using class variables with no good reason.

Also please be careful with the struct here, you are mutating it which is dangerous in Crystal.

1 Like

@sudo-nice

https://play.crystal-lang.org/#/r/769t

require "yaml"

class Conf
  property zzz : String?
  include YAML::Serializable
  # ...
  def initialize(); end
end

Config = Conf.new
Config.zzz = "MuffinGlobalVariable"

pp Config.zzz

Can use struct or class

Btw, why is forward_missing_to in the language. Looks like some kind of hack lmaoo. Is that actual real syntax? Looks out of place and I had to second guess what I was reading after I read it.

It enables trivial implementations of proxy objects (like decorators, presenters, or similar patterns), which are useful for object-design patterns like DCI.

Come to think about it, “constant” is a bit of a misnomer. You may expect that it’s not gonna be reassigned or garbage collected, but there are no promises regading its mutability. The latter is usually managed by type system.