Here is the code that reproduces the problem - https://carc.in/#/r/f4dp
module CounterTree
struct 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
# Total is updated
counter = CounterTree::Counter.new
counter.increment
counter.increment
counter.increment
pp counter
puts "---------"
# Total is not updated
tree = CounterTree::Tree.new
tree.increment 1234_u64
tree.increment 1234_u64
tree.increment 1234_u64
pp tree
I have a value counter that is a simple hash table wrapped in a Counter
struct. I increment the getter value total each time it is updated for caching purposes.
I also have a Tree
struct, which I need to manage groups of counters. It's arranged in a similar way - inside a hash table, where the key is some numeric value, and the value of the key is Counter
.
The problem is that if I create a Counter
instance and increment the value, the getter total is updated correctly, but if I do it through Tree
, the getter is always zero.
At first I thought there was some problem with the yield
and block inline, but trying to rewrite without yield
gave exactly the same result.
Where should I look for the problem?
The problem is solved by using class
instead of struct
because:
Mutable structs are still allowed, but you should be careful when writing code involving mutability if you want to avoid surprises that are described below.