2 #include "internal/gc.h"
3 #include "internal/hash.h"
4 #include "internal/proc.h"
5 #include "internal/sanitizers.h"
11 * WeakMap contains one ST table which contains a pointer to the object as the
12 * key and a pointer to the object as the value. This means that the key and
13 * value of the table are both of the type `VALUE *`.
15 * The objects are not directly stored as keys and values in the table because
16 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
17 * when the object is reclaimed. Using a pointer into the ST table entry is not
18 * safe because the pointer can change when the ST table is resized.
20 * WeakMap hashes and compares using the pointer address of the object.
22 * For performance and memory efficiency reasons, the key and value
23 * are allocated at the same time and adjacent to each other.
25 * During GC and while iterating, reclaimed entries (i.e. either the key or
26 * value points to `Qundef`) are removed from the ST table.
34 wmap_live_p(VALUE obj
)
40 wmap_free_entry(VALUE
*key
, VALUE
*val
)
42 assert(key
+ 1 == val
);
44 /* We only need to free key because val is allocated beside key on in the
45 * same malloc call. */
46 ruby_sized_xfree(key
, sizeof(VALUE
) * 2);
50 wmap_mark_weak_table_i(st_data_t key
, st_data_t val
, st_data_t _
)
52 VALUE key_obj
= *(VALUE
*)key
;
53 VALUE val_obj
= *(VALUE
*)val
;
55 if (wmap_live_p(key_obj
) && wmap_live_p(val_obj
)) {
56 rb_gc_mark_weak((VALUE
*)key
);
57 rb_gc_mark_weak((VALUE
*)val
);
62 wmap_free_entry((VALUE
*)key
, (VALUE
*)val
);
71 struct weakmap
*w
= ptr
;
73 st_foreach(w
->table
, wmap_mark_weak_table_i
, (st_data_t
)0);
78 wmap_free_table_i(st_data_t key
, st_data_t val
, st_data_t arg
)
80 wmap_free_entry((VALUE
*)key
, (VALUE
*)val
);
87 struct weakmap
*w
= ptr
;
89 st_foreach(w
->table
, wmap_free_table_i
, 0);
90 st_free_table(w
->table
);
94 wmap_memsize(const void *ptr
)
96 const struct weakmap
*w
= ptr
;
99 size
+= st_memsize(w
->table
);
100 /* The key and value of the table each take sizeof(VALUE) in size. */
101 size
+= st_table_size(w
->table
) * (2 * sizeof(VALUE
));
107 wmap_compact_table_i(st_data_t key
, st_data_t val
, st_data_t data
)
109 st_table
*table
= (st_table
*)data
;
111 VALUE key_obj
= *(VALUE
*)key
;
112 VALUE val_obj
= *(VALUE
*)val
;
114 if (wmap_live_p(key_obj
) && wmap_live_p(val_obj
)) {
115 VALUE new_key_obj
= rb_gc_location(key_obj
);
117 *(VALUE
*)val
= rb_gc_location(val_obj
);
119 /* If the key object moves, then we must reinsert because the hash is
120 * based on the pointer rather than the object itself. */
121 if (key_obj
!= new_key_obj
) {
122 *(VALUE
*)key
= new_key_obj
;
124 DURING_GC_COULD_MALLOC_REGION_START();
126 st_insert(table
, key
, val
);
128 DURING_GC_COULD_MALLOC_REGION_END();
134 wmap_free_entry((VALUE
*)key
, (VALUE
*)val
);
143 wmap_compact(void *ptr
)
145 struct weakmap
*w
= ptr
;
148 st_foreach(w
->table
, wmap_compact_table_i
, (st_data_t
)w
->table
);
152 static const rb_data_type_t weakmap_type
= {
160 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
| RUBY_TYPED_WB_PROTECTED
| RUBY_TYPED_EMBEDDABLE
164 wmap_cmp(st_data_t x
, st_data_t y
)
166 return *(VALUE
*)x
!= *(VALUE
*)y
;
170 wmap_hash(st_data_t n
)
172 return st_numhash(*(VALUE
*)n
);
175 static const struct st_hash_type wmap_hash_type
= {
181 wmap_allocate(VALUE klass
)
184 VALUE obj
= TypedData_Make_Struct(klass
, struct weakmap
, &weakmap_type
, w
);
185 w
->table
= st_init_table(&wmap_hash_type
);
189 struct wmap_foreach_data
{
191 void (*func
)(VALUE
, VALUE
, st_data_t
);
196 wmap_foreach_i(st_data_t key
, st_data_t val
, st_data_t arg
)
198 struct wmap_foreach_data
*data
= (struct wmap_foreach_data
*)arg
;
200 VALUE key_obj
= *(VALUE
*)key
;
201 VALUE val_obj
= *(VALUE
*)val
;
203 if (wmap_live_p(key_obj
) && wmap_live_p(val_obj
)) {
204 data
->func(key_obj
, val_obj
, data
->arg
);
207 wmap_free_entry((VALUE
*)key
, (VALUE
*)val
);
216 wmap_foreach(struct weakmap
*w
, void (*func
)(VALUE
, VALUE
, st_data_t
), st_data_t arg
)
218 struct wmap_foreach_data foreach_data
= {
224 st_foreach(w
->table
, wmap_foreach_i
, (st_data_t
)&foreach_data
);
228 wmap_inspect_append(VALUE str
, VALUE obj
)
230 if (SPECIAL_CONST_P(obj
)) {
231 return rb_str_append(str
, rb_inspect(obj
));
234 return rb_str_append(str
, rb_any_to_s(obj
));
239 wmap_inspect_i(VALUE key
, VALUE val
, st_data_t data
)
241 VALUE str
= (VALUE
)data
;
243 if (RSTRING_PTR(str
)[0] == '#') {
244 rb_str_cat2(str
, ", ");
247 rb_str_cat2(str
, ": ");
248 RSTRING_PTR(str
)[0] = '#';
251 wmap_inspect_append(str
, key
);
252 rb_str_cat2(str
, " => ");
253 wmap_inspect_append(str
, val
);
257 wmap_inspect(VALUE self
)
259 VALUE c
= rb_class_name(CLASS_OF(self
));
261 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
263 VALUE str
= rb_sprintf("-<%"PRIsVALUE
":%p", c
, (void *)self
);
265 wmap_foreach(w
, wmap_inspect_i
, (st_data_t
)str
);
267 RSTRING_PTR(str
)[0] = '#';
268 rb_str_cat2(str
, ">");
274 wmap_each_i(VALUE key
, VALUE val
, st_data_t _
)
276 rb_yield_values(2, key
, val
);
281 * map.each {|key, val| ... } -> self
283 * Iterates over keys and values. Note that unlike other collections,
284 * +each+ without block isn't supported.
288 wmap_each(VALUE self
)
291 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
293 wmap_foreach(w
, wmap_each_i
, (st_data_t
)0);
299 wmap_each_key_i(VALUE key
, VALUE _val
, st_data_t _data
)
306 * map.each_key {|key| ... } -> self
308 * Iterates over keys. Note that unlike other collections,
309 * +each_key+ without block isn't supported.
313 wmap_each_key(VALUE self
)
316 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
318 wmap_foreach(w
, wmap_each_key_i
, (st_data_t
)0);
324 wmap_each_value_i(VALUE _key
, VALUE val
, st_data_t _data
)
331 * map.each_value {|val| ... } -> self
333 * Iterates over values. Note that unlike other collections,
334 * +each_value+ without block isn't supported.
338 wmap_each_value(VALUE self
)
341 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
343 wmap_foreach(w
, wmap_each_value_i
, (st_data_t
)0);
349 wmap_keys_i(st_data_t key
, st_data_t _
, st_data_t arg
)
351 VALUE ary
= (VALUE
)arg
;
353 rb_ary_push(ary
, key
);
358 * map.keys -> new_array
360 * Returns a new Array containing all keys in the map.
364 wmap_keys(VALUE self
)
367 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
369 VALUE ary
= rb_ary_new();
370 wmap_foreach(w
, wmap_keys_i
, (st_data_t
)ary
);
376 wmap_values_i(st_data_t key
, st_data_t val
, st_data_t arg
)
378 VALUE ary
= (VALUE
)arg
;
380 rb_ary_push(ary
, (VALUE
)val
);
385 * map.values -> new_array
387 * Returns a new Array containing all values in the map.
391 wmap_values(VALUE self
)
394 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
396 VALUE ary
= rb_ary_new();
397 wmap_foreach(w
, wmap_values_i
, (st_data_t
)ary
);
403 nonspecial_obj_id(VALUE obj
)
405 #if SIZEOF_LONG == SIZEOF_VOIDP
406 return (VALUE
)((SIGNED_VALUE
)(obj
)|FIXNUM_FLAG
);
407 #elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
408 return LL2NUM((SIGNED_VALUE
)(obj
) / 2);
410 # error not supported
415 wmap_aset_replace(st_data_t
*key
, st_data_t
*val
, st_data_t new_key_ptr
, int existing
)
417 VALUE new_key
= *(VALUE
*)new_key_ptr
;
418 VALUE new_val
= *(((VALUE
*)new_key_ptr
) + 1);
421 assert(*(VALUE
*)*key
== new_key
);
424 VALUE
*pair
= xmalloc(sizeof(VALUE
) * 2);
426 *key
= (st_data_t
)pair
;
427 *val
= (st_data_t
)(pair
+ 1);
430 *(VALUE
*)*key
= new_key
;
431 *(VALUE
*)*val
= new_val
;
438 * map[key] = value -> value
440 * Associates the given +value+ with the given +key+.
442 * If the given +key+ exists, replaces its value with the given +value+;
443 * the ordering is not affected.
446 wmap_aset(VALUE self
, VALUE key
, VALUE val
)
449 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
451 VALUE pair
[2] = { key
, val
};
453 st_update(w
->table
, (st_data_t
)pair
, wmap_aset_replace
, (st_data_t
)pair
);
455 RB_OBJ_WRITTEN(self
, Qundef
, key
);
456 RB_OBJ_WRITTEN(self
, Qundef
, val
);
458 return nonspecial_obj_id(val
);
461 /* Retrieves a weakly referenced object with the given key */
463 wmap_lookup(VALUE self
, VALUE key
)
465 assert(wmap_live_p(key
));
468 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
471 if (!st_lookup(w
->table
, (st_data_t
)&key
, &data
)) return Qundef
;
473 if (!wmap_live_p(*(VALUE
*)data
)) return Qundef
;
475 return *(VALUE
*)data
;
482 * Returns the value associated with the given +key+ if found.
484 * If +key+ is not found, returns +nil+.
487 wmap_aref(VALUE self
, VALUE key
)
489 VALUE obj
= wmap_lookup(self
, key
);
490 return !UNDEF_P(obj
) ? obj
: Qnil
;
495 * map.delete(key) -> value or nil
496 * map.delete(key) {|key| ... } -> object
498 * Deletes the entry for the given +key+ and returns its associated value.
500 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
501 * m = ObjectSpace::WeakMap.new
504 * m.delete(key) # => 1
507 * If no block is given and +key+ is not found, returns +nil+.
509 * If a block is given and +key+ is found, ignores the block,
510 * deletes the entry, and returns the associated value:
511 * m = ObjectSpace::WeakMap.new
514 * m.delete(key) { |key| raise 'Will never happen'} # => 2
516 * If a block is given and +key+ is not found,
517 * yields the +key+ to the block and returns the block's return value:
518 * m = ObjectSpace::WeakMap.new
519 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
522 wmap_delete(VALUE self
, VALUE key
)
525 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
527 VALUE orig_key
= key
;
528 st_data_t orig_key_data
= (st_data_t
)&orig_key
;
529 st_data_t orig_val_data
;
530 if (st_delete(w
->table
, &orig_key_data
, &orig_val_data
)) {
531 VALUE orig_val
= *(VALUE
*)orig_val_data
;
533 rb_gc_remove_weak(self
, (VALUE
*)orig_key_data
);
534 rb_gc_remove_weak(self
, (VALUE
*)orig_val_data
);
536 wmap_free_entry((VALUE
*)orig_key_data
, (VALUE
*)orig_val_data
);
538 if (wmap_live_p(orig_val
)) {
543 if (rb_block_given_p()) {
544 return rb_yield(key
);
553 * map.key?(key) -> true or false
555 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
558 wmap_has_key(VALUE self
, VALUE key
)
560 return RBOOL(!UNDEF_P(wmap_lookup(self
, key
)));
567 * Returns the number of referenced objects
570 wmap_size(VALUE self
)
573 TypedData_Get_Struct(self
, struct weakmap
, &weakmap_type
, w
);
575 st_index_t n
= st_table_size(w
->table
);
577 #if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
584 /* ===== WeakKeyMap =====
586 * WeakKeyMap contains one ST table which contains a pointer to the object as
587 * the key and the object as the value. This means that the key is of the type
588 * `VALUE *` while the value is of the type `VALUE`.
590 * The object is not not directly stored as keys in the table because
591 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
592 * when the object is reclaimed. Using a pointer into the ST table entry is not
593 * safe because the pointer can change when the ST table is resized.
595 * WeakKeyMap hashes and compares using the `#hash` and `#==` methods of the
596 * object, respectively.
598 * During GC and while iterating, reclaimed entries (i.e. the key points to
599 * `Qundef`) are removed from the ST table.
607 wkmap_mark_table_i(st_data_t key
, st_data_t val_obj
, st_data_t _
)
609 VALUE key_obj
= *(VALUE
*)key
;
611 if (wmap_live_p(key_obj
)) {
612 rb_gc_mark_weak((VALUE
*)key
);
613 rb_gc_mark_movable((VALUE
)val_obj
);
618 ruby_sized_xfree((VALUE
*)key
, sizeof(VALUE
));
625 wkmap_mark(void *ptr
)
627 struct weakkeymap
*w
= ptr
;
629 st_foreach(w
->table
, wkmap_mark_table_i
, (st_data_t
)0);
634 wkmap_free_table_i(st_data_t key
, st_data_t _val
, st_data_t _arg
)
636 ruby_sized_xfree((VALUE
*)key
, sizeof(VALUE
));
641 wkmap_free(void *ptr
)
643 struct weakkeymap
*w
= ptr
;
645 st_foreach(w
->table
, wkmap_free_table_i
, 0);
646 st_free_table(w
->table
);
650 wkmap_memsize(const void *ptr
)
652 const struct weakkeymap
*w
= ptr
;
655 size
+= st_memsize(w
->table
);
656 /* Each key of the table takes sizeof(VALUE) in size. */
657 size
+= st_table_size(w
->table
) * sizeof(VALUE
);
663 wkmap_compact_table_i(st_data_t key
, st_data_t val_obj
, st_data_t _data
, int _error
)
665 VALUE key_obj
= *(VALUE
*)key
;
667 if (wmap_live_p(key_obj
)) {
668 if (key_obj
!= rb_gc_location(key_obj
) || val_obj
!= rb_gc_location(val_obj
)) {
673 ruby_sized_xfree((VALUE
*)key
, sizeof(VALUE
));
682 wkmap_compact_table_replace(st_data_t
*key_ptr
, st_data_t
*val_ptr
, st_data_t _data
, int existing
)
686 *(VALUE
*)*key_ptr
= rb_gc_location(*(VALUE
*)*key_ptr
);
687 *val_ptr
= (st_data_t
)rb_gc_location((VALUE
)*val_ptr
);
693 wkmap_compact(void *ptr
)
695 struct weakkeymap
*w
= ptr
;
698 st_foreach_with_replace(w
->table
, wkmap_compact_table_i
, wkmap_compact_table_replace
, (st_data_t
)0);
702 static const rb_data_type_t weakkeymap_type
= {
710 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
| RUBY_TYPED_WB_PROTECTED
| RUBY_TYPED_EMBEDDABLE
714 wkmap_cmp(st_data_t x
, st_data_t y
)
716 VALUE x_obj
= *(VALUE
*)x
;
717 VALUE y_obj
= *(VALUE
*)y
;
719 if (wmap_live_p(x_obj
) && wmap_live_p(y_obj
)) {
720 return rb_any_cmp(x_obj
, y_obj
);
723 /* If one of the objects is dead, then they cannot be the same. */
729 wkmap_hash(st_data_t n
)
731 VALUE obj
= *(VALUE
*)n
;
732 assert(wmap_live_p(obj
));
734 return rb_any_hash(obj
);
737 static const struct st_hash_type wkmap_hash_type
= {
743 wkmap_allocate(VALUE klass
)
745 struct weakkeymap
*w
;
746 VALUE obj
= TypedData_Make_Struct(klass
, struct weakkeymap
, &weakkeymap_type
, w
);
747 w
->table
= st_init_table(&wkmap_hash_type
);
752 wkmap_lookup(VALUE self
, VALUE key
)
754 struct weakkeymap
*w
;
755 TypedData_Get_Struct(self
, struct weakkeymap
, &weakkeymap_type
, w
);
758 if (!st_lookup(w
->table
, (st_data_t
)&key
, &data
)) return Qundef
;
767 * Returns the value associated with the given +key+ if found.
769 * If +key+ is not found, returns +nil+.
772 wkmap_aref(VALUE self
, VALUE key
)
774 VALUE obj
= wkmap_lookup(self
, key
);
775 return obj
!= Qundef
? obj
: Qnil
;
778 struct wkmap_aset_args
{
784 wkmap_aset_replace(st_data_t
*key
, st_data_t
*val
, st_data_t data_args
, int existing
)
786 struct wkmap_aset_args
*args
= (struct wkmap_aset_args
*)data_args
;
789 *key
= (st_data_t
)xmalloc(sizeof(VALUE
));
792 *(VALUE
*)*key
= args
->new_key
;
793 *val
= (st_data_t
)args
->new_val
;
800 * map[key] = value -> value
802 * Associates the given +value+ with the given +key+
804 * The reference to +key+ is weak, so when there is no other reference
805 * to +key+ it may be garbage collected.
807 * If the given +key+ exists, replaces its value with the given +value+;
808 * the ordering is not affected
811 wkmap_aset(VALUE self
, VALUE key
, VALUE val
)
813 struct weakkeymap
*w
;
814 TypedData_Get_Struct(self
, struct weakkeymap
, &weakkeymap_type
, w
);
816 if (!FL_ABLE(key
) || SYMBOL_P(key
) || RB_BIGNUM_TYPE_P(key
) || RB_TYPE_P(key
, T_FLOAT
)) {
817 rb_raise(rb_eArgError
, "WeakKeyMap must be garbage collectable");
818 UNREACHABLE_RETURN(Qnil
);
821 struct wkmap_aset_args args
= {
826 st_update(w
->table
, (st_data_t
)&key
, wkmap_aset_replace
, (st_data_t
)&args
);
828 RB_OBJ_WRITTEN(self
, Qundef
, key
);
829 RB_OBJ_WRITTEN(self
, Qundef
, val
);
836 * map.delete(key) -> value or nil
837 * map.delete(key) {|key| ... } -> object
839 * Deletes the entry for the given +key+ and returns its associated value.
841 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
842 * m = ObjectSpace::WeakKeyMap.new
843 * key = "foo" # to hold reference to the key
845 * m.delete("foo") # => 1
848 * If no block given and +key+ is not found, returns +nil+.
850 * If a block is given and +key+ is found, ignores the block,
851 * deletes the entry, and returns the associated value:
852 * m = ObjectSpace::WeakKeyMap.new
853 * key = "foo" # to hold reference to the key
855 * m.delete("foo") { |key| raise 'Will never happen'} # => 2
857 * If a block is given and +key+ is not found,
858 * yields the +key+ to the block and returns the block's return value:
859 * m = ObjectSpace::WeakKeyMap.new
860 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
864 wkmap_delete(VALUE self
, VALUE key
)
866 struct weakkeymap
*w
;
867 TypedData_Get_Struct(self
, struct weakkeymap
, &weakkeymap_type
, w
);
869 VALUE orig_key
= key
;
870 st_data_t orig_key_data
= (st_data_t
)&orig_key
;
871 st_data_t orig_val_data
;
872 if (st_delete(w
->table
, &orig_key_data
, &orig_val_data
)) {
873 VALUE orig_val
= (VALUE
)orig_val_data
;
875 rb_gc_remove_weak(self
, (VALUE
*)orig_key_data
);
877 ruby_sized_xfree((VALUE
*)orig_key_data
, sizeof(VALUE
));
882 if (rb_block_given_p()) {
883 return rb_yield(key
);
892 * map.getkey(key) -> existing_key or nil
894 * Returns the existing equal key if it exists, otherwise returns +nil+.
896 * This might be useful for implementing caches, so that only one copy of
897 * some object would be used everywhere in the program:
899 * value = {amount: 1, currency: 'USD'}
901 * # Now if we put this object in a cache:
902 * cache = ObjectSpace::WeakKeyMap.new
903 * cache[value] = true
905 * # ...we can always extract from there and use the same object:
906 * copy = cache.getkey({amount: 1, currency: 'USD'})
907 * copy.object_id == value.object_id #=> true
910 wkmap_getkey(VALUE self
, VALUE key
)
912 struct weakkeymap
*w
;
913 TypedData_Get_Struct(self
, struct weakkeymap
, &weakkeymap_type
, w
);
916 if (!st_get_key(w
->table
, (st_data_t
)&key
, &orig_key
)) return Qnil
;
918 return *(VALUE
*)orig_key
;
923 * map.key?(key) -> true or false
925 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
928 wkmap_has_key(VALUE self
, VALUE key
)
930 return RBOOL(wkmap_lookup(self
, key
) != Qundef
);
937 * Removes all map entries; returns +self+.
940 wkmap_clear(VALUE self
)
942 struct weakkeymap
*w
;
943 TypedData_Get_Struct(self
, struct weakkeymap
, &weakkeymap_type
, w
);
945 st_foreach(w
->table
, wkmap_free_table_i
, 0);
953 * map.inspect -> new_string
955 * Returns a new String containing informations about the map:
957 * m = ObjectSpace::WeakKeyMap.new
959 * m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
963 wkmap_inspect(VALUE self
)
965 struct weakkeymap
*w
;
966 TypedData_Get_Struct(self
, struct weakkeymap
, &weakkeymap_type
, w
);
968 st_index_t n
= st_table_size(w
->table
);
970 #if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
971 const char * format
= "#<%"PRIsVALUE
":%p size=%lu>";
973 const char * format
= "#<%"PRIsVALUE
":%p size=%llu>";
976 VALUE str
= rb_sprintf(format
, rb_class_name(CLASS_OF(self
)), (void *)self
, n
);
981 * Document-class: ObjectSpace::WeakMap
983 * An ObjectSpace::WeakMap is a key-value map that holds weak references
984 * to its keys and values, so they can be garbage-collected when there are
985 * no more references left.
987 * Keys in the map are compared by identity.
989 * m = ObjectSpace::WeekMap.new
998 * m[key1] #=> #<Object:0x0...>
999 * m[key2] #=> #<Object:0x0...>
1001 * val1 = nil # remove the other reference to value
1005 * m.keys #=> ["bar"]
1007 * key2 = nil # remove the other reference to key
1013 * (Note that GC.start is used here only for demonstrational purposes and might
1014 * not always lead to demonstrated results.)
1017 * See also ObjectSpace::WeakKeyMap map class, which compares keys by value,
1018 * and holds weak references only to the keys.
1022 * Document-class: ObjectSpace::WeakKeyMap
1024 * An ObjectSpace::WeakKeyMap is a key-value map that holds weak references
1025 * to its keys, so they can be garbage collected when there is no more references.
1027 * Unlike ObjectSpace::WeakMap:
1029 * * references to values are _strong_, so they aren't garbage collected while
1030 * they are in the map;
1031 * * keys are compared by value (using Object#eql?), not by identity;
1032 * * only garbage-collectable objects can be used as keys.
1034 * map = ObjectSpace::WeakKeyMap.new
1035 * val = Time.new(2023, 12, 7)
1039 * # Value is fetched by equality: the instance of string "name" is
1040 * # different here, but it is equal to the key
1041 * map["name"] #=> 2023-12-07 00:00:00 +0200
1045 * # There is no more references to `val`, yet the pair isn't
1046 * # garbage-collected.
1047 * map["name"] #=> 2023-12-07 00:00:00 +0200
1051 * # There is no more references to `key`, key and value are
1052 * # garbage-collected.
1053 * map["name"] #=> nil
1055 * (Note that GC.start is used here only for demonstrational purposes and might
1056 * not always lead to demonstrated results.)
1058 * The collection is especially useful for implementing caches of lightweight value
1059 * objects, so that only one copy of each value representation would be stored in
1060 * memory, but the copies that aren't used would be garbage-collected.
1062 * CACHE = ObjectSpace::WeakKeyMap
1064 * def make_value(**)
1065 * val = ValueObject.new(**)
1066 * if (existing = @cache.getkey(val))
1067 * # if the object with this value exists, we return it
1070 * # otherwise, put it in the cache
1071 * @cache[val] = true
1076 * This will result in +make_value+ returning the same object for same set of attributes
1077 * always, but the values that aren't needed anymore woudn't be sitting in the cache forever.
1083 VALUE rb_mObjectSpace
= rb_define_module("ObjectSpace");
1085 VALUE rb_cWeakMap
= rb_define_class_under(rb_mObjectSpace
, "WeakMap", rb_cObject
);
1086 rb_define_alloc_func(rb_cWeakMap
, wmap_allocate
);
1087 rb_define_method(rb_cWeakMap
, "[]=", wmap_aset
, 2);
1088 rb_define_method(rb_cWeakMap
, "[]", wmap_aref
, 1);
1089 rb_define_method(rb_cWeakMap
, "delete", wmap_delete
, 1);
1090 rb_define_method(rb_cWeakMap
, "include?", wmap_has_key
, 1);
1091 rb_define_method(rb_cWeakMap
, "member?", wmap_has_key
, 1);
1092 rb_define_method(rb_cWeakMap
, "key?", wmap_has_key
, 1);
1093 rb_define_method(rb_cWeakMap
, "inspect", wmap_inspect
, 0);
1094 rb_define_method(rb_cWeakMap
, "each", wmap_each
, 0);
1095 rb_define_method(rb_cWeakMap
, "each_pair", wmap_each
, 0);
1096 rb_define_method(rb_cWeakMap
, "each_key", wmap_each_key
, 0);
1097 rb_define_method(rb_cWeakMap
, "each_value", wmap_each_value
, 0);
1098 rb_define_method(rb_cWeakMap
, "keys", wmap_keys
, 0);
1099 rb_define_method(rb_cWeakMap
, "values", wmap_values
, 0);
1100 rb_define_method(rb_cWeakMap
, "size", wmap_size
, 0);
1101 rb_define_method(rb_cWeakMap
, "length", wmap_size
, 0);
1102 rb_include_module(rb_cWeakMap
, rb_mEnumerable
);
1104 VALUE rb_cWeakKeyMap
= rb_define_class_under(rb_mObjectSpace
, "WeakKeyMap", rb_cObject
);
1105 rb_define_alloc_func(rb_cWeakKeyMap
, wkmap_allocate
);
1106 rb_define_method(rb_cWeakKeyMap
, "[]=", wkmap_aset
, 2);
1107 rb_define_method(rb_cWeakKeyMap
, "[]", wkmap_aref
, 1);
1108 rb_define_method(rb_cWeakKeyMap
, "delete", wkmap_delete
, 1);
1109 rb_define_method(rb_cWeakKeyMap
, "getkey", wkmap_getkey
, 1);
1110 rb_define_method(rb_cWeakKeyMap
, "key?", wkmap_has_key
, 1);
1111 rb_define_method(rb_cWeakKeyMap
, "clear", wkmap_clear
, 0);
1112 rb_define_method(rb_cWeakKeyMap
, "inspect", wkmap_inspect
, 0);