I have a helper class defined as follows:
require "toml"
module Test
class Utils
@@config
def self.config
if @@config.is_a?(Nil)
raw_config = File.read("/usr/local/test/config.toml")
@@config = TOML.parse(raw_config)
end
@@config
end
end
end
When I call this method elsewhere in the code:
server = TCPServer.new("localhost", Utils.config["port"])
I receive the following compile-time error:
in src/test/daemon.cr:10: undefined method '[]' for Nil (compile-time type is (Hash(String, TOML::Type) | Nil))
server = TCPServer.new("localhost", Utils.config["port"])
There is no way for Utils.config
to run something that is Nil
, so I don't understand the error.
Utils.config
will always return something that is not Nil
?config
) that will be shared between classes, but only should be created once?EDIT: See Johannes Müller's answer below, it is a better solution.
In general, if you want to avoid Nil
, you should type your class and instance variables:
@@config : Hash(String,Toml::Type)
That will help the compiler help you - by finding code paths that could lead to a Nil
value and alerting you during compile time.
A potential fix for the code:
require "toml"
module Test
class Utils
@@config = {} of String => TOML::Type # This line prevents union with Nil
def self.config
if @@config.empty?
raw_config = File.read("/usr/local/test/config.toml")
@@config = TOML.parse(raw_config)
else
@@config
end
end
end
end
puts Test::Utils.config["port"]
I couldn't test that directly due to the toml requirement, but a running example using strings instead is here: https://play.crystal-lang.org/#/r/30kl
For your second question, this approach may work:
require "toml"
module Test
class Utils
CONFIG = TOML.parse(File.read("/usr/local/test/config.toml"))
end
end
puts Test::Utils::CONFIG["port"]
Example code using a string instead of TOML: https://play.crystal-lang.org/#/r/30kt