Class: ObjectSpace::WeakKeyMap
Overview
An WeakKeyMap
is a key-value map that holds weak references to its keys, so they can be garbage collected when there is no more references.
Unlike WeakMap
:
-
references to values are strong, so they aren’t garbage collected while they are in the map;
-
keys are compared by value (using Object#eql?), not by identity;
-
only garbage-collectable objects can be used as keys.
map = ObjectSpace::WeakKeyMap.new val = Time.new(2023, 12, 7) key = "name" map[key] = val # Value is fetched by equality: the instance of string "name" is # different here, but it is equal to the key map["name"] #=> 2023-12-07 00:00:00 +0200 val = nil GC.start # There are no more references to {val}, yet the pair isn't # garbage-collected. map["name"] #=> 2023-12-07 00:00:00 +0200 key = nil GC.start # There are no more references to {key}, key and value are # garbage-collected. map["name"] #=> nil
(Note that GC.start is used here only for demonstrational purposes and might not always lead to demonstrated results.)
The collection is especially useful for implementing caches of lightweight value objects, so that only one copy of each value representation would be stored in memory, but the copies that aren’t used would be garbage-collected.
CACHE = ObjectSpace::WeakKeyMap
def make_value(**)
val = ValueObject.new(**)
if (existing = @cache.getkey(val))
# if the object with this value exists, we return it
existing
else
# otherwise, put it in the cache
@cache[val] = true
val
end
end
This will result in make_value
returning the same object for same set of attributes always, but the values that aren’t needed anymore wouldn’t be sitting in the cache forever.
Instance Method Summary
-
#[](key) ⇒ value
Returns the value associated with the given
key
if found. -
#[]=(key, value) ⇒ value
Associates the given
value
with the givenkey
-
#clear ⇒ self
Removes all map entries; returns
self
. -
#delete(key) ⇒ value?
Deletes the entry for the given
key
and returns its associated value. -
#getkey(key) ⇒ existing_key?
Returns the existing equal key if it exists, otherwise returns
nil
. -
#inspect ⇒ String
Returns a new
::String
containing informations about the map: -
#key?(key) ⇒ Boolean
Returns
true
ifkey
is a key inself
, otherwisefalse
.
Instance Method Details
#[](key) ⇒ value
Returns the value associated with the given key
if found.
If key
is not found, returns nil
.
# File 'weakmap.c', line 820
static VALUE wkmap_aref(VALUE self, VALUE key) { VALUE obj = wkmap_lookup(self, key); return !UNDEF_P(obj) ? obj : Qnil; }
#[]=(key, value) ⇒ value
Associates the given value
with the given key
The reference to key
is weak, so when there is no other reference to key
it may be garbage collected.
If the given key
exists, replaces its value with the given value
; the ordering is not affected
# File 'weakmap.c', line 859
static VALUE wkmap_aset(VALUE self, VALUE key, VALUE val) { struct weakkeymap *w; TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) { rb_raise(rb_eArgError, "WeakKeyMap must be garbage collectable"); UNREACHABLE_RETURN(Qnil); } struct wkmap_aset_args args = { .new_key = key, .new_val = val, }; st_update(w->table, (st_data_t)&key, wkmap_aset_replace, (st_data_t)&args); RB_OBJ_WRITTEN(self, Qundef, key); RB_OBJ_WRITTEN(self, Qundef, val); return val; }
#clear ⇒ self
Removes all map entries; returns self
.
# File 'weakmap.c', line 999
static VALUE wkmap_clear(VALUE self) { struct weakkeymap *w; TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); st_foreach(w->table, wkmap_clear_i, (st_data_t)self); st_clear(w->table); return self; }
#delete(key) ⇒ value
?
#delete(key) {|key| ... } ⇒ Object
value
?
#delete(key) {|key| ... } ⇒ Object
Deletes the entry for the given key
and returns its associated value.
If no block is given and key
is found, deletes the entry and returns the associated value:
m = ObjectSpace::WeakKeyMap.new
key = "foo" # to hold reference to the key
m[key] = 1
m.delete("foo") # => 1
m["foo"] # => nil
If no block given and key
is not found, returns nil
.
If a block is given and key
is found, ignores the block, deletes the entry, and returns the associated value:
m = ObjectSpace::WeakKeyMap.new
key = "foo" # to hold reference to the key
m[key] = 2
m.delete("foo") { |key| raise 'Will never happen'} # => 2
If a block is given and key
is not found, yields the key
to the block and returns the block’s return value:
m = ObjectSpace::WeakKeyMap.new
m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
# File 'weakmap.c', line 912
static VALUE wkmap_delete(VALUE self, VALUE key) { struct weakkeymap *w; TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); VALUE orig_key = key; st_data_t orig_key_data = (st_data_t)&orig_key; st_data_t orig_val_data; if (st_delete(w->table, &orig_key_data, &orig_val_data)) { VALUE orig_val = (VALUE)orig_val_data; rb_gc_remove_weak(self, (VALUE *)orig_key_data); ruby_sized_xfree((VALUE *)orig_key_data, sizeof(VALUE)); return orig_val; } if (rb_block_given_p()) { return rb_yield(key); } else { return Qnil; } }
#getkey(key) ⇒ existing_key
?
Returns the existing equal key if it exists, otherwise returns nil
.
This might be useful for implementing caches, so that only one copy of some object would be used everywhere in the program:
value = {amount: 1, currency: 'USD'}
# Now if we put this object in a cache:
cache = ObjectSpace::WeakKeyMap.new
cache[value] = true
# ...we can always extract from there and use the same object:
copy = cache.getkey({amount: 1, currency: 'USD'})
copy.object_id == value.object_id #=> true
# File 'weakmap.c', line 958
static VALUE wkmap_getkey(VALUE self, VALUE key) { struct weakkeymap *w; TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); st_data_t orig_key; if (!st_get_key(w->table, (st_data_t)&key, &orig_key)) return Qnil; return *(VALUE *)orig_key; }
#inspect ⇒ String
Returns a new ::String
containing informations about the map:
m = ObjectSpace::WeakKeyMap.new
m[key] = value
m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
# File 'weakmap.c', line 1022
static VALUE wkmap_inspect(VALUE self) { struct weakkeymap *w; TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w); st_index_t n = st_table_size(w->table); #if SIZEOF_ST_INDEX_T <= SIZEOF_LONG const char * format = "#<%"PRIsVALUE":%p size=%lu>"; #else const char * format = "#<%"PRIsVALUE":%p size=%llu>"; #endif VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n); return str; }
#key?(key) ⇒ Boolean
Returns true
if key
is a key in self
, otherwise false
.
# File 'weakmap.c', line 976
static VALUE wkmap_has_key(VALUE self, VALUE key) { return RBOOL(!UNDEF_P(wkmap_lookup(self, key))); }