In Ruby, how does ObjectSpace::WeakMap differ from a Hash and from ObjectSpace::WeakKeyMap when you use it as a cache?
answer
- entries vanish after collection
- WeakMap: weak keys and values
- WeakMap compares keys by identity
- WeakKeyMap: eql? keys, strong values
- WeakKeyMap#getkey for canonical objects
basics
~20 sA Hash keeps keys and values alive. ObjectSpace::WeakMap holds both weakly and compares keys by identity, so entries vanish once either is collected. ObjectSpace::WeakKeyMap, since Ruby 3.3, holds only keys weakly, keeps values strongly and compares keys with eql?.
solid answer
~40 sA `Hash` used as a cache keeps every key and value reachable, so it grows until you evict. `ObjectSpace::WeakMap` holds **both keys and values weakly** and compares keys by **identity**: once nothing else references the key or the value, the GC can drop the entry, and `map[key]` then returns `nil`. Identity comparison means a different but equal String will not find an entry. `ObjectSpace::WeakKeyMap`, added in Ruby 3.3, holds **only keys** weakly, keeps **values strongly**, and compares keys with `eql?` and `hash`, which suits attaching data to objects you do not own or deduplicating value objects with `getkey`. Both maps are CRuby-specific tools; entries disappear only when a collection actually runs, so code must always handle a miss.
code
ruby · 11 lineswm = ObjectSpace::WeakMap.new
key = +"report"
wm[key] = Object.new
p wm[key] # => #<Object:...>
p wm[+"report"] # => nil: equal String, different object
wk = ObjectSpace::WeakKeyMap.new
wk[key] = :cached
p wk[+"report"] # => :cached: keys compared with eql?
p wk.getkey(+"report").equal?(key) # => truego deeper
Recall that a Hash keeps what it holds alive while a weak map lets the GC drop entries whose objects are no longer used.
Explain WeakMap's weak keys and values with identity comparison versus WeakKeyMap's weak keys, strong values and eql? comparison.
Show when a weak map beats an explicit bound, why misses must always be handled, and why tests must not assert eviction.
Decide where cache lifetimes should follow object lifetimes and where the team needs explicit, observable eviction instead.
## Three maps, three lifetimes | | `Hash` | `ObjectSpace::WeakMap` | `ObjectSpace::WeakKeyMap` | |---|---|---|---| | Keys held | strongly | weakly | weakly | | Values held | strongly | weakly | strongly | | Key comparison | `eql?` and `hash` | identity | `eql?` and `hash` | | Entry disappears when | you delete it | key or value is collected | key is collected | | Since | always | long-standing | Ruby 3.3 | "Weakly" means the map's own reference does not keep the object alive: if nothing else in the program references it, the garbage collector may reclaim it and the entry goes away. ## ObjectSpace::WeakMap `WeakMap` is the older of the two. Its documentation states that it holds weak references to its keys **and** values and compares keys **by identity**. Its interface is Hash-like but smaller: - `[]=`, `[]`, `key?` / `include?` / `member?`; - `delete` (added in Ruby 3.3 to clear an entry eagerly); - `each`, `each_key`, `each_value`, `keys`, `values`, `size`. Consequences worth stating: 1. **Identity keys**: `map["name"] = x` followed by `map["name"]` with a *different* String object returns `nil`, even though the strings are equal. 2. **Either side can drop the entry**: if the value has no other references, the entry disappears even while the key is alive. 3. **Misses are normal**: code must treat `nil` as "rebuild it", never as an error. A typical use is a cache from an object you keep alive elsewhere to a derived object you can recompute. ## ObjectSpace::WeakKeyMap `WeakKeyMap`, added in Ruby 3.3, fixes the two things that make `WeakMap` awkward: - keys are compared **by value** with `eql?`, like a Hash; - **values are strong**, so they stay as long as the key lives. Only garbage-collectable objects can be keys; a Symbol, Integer or Float key raises `ArgumentError`. It offers `[]=`, `[]`, `key?`, `delete`, `clear` and `getkey`. `getkey(obj)` returns the **existing key** that is `eql?` to `obj`, which enables a documented pattern: keep one canonical instance of each equal value object and let unused ones be collected. ## When a weak map is the wrong tool - **Bounded caches with an eviction policy** - a weak map evicts on GC timing, not on size or age; for predictable memory use an explicit bound. - **Uncollectable keys** - a weak map gains nothing when its keys never die, such as small Integers; `WeakKeyMap` goes further and raises `ArgumentError` ("WeakKeyMap keys must be garbage collectable") for Symbols, Integers and Floats. - **Tests relying on eviction** - the class documentation itself notes that `GC.start` in its examples "might not always lead to demonstrated results"; never assert that an entry is gone right after `GC.start`. ## Summary `WeakMap`: weak keys and values, identity comparison. `WeakKeyMap`: weak keys, strong values, `eql?` comparison, plus `getkey`. A `Hash` keeps everything until you remove it. Choose a weak map when the entry's lifetime should follow an object you do not control, and handle misses every time.
- Why can a WeakMap entry disappear while its key is still in use?`WeakMap` holds values weakly too. If the value object has no other references, the collector may reclaim it and the entry is gone even though the key is alive. Keep a strong reference to the value elsewhere, or use `WeakKeyMap`, whose values are strong.
- What is WeakKeyMap#getkey for?It returns the existing key that is `eql?` to the argument, or `nil`. That lets you keep one canonical instance of each equal value object: look it up with `getkey`, return the stored one if present, otherwise store the new one. Unused canonical instances are still collected because keys are weak.
saying these in an interview costs you the question
- WeakMap compares keys with eql? just like a Hash
- WeakKeyMap holds its values weakly as well as its keys
- After GC.start, a weak map entry is guaranteed to be gone
- A weak map is a good bounded cache with predictable size
- WeakMap entries can only disappear when the key is collected