Crystal:确保 return 值不为 Nil
Crystal: Ensure return value is not Nil
我有一个助手 class 定义如下:
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
当我在代码的其他地方调用这个方法时:
server = TCPServer.new("localhost", Utils.config["port"])
我收到以下编译时错误:
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"])
Utils.config
无法 运行 是 Nil
的东西,所以我不明白这个错误。
- 如何告诉编译器
Utils.config
总是 return 不是 Nil
的东西?
- (次要附加问题)对于将在 class 之间共享但只应创建一次的资源(
config
)来说,这是一个好的设计模式吗?
编辑:请参阅下面 Johannes Müller 的回答,这是一个更好的解决方案。
一般来说,如果你想避免 Nil
,你应该输入你的 class 和实例变量:
@@config : Hash(String,Toml::Type)
这将有助于编译器帮助您 - 通过查找可能导致 Nil
值的代码路径并在编译期间提醒您。
代码的潜在修复:
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"]
由于 toml 的要求,我无法直接测试它,但是这里有一个使用字符串的 运行 示例:https://play.crystal-lang.org/#/r/30kl
对于你的第二个问题,这种方法可能有效:
require "toml"
module Test
class Utils
CONFIG = TOML.parse(File.read("/usr/local/test/config.toml"))
end
end
puts Test::Utils::CONFIG["port"]
使用字符串而不是 TOML 的示例代码:https://play.crystal-lang.org/#/r/30kt
您的代码的问题是,在 if 分支中检查 @config
是否为 nil(顺便说一句,使用 @config.nil?
更容易)时,该实例变量的值可能已经更改,直到它到达 return 行。例如,如果它是从不同的光纤更改的,编译器必须假设它可以再次为 nil。
您可以将其保存到局部变量,return this
class Utils
@@config
def self.config
if (config = @@config).nil?
raw_config = File.read("/usr/local/test/config.toml")
@@config = TOML.parse(raw_config)
else
config
end
end
end
或者稍微重构但本质上是一样的:
class Utils
@@config
def self.config
@@config ||= begin
raw_config = File.read("/usr/local/test/config.toml")
TOML.parse(raw_config)
end
end
end
我更喜欢将 @@config
设置为 nil
而不是使用空对象进行默认初始化,因为它清楚地表明该对象不可用。
例如,如果配置文件恰好为空,检查 empty?
将始终触发重新加载和解析,从而消除记忆功能。
||=
运算符基本上意味着
if config = @@config
config
else
@@config = # ...
end
我有一个助手 class 定义如下:
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
当我在代码的其他地方调用这个方法时:
server = TCPServer.new("localhost", Utils.config["port"])
我收到以下编译时错误:
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"])
Utils.config
无法 运行 是 Nil
的东西,所以我不明白这个错误。
- 如何告诉编译器
Utils.config
总是 return 不是Nil
的东西? - (次要附加问题)对于将在 class 之间共享但只应创建一次的资源(
config
)来说,这是一个好的设计模式吗?
编辑:请参阅下面 Johannes Müller 的回答,这是一个更好的解决方案。
一般来说,如果你想避免 Nil
,你应该输入你的 class 和实例变量:
@@config : Hash(String,Toml::Type)
这将有助于编译器帮助您 - 通过查找可能导致 Nil
值的代码路径并在编译期间提醒您。
代码的潜在修复:
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"]
由于 toml 的要求,我无法直接测试它,但是这里有一个使用字符串的 运行 示例:https://play.crystal-lang.org/#/r/30kl
对于你的第二个问题,这种方法可能有效:
require "toml"
module Test
class Utils
CONFIG = TOML.parse(File.read("/usr/local/test/config.toml"))
end
end
puts Test::Utils::CONFIG["port"]
使用字符串而不是 TOML 的示例代码:https://play.crystal-lang.org/#/r/30kt
您的代码的问题是,在 if 分支中检查 @config
是否为 nil(顺便说一句,使用 @config.nil?
更容易)时,该实例变量的值可能已经更改,直到它到达 return 行。例如,如果它是从不同的光纤更改的,编译器必须假设它可以再次为 nil。
您可以将其保存到局部变量,return this
class Utils
@@config
def self.config
if (config = @@config).nil?
raw_config = File.read("/usr/local/test/config.toml")
@@config = TOML.parse(raw_config)
else
config
end
end
end
或者稍微重构但本质上是一样的:
class Utils
@@config
def self.config
@@config ||= begin
raw_config = File.read("/usr/local/test/config.toml")
TOML.parse(raw_config)
end
end
end
我更喜欢将 @@config
设置为 nil
而不是使用空对象进行默认初始化,因为它清楚地表明该对象不可用。
例如,如果配置文件恰好为空,检查 empty?
将始终触发重新加载和解析,从而消除记忆功能。
||=
运算符基本上意味着
if config = @@config
config
else
@@config = # ...
end