When using Hash.new() to initialize a hash, we all know for undefined it will return specific value which sent as a parameter to the ```Hash.new``
But when doing something like that keys get invisible
Maybe it's normal behavior because, for each missing key, it initialize new empty has and merge it to that. But I'm not sure it can cause a memory leak or not when removing the main hash (:my_hash)
my_hash[:my_key] returns my_hash.default (not assigning it to any key)
my_hash[:my_key].merge!(value: '') updates this object
Now this object is available for ANY key in its updated form, not only :my_key:
my_hash=Hash.new({})my_hash.default# => {} my_hash[:my_key]#=> {}my_hash[:my_key].merge!(value: '')my_hash.default# => {:value=>""}# Nothing special about my_key here, you just have your default:my_hash.dig(:my_key)#=> {:value=>""}my_hash[:my_key]#=> {:value=>""}# It would be the same with any key:my_hash[:any_other_key]#=> {:value=>""}
...even if a bit surprising on a first occurence, nothing is "hidden" here ;)
This types of subtle errors can be avoided by:
# 1. Either passing blocks instead of single value:my_hash=Hash.new{{}}my_hash[:my_key].merge!(value: '')my_hash[:my_key]#=> {}# 2. Passing unchangeable values as defaults:my_hash=Hash.new({}.freeze)my_hash[:my_key].merge!(value: '')# FrozenError (can't modify frozen Hash: {})
So does it mean when main hash lost his references, this new allocated hash will also be collected by GC? I haven't get a chance to test it (also don't know how to do proper test :) )
You typically never want a hash's default to be mutable. Try to name that default in your head (e.g. X={}; my_hash= Hash.new(X), this might help understand it.
You may want to do instead: Hash.new{ |h, k| h[k] = {} } which creates a new hash for every key.
Further questions are probably better asked on StackOverflow or similar.
my_hash=Hash.new({})my_hash[:my_key].merge!(value: '')id=my_hash.default.object_idpObjectSpace.each_object.find{|o|o.object_id==id}#=> {:value=>""} -- it existsmy_hash=nil# allow myhash __and its default__ to be garbage-collectedGC.start# collect garbagepObjectSpace.each_object.find{|o|o.object_id==id}# try to find it#=> nil -- no such object, yes, it got collected