嵌套结构体场景下Getter值未更新问题排查咨询
问题分析与解决
问题根源
核心原因是Crystal语言中struct与class的类型特性差异:
Counter被定义为struct,属于值类型。当你把Counter实例存入Tree的哈希表,或是从哈希表中取出时,都会生成新的副本。- 通过
Tree调用increment时,实际操作的是哈希表中Counter的副本,原实例的total根本没被修改,所以最终看到的total始终为0。 - 直接调用
Counter实例的increment时,操作的是原实例本身,所以total能正常更新。
修复方案
把Counter从struct改为class即可——class是引用类型,赋值和传递时只会传递引用,所有操作都会作用在同一个实例上:
module CounterTree # 将struct改为class class Counter getter table : Hash(UInt64, UInt64) getter total : UInt64 def initialize @table = Hash(UInt64, UInt64).new @total = 0_u64 end def increment : UInt64 insert(key = timestamp, value : UInt64 = 1) end def insert(key : UInt64, value : UInt64) : UInt64 if @table.has_key?(key) @table[key] += value else @table[key] = value end increment_total(value) value end private def increment_total(value : UInt64) : UInt64 @total += value end private def timestamp Time.local.to_unix_ms.to_u64 end end struct Tree getter branches = [] of Hash(UInt64, CounterTree::Counter) getter branches_num : UInt32 def initialize(@branches_num = 9) @branches = Array.new(@branches_num) do Hash(UInt64, CounterTree::Counter).new end end def increment(key : UInt64) : UInt64 with_counter(key) do increment end end private def with_counter(key : UInt64) index = (key % @branches_num).to_u64 unless @branches[index][key]? @branches[index][key] = Counter.new end with @branches[index][key] yield end end end # 测试代码保持不变 counter = CounterTree::Counter.new counter.increment counter.increment counter.increment pp counter puts "---------" tree = CounterTree::Tree.new tree.increment 1234_u64 tree.increment 1234_u64 tree.increment 1234_u64 pp tree
额外说明
在Crystal中,struct适合封装不可变或简单的值类型数据;如果需要一个能被多处引用、修改状态的类型,应该使用class。这是值类型与引用类型的核心区别,也是这类状态不更新问题的常见诱因。
内容的提问来源于stack exchange,提问作者Sergey Fedorov
相关产品推荐
相关产品推荐

