我想在 Crystal 中定义一个通用的记忆包装器。我有以下水晶代码:
module Scalar(T)
abstract def value: T
end
class ScSticky(T)
include Scalar(T)
def initialize(sc : Scalar(T))
@sc = sc
@val = uninitialized T
end
def value: T
@val ||= @sc.value
end
end
Run Code Online (Sandbox Code Playgroud)
换句话说,我只想ScSticky调用底层Scalar(T)一次,并为所有后续调用返回缓存的输出。T然而,如果是这样的话,上述方法就不起作用了Int32
例如,当包装这个类时
class ScCounter
include Scalar(Int32)
def initialize
@val = 100
end
def value: Int32
@val += 1
@val
end
end
Run Code Online (Sandbox Code Playgroud)
ScSticky(ScCounter.new).value将始终等于0(据我所知,因为unitialized Int32实际上是用 0 值初始化的)
我非常感谢有关此问题的帮助
更新:似乎实现这一点的正确方法是使用nil,但是我在理解这种实现到底应该是什么样子方面遇到了问题。我也希望能够记住.value方法,即使它返回nil(换句话说,如果它T是一个 nilable 类型)
您正在使用不安全的功能“ uninitialized”,这意味着“保留以前内存中的任何内容”(理论上该值是随机的并且可能无效,实际上您通常最终会得到 0 无论如何 - 但这仍然不能保证)。
关于该功能的简短故事uninitialized是请永远不要使用它。
如果你这样写的话,这种行为不会让你感到惊讶@val = 0——而且这就是你所写的。
您必须定义@val : T? = nil-- 使其可为零(具有单独的可能值nil,它是它自己的类型 - Nil)。
您可能认为这unitialized会带来影响nil,但事实绝对不是。
为了回应您关于还包含nil可能值的评论,这里有一个完整的解决方案,它使用用户永远无法创建的独特的“哨兵”结构而不是 Nil。
module Scalar(T)
abstract def value: T
end
private struct Sentinel
end
class ScSticky(T)
include Scalar(T)
@val : T | Sentinel = Sentinel.new
def initialize(@sc : Scalar(T))
end
def value: T
val = @val
if val.is_a?(Sentinel)
@val = @sc.value
else
val
end
end
end
class ScCounter
include Scalar(Int32)
def initialize
@val = 100
end
def value: Int32
@val += 1
end
end
sc = ScSticky.new(ScCounter.new)
p! sc.value #=> 101
p! sc.value #=> 101
Run Code Online (Sandbox Code Playgroud)