[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / weakmap.c
blob8939056811ef748e1573d36c2a53d0f6d6e9b8d1
1 #include "internal.h"
2 #include "internal/gc.h"
3 #include "internal/hash.h"
4 #include "internal/proc.h"
5 #include "internal/sanitizers.h"
6 #include "ruby/st.h"
8 /* ===== WeakMap =====
10 * WeakMap contains one ST table which contains a pointer to the object as the
11 * key and a pointer to the object as the value. This means that the key and
12 * value of the table are both of the type `VALUE *`.
14 * The objects are not directly stored as keys and values in the table because
15 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
16 * when the object is reclaimed. Using a pointer into the ST table entry is not
17 * safe because the pointer can change when the ST table is resized.
19 * WeakMap hashes and compares using the pointer address of the object.
21 * For performance and memory efficiency reasons, the key and value
22 * are allocated at the same time and adjacent to each other.
24 * During GC and while iterating, reclaimed entries (i.e. either the key or
25 * value points to `Qundef`) are removed from the ST table.
28 struct weakmap {
29 st_table *table;
32 static bool
33 wmap_live_p(VALUE obj)
35 return !UNDEF_P(obj);
38 static void
39 wmap_free_entry(VALUE *key, VALUE *val)
41 RUBY_ASSERT(key + 1 == val);
43 /* We only need to free key because val is allocated beside key on in the
44 * same malloc call. */
45 ruby_sized_xfree(key, sizeof(VALUE) * 2);
48 static int
49 wmap_mark_weak_table_i(st_data_t key, st_data_t val, st_data_t _)
51 VALUE key_obj = *(VALUE *)key;
52 VALUE val_obj = *(VALUE *)val;
54 if (wmap_live_p(key_obj) && wmap_live_p(val_obj)) {
55 rb_gc_mark_weak((VALUE *)key);
56 rb_gc_mark_weak((VALUE *)val);
58 return ST_CONTINUE;
60 else {
61 wmap_free_entry((VALUE *)key, (VALUE *)val);
63 return ST_DELETE;
67 static void
68 wmap_mark(void *ptr)
70 struct weakmap *w = ptr;
71 if (w->table) {
72 st_foreach(w->table, wmap_mark_weak_table_i, (st_data_t)0);
76 static int
77 wmap_free_table_i(st_data_t key, st_data_t val, st_data_t arg)
79 wmap_free_entry((VALUE *)key, (VALUE *)val);
80 return ST_CONTINUE;
83 static void
84 wmap_free(void *ptr)
86 struct weakmap *w = ptr;
88 st_foreach(w->table, wmap_free_table_i, 0);
89 st_free_table(w->table);
92 static size_t
93 wmap_memsize(const void *ptr)
95 const struct weakmap *w = ptr;
97 size_t size = 0;
98 size += st_memsize(w->table);
99 /* The key and value of the table each take sizeof(VALUE) in size. */
100 size += st_table_size(w->table) * (2 * sizeof(VALUE));
102 return size;
105 static int
106 wmap_compact_table_i(st_data_t key, st_data_t val, st_data_t data)
108 st_table *table = (st_table *)data;
110 VALUE key_obj = *(VALUE *)key;
111 VALUE val_obj = *(VALUE *)val;
113 if (wmap_live_p(key_obj) && wmap_live_p(val_obj)) {
114 VALUE new_key_obj = rb_gc_location(key_obj);
116 *(VALUE *)val = rb_gc_location(val_obj);
118 /* If the key object moves, then we must reinsert because the hash is
119 * based on the pointer rather than the object itself. */
120 if (key_obj != new_key_obj) {
121 *(VALUE *)key = new_key_obj;
123 DURING_GC_COULD_MALLOC_REGION_START();
125 st_insert(table, key, val);
127 DURING_GC_COULD_MALLOC_REGION_END();
129 return ST_DELETE;
132 else {
133 wmap_free_entry((VALUE *)key, (VALUE *)val);
135 return ST_DELETE;
138 return ST_CONTINUE;
141 static void
142 wmap_compact(void *ptr)
144 struct weakmap *w = ptr;
146 if (w->table) {
147 st_foreach(w->table, wmap_compact_table_i, (st_data_t)w->table);
151 static const rb_data_type_t weakmap_type = {
152 "weakmap",
154 wmap_mark,
155 wmap_free,
156 wmap_memsize,
157 wmap_compact,
159 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
162 static int
163 wmap_cmp(st_data_t x, st_data_t y)
165 return *(VALUE *)x != *(VALUE *)y;
168 static st_index_t
169 wmap_hash(st_data_t n)
171 return st_numhash(*(VALUE *)n);
174 static const struct st_hash_type wmap_hash_type = {
175 wmap_cmp,
176 wmap_hash,
179 static VALUE
180 wmap_allocate(VALUE klass)
182 struct weakmap *w;
183 VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &weakmap_type, w);
184 w->table = st_init_table(&wmap_hash_type);
185 return obj;
188 struct wmap_foreach_data {
189 struct weakmap *w;
190 void (*func)(VALUE, VALUE, st_data_t);
191 st_data_t arg;
194 static int
195 wmap_foreach_i(st_data_t key, st_data_t val, st_data_t arg)
197 struct wmap_foreach_data *data = (struct wmap_foreach_data *)arg;
199 VALUE key_obj = *(VALUE *)key;
200 VALUE val_obj = *(VALUE *)val;
202 if (wmap_live_p(key_obj) && wmap_live_p(val_obj)) {
203 data->func(key_obj, val_obj, data->arg);
205 else {
206 wmap_free_entry((VALUE *)key, (VALUE *)val);
208 return ST_DELETE;
211 return ST_CONTINUE;
214 static void
215 wmap_foreach(struct weakmap *w, void (*func)(VALUE, VALUE, st_data_t), st_data_t arg)
217 struct wmap_foreach_data foreach_data = {
218 .w = w,
219 .func = func,
220 .arg = arg,
223 st_foreach(w->table, wmap_foreach_i, (st_data_t)&foreach_data);
226 static VALUE
227 wmap_inspect_append(VALUE str, VALUE obj)
229 if (SPECIAL_CONST_P(obj)) {
230 return rb_str_append(str, rb_inspect(obj));
232 else {
233 return rb_str_append(str, rb_any_to_s(obj));
237 static void
238 wmap_inspect_i(VALUE key, VALUE val, st_data_t data)
240 VALUE str = (VALUE)data;
242 if (RSTRING_PTR(str)[0] == '#') {
243 rb_str_cat2(str, ", ");
245 else {
246 rb_str_cat2(str, ": ");
247 RSTRING_PTR(str)[0] = '#';
250 wmap_inspect_append(str, key);
251 rb_str_cat2(str, " => ");
252 wmap_inspect_append(str, val);
255 static VALUE
256 wmap_inspect(VALUE self)
258 VALUE c = rb_class_name(CLASS_OF(self));
259 struct weakmap *w;
260 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
262 VALUE str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self);
264 wmap_foreach(w, wmap_inspect_i, (st_data_t)str);
266 RSTRING_PTR(str)[0] = '#';
267 rb_str_cat2(str, ">");
269 return str;
272 static void
273 wmap_each_i(VALUE key, VALUE val, st_data_t _)
275 rb_yield_values(2, key, val);
279 * call-seq:
280 * map.each {|key, val| ... } -> self
282 * Iterates over keys and values. Note that unlike other collections,
283 * +each+ without block isn't supported.
286 static VALUE
287 wmap_each(VALUE self)
289 struct weakmap *w;
290 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
292 wmap_foreach(w, wmap_each_i, (st_data_t)0);
294 return self;
297 static void
298 wmap_each_key_i(VALUE key, VALUE _val, st_data_t _data)
300 rb_yield(key);
304 * call-seq:
305 * map.each_key {|key| ... } -> self
307 * Iterates over keys. Note that unlike other collections,
308 * +each_key+ without block isn't supported.
311 static VALUE
312 wmap_each_key(VALUE self)
314 struct weakmap *w;
315 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
317 wmap_foreach(w, wmap_each_key_i, (st_data_t)0);
319 return self;
322 static void
323 wmap_each_value_i(VALUE _key, VALUE val, st_data_t _data)
325 rb_yield(val);
329 * call-seq:
330 * map.each_value {|val| ... } -> self
332 * Iterates over values. Note that unlike other collections,
333 * +each_value+ without block isn't supported.
336 static VALUE
337 wmap_each_value(VALUE self)
339 struct weakmap *w;
340 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
342 wmap_foreach(w, wmap_each_value_i, (st_data_t)0);
344 return self;
347 static void
348 wmap_keys_i(st_data_t key, st_data_t _, st_data_t arg)
350 VALUE ary = (VALUE)arg;
352 rb_ary_push(ary, key);
356 * call-seq:
357 * map.keys -> new_array
359 * Returns a new Array containing all keys in the map.
362 static VALUE
363 wmap_keys(VALUE self)
365 struct weakmap *w;
366 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
368 VALUE ary = rb_ary_new();
369 wmap_foreach(w, wmap_keys_i, (st_data_t)ary);
371 return ary;
374 static void
375 wmap_values_i(st_data_t key, st_data_t val, st_data_t arg)
377 VALUE ary = (VALUE)arg;
379 rb_ary_push(ary, (VALUE)val);
383 * call-seq:
384 * map.values -> new_array
386 * Returns a new Array containing all values in the map.
389 static VALUE
390 wmap_values(VALUE self)
392 struct weakmap *w;
393 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
395 VALUE ary = rb_ary_new();
396 wmap_foreach(w, wmap_values_i, (st_data_t)ary);
398 return ary;
401 static VALUE
402 nonspecial_obj_id(VALUE obj)
404 #if SIZEOF_LONG == SIZEOF_VOIDP
405 return (VALUE)((SIGNED_VALUE)(obj)|FIXNUM_FLAG);
406 #elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
407 return LL2NUM((SIGNED_VALUE)(obj) / 2);
408 #else
409 # error not supported
410 #endif
413 static int
414 wmap_aset_replace(st_data_t *key, st_data_t *val, st_data_t new_key_ptr, int existing)
416 VALUE new_key = *(VALUE *)new_key_ptr;
417 VALUE new_val = *(((VALUE *)new_key_ptr) + 1);
419 if (existing) {
420 RUBY_ASSERT(*(VALUE *)*key == new_key);
422 else {
423 VALUE *pair = xmalloc(sizeof(VALUE) * 2);
425 *key = (st_data_t)pair;
426 *val = (st_data_t)(pair + 1);
429 *(VALUE *)*key = new_key;
430 *(VALUE *)*val = new_val;
432 return ST_CONTINUE;
436 * call-seq:
437 * map[key] = value -> value
439 * Associates the given +value+ with the given +key+.
441 * If the given +key+ exists, replaces its value with the given +value+;
442 * the ordering is not affected.
444 static VALUE
445 wmap_aset(VALUE self, VALUE key, VALUE val)
447 struct weakmap *w;
448 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
450 VALUE pair[2] = { key, val };
452 st_update(w->table, (st_data_t)pair, wmap_aset_replace, (st_data_t)pair);
454 RB_OBJ_WRITTEN(self, Qundef, key);
455 RB_OBJ_WRITTEN(self, Qundef, val);
457 return nonspecial_obj_id(val);
460 /* Retrieves a weakly referenced object with the given key */
461 static VALUE
462 wmap_lookup(VALUE self, VALUE key)
464 RUBY_ASSERT(wmap_live_p(key));
466 struct weakmap *w;
467 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
469 st_data_t data;
470 if (!st_lookup(w->table, (st_data_t)&key, &data)) return Qundef;
472 if (!wmap_live_p(*(VALUE *)data)) return Qundef;
474 return *(VALUE *)data;
478 * call-seq:
479 * map[key] -> value
481 * Returns the value associated with the given +key+ if found.
483 * If +key+ is not found, returns +nil+.
485 static VALUE
486 wmap_aref(VALUE self, VALUE key)
488 VALUE obj = wmap_lookup(self, key);
489 return !UNDEF_P(obj) ? obj : Qnil;
493 * call-seq:
494 * map.delete(key) -> value or nil
495 * map.delete(key) {|key| ... } -> object
497 * Deletes the entry for the given +key+ and returns its associated value.
499 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
500 * m = ObjectSpace::WeakMap.new
501 * key = "foo"
502 * m[key] = 1
503 * m.delete(key) # => 1
504 * m[key] # => nil
506 * If no block is given and +key+ is not found, returns +nil+.
508 * If a block is given and +key+ is found, ignores the block,
509 * deletes the entry, and returns the associated value:
510 * m = ObjectSpace::WeakMap.new
511 * key = "foo"
512 * m[key] = 2
513 * m.delete(key) { |key| raise 'Will never happen'} # => 2
515 * If a block is given and +key+ is not found,
516 * yields the +key+ to the block and returns the block's return value:
517 * m = ObjectSpace::WeakMap.new
518 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
520 static VALUE
521 wmap_delete(VALUE self, VALUE key)
523 struct weakmap *w;
524 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
526 VALUE orig_key = key;
527 st_data_t orig_key_data = (st_data_t)&orig_key;
528 st_data_t orig_val_data;
529 if (st_delete(w->table, &orig_key_data, &orig_val_data)) {
530 VALUE orig_val = *(VALUE *)orig_val_data;
532 rb_gc_remove_weak(self, (VALUE *)orig_key_data);
533 rb_gc_remove_weak(self, (VALUE *)orig_val_data);
535 wmap_free_entry((VALUE *)orig_key_data, (VALUE *)orig_val_data);
537 if (wmap_live_p(orig_val)) {
538 return orig_val;
542 if (rb_block_given_p()) {
543 return rb_yield(key);
545 else {
546 return Qnil;
551 * call-seq:
552 * map.key?(key) -> true or false
554 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
556 static VALUE
557 wmap_has_key(VALUE self, VALUE key)
559 return RBOOL(!UNDEF_P(wmap_lookup(self, key)));
563 * call-seq:
564 * map.size -> number
566 * Returns the number of referenced objects
568 static VALUE
569 wmap_size(VALUE self)
571 struct weakmap *w;
572 TypedData_Get_Struct(self, struct weakmap, &weakmap_type, w);
574 st_index_t n = st_table_size(w->table);
576 #if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
577 return ULONG2NUM(n);
578 #else
579 return ULL2NUM(n);
580 #endif
583 /* ===== WeakKeyMap =====
585 * WeakKeyMap contains one ST table which contains a pointer to the object as
586 * the key and the object as the value. This means that the key is of the type
587 * `VALUE *` while the value is of the type `VALUE`.
589 * The object is not directly stored as keys in the table because
590 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
591 * when the object is reclaimed. Using a pointer into the ST table entry is not
592 * safe because the pointer can change when the ST table is resized.
594 * WeakKeyMap hashes and compares using the `#hash` and `#==` methods of the
595 * object, respectively.
597 * During GC and while iterating, reclaimed entries (i.e. the key points to
598 * `Qundef`) are removed from the ST table.
601 struct weakkeymap {
602 st_table *table;
605 static int
606 wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t _)
608 VALUE key_obj = *(VALUE *)key;
610 if (wmap_live_p(key_obj)) {
611 rb_gc_mark_weak((VALUE *)key);
612 rb_gc_mark_movable((VALUE)val_obj);
614 return ST_CONTINUE;
616 else {
617 ruby_sized_xfree((VALUE *)key, sizeof(VALUE));
619 return ST_DELETE;
623 static void
624 wkmap_mark(void *ptr)
626 struct weakkeymap *w = ptr;
627 if (w->table) {
628 st_foreach(w->table, wkmap_mark_table_i, (st_data_t)0);
632 static int
633 wkmap_free_table_i(st_data_t key, st_data_t _val, st_data_t _arg)
635 ruby_sized_xfree((VALUE *)key, sizeof(VALUE));
636 return ST_CONTINUE;
639 static void
640 wkmap_free(void *ptr)
642 struct weakkeymap *w = ptr;
644 st_foreach(w->table, wkmap_free_table_i, 0);
645 st_free_table(w->table);
648 static size_t
649 wkmap_memsize(const void *ptr)
651 const struct weakkeymap *w = ptr;
653 size_t size = 0;
654 size += st_memsize(w->table);
655 /* Each key of the table takes sizeof(VALUE) in size. */
656 size += st_table_size(w->table) * sizeof(VALUE);
658 return size;
661 static int
662 wkmap_compact_table_i(st_data_t key, st_data_t val_obj, st_data_t _data, int _error)
664 VALUE key_obj = *(VALUE *)key;
666 if (wmap_live_p(key_obj)) {
667 if (key_obj != rb_gc_location(key_obj) || val_obj != rb_gc_location(val_obj)) {
668 return ST_REPLACE;
671 else {
672 ruby_sized_xfree((VALUE *)key, sizeof(VALUE));
674 return ST_DELETE;
677 return ST_CONTINUE;
680 static int
681 wkmap_compact_table_replace(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t _data, int existing)
683 RUBY_ASSERT(existing);
685 *(VALUE *)*key_ptr = rb_gc_location(*(VALUE *)*key_ptr);
686 *val_ptr = (st_data_t)rb_gc_location((VALUE)*val_ptr);
688 return ST_CONTINUE;
691 static void
692 wkmap_compact(void *ptr)
694 struct weakkeymap *w = ptr;
696 if (w->table) {
697 st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)0);
701 static const rb_data_type_t weakkeymap_type = {
702 "weakkeymap",
704 wkmap_mark,
705 wkmap_free,
706 wkmap_memsize,
707 wkmap_compact,
709 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
712 static int
713 wkmap_cmp(st_data_t x, st_data_t y)
715 VALUE x_obj = *(VALUE *)x;
716 VALUE y_obj = *(VALUE *)y;
718 if (wmap_live_p(x_obj) && wmap_live_p(y_obj)) {
719 return rb_any_cmp(x_obj, y_obj);
721 else {
722 /* If one of the objects is dead, then they cannot be the same. */
723 return 1;
727 static st_index_t
728 wkmap_hash(st_data_t n)
730 VALUE obj = *(VALUE *)n;
731 RUBY_ASSERT(wmap_live_p(obj));
733 return rb_any_hash(obj);
736 static const struct st_hash_type wkmap_hash_type = {
737 wkmap_cmp,
738 wkmap_hash,
741 static VALUE
742 wkmap_allocate(VALUE klass)
744 struct weakkeymap *w;
745 VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &weakkeymap_type, w);
746 w->table = st_init_table(&wkmap_hash_type);
747 return obj;
750 static VALUE
751 wkmap_lookup(VALUE self, VALUE key)
753 struct weakkeymap *w;
754 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
756 st_data_t data;
757 if (!st_lookup(w->table, (st_data_t)&key, &data)) return Qundef;
759 return (VALUE)data;
763 * call-seq:
764 * map[key] -> value
766 * Returns the value associated with the given +key+ if found.
768 * If +key+ is not found, returns +nil+.
770 static VALUE
771 wkmap_aref(VALUE self, VALUE key)
773 VALUE obj = wkmap_lookup(self, key);
774 return !UNDEF_P(obj) ? obj : Qnil;
777 struct wkmap_aset_args {
778 VALUE new_key;
779 VALUE new_val;
782 static int
783 wkmap_aset_replace(st_data_t *key, st_data_t *val, st_data_t data_args, int existing)
785 struct wkmap_aset_args *args = (struct wkmap_aset_args *)data_args;
787 if (!existing) {
788 *key = (st_data_t)xmalloc(sizeof(VALUE));
791 *(VALUE *)*key = args->new_key;
792 *val = (st_data_t)args->new_val;
794 return ST_CONTINUE;
798 * call-seq:
799 * map[key] = value -> value
801 * Associates the given +value+ with the given +key+
803 * The reference to +key+ is weak, so when there is no other reference
804 * to +key+ it may be garbage collected.
806 * If the given +key+ exists, replaces its value with the given +value+;
807 * the ordering is not affected
809 static VALUE
810 wkmap_aset(VALUE self, VALUE key, VALUE val)
812 struct weakkeymap *w;
813 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
815 if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) {
816 rb_raise(rb_eArgError, "WeakKeyMap must be garbage collectable");
817 UNREACHABLE_RETURN(Qnil);
820 struct wkmap_aset_args args = {
821 .new_key = key,
822 .new_val = val,
825 st_update(w->table, (st_data_t)&key, wkmap_aset_replace, (st_data_t)&args);
827 RB_OBJ_WRITTEN(self, Qundef, key);
828 RB_OBJ_WRITTEN(self, Qundef, val);
830 return val;
834 * call-seq:
835 * map.delete(key) -> value or nil
836 * map.delete(key) {|key| ... } -> object
838 * Deletes the entry for the given +key+ and returns its associated value.
840 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
841 * m = ObjectSpace::WeakKeyMap.new
842 * key = "foo" # to hold reference to the key
843 * m[key] = 1
844 * m.delete("foo") # => 1
845 * m["foo"] # => nil
847 * If no block given and +key+ is not found, returns +nil+.
849 * If a block is given and +key+ is found, ignores the block,
850 * deletes the entry, and returns the associated value:
851 * m = ObjectSpace::WeakKeyMap.new
852 * key = "foo" # to hold reference to the key
853 * m[key] = 2
854 * m.delete("foo") { |key| raise 'Will never happen'} # => 2
856 * If a block is given and +key+ is not found,
857 * yields the +key+ to the block and returns the block's return value:
858 * m = ObjectSpace::WeakKeyMap.new
859 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
862 static VALUE
863 wkmap_delete(VALUE self, VALUE key)
865 struct weakkeymap *w;
866 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
868 VALUE orig_key = key;
869 st_data_t orig_key_data = (st_data_t)&orig_key;
870 st_data_t orig_val_data;
871 if (st_delete(w->table, &orig_key_data, &orig_val_data)) {
872 VALUE orig_val = (VALUE)orig_val_data;
874 rb_gc_remove_weak(self, (VALUE *)orig_key_data);
876 ruby_sized_xfree((VALUE *)orig_key_data, sizeof(VALUE));
878 return orig_val;
881 if (rb_block_given_p()) {
882 return rb_yield(key);
884 else {
885 return Qnil;
890 * call-seq:
891 * map.getkey(key) -> existing_key or nil
893 * Returns the existing equal key if it exists, otherwise returns +nil+.
895 * This might be useful for implementing caches, so that only one copy of
896 * some object would be used everywhere in the program:
898 * value = {amount: 1, currency: 'USD'}
900 * # Now if we put this object in a cache:
901 * cache = ObjectSpace::WeakKeyMap.new
902 * cache[value] = true
904 * # ...we can always extract from there and use the same object:
905 * copy = cache.getkey({amount: 1, currency: 'USD'})
906 * copy.object_id == value.object_id #=> true
908 static VALUE
909 wkmap_getkey(VALUE self, VALUE key)
911 struct weakkeymap *w;
912 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
914 st_data_t orig_key;
915 if (!st_get_key(w->table, (st_data_t)&key, &orig_key)) return Qnil;
917 return *(VALUE *)orig_key;
921 * call-seq:
922 * map.key?(key) -> true or false
924 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
926 static VALUE
927 wkmap_has_key(VALUE self, VALUE key)
929 return RBOOL(!UNDEF_P(wkmap_lookup(self, key)));
933 * call-seq:
934 * map.clear -> self
936 * Removes all map entries; returns +self+.
938 static VALUE
939 wkmap_clear(VALUE self)
941 struct weakkeymap *w;
942 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
944 st_foreach(w->table, wkmap_free_table_i, 0);
945 st_clear(w->table);
947 return self;
951 * call-seq:
952 * map.inspect -> new_string
954 * Returns a new String containing informations about the map:
956 * m = ObjectSpace::WeakKeyMap.new
957 * m[key] = value
958 * m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
961 static VALUE
962 wkmap_inspect(VALUE self)
964 struct weakkeymap *w;
965 TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);
967 st_index_t n = st_table_size(w->table);
969 #if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
970 const char * format = "#<%"PRIsVALUE":%p size=%lu>";
971 #else
972 const char * format = "#<%"PRIsVALUE":%p size=%llu>";
973 #endif
975 VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n);
976 return str;
980 * Document-class: ObjectSpace::WeakMap
982 * An ObjectSpace::WeakMap is a key-value map that holds weak references
983 * to its keys and values, so they can be garbage-collected when there are
984 * no more references left.
986 * Keys in the map are compared by identity.
988 * m = ObjectSpace::WeakMap.new
989 * key1 = "foo"
990 * val1 = Object.new
991 * m[key1] = val1
993 * key2 = "foo"
994 * val2 = Object.new
995 * m[key2] = val2
997 * m[key1] #=> #<Object:0x0...>
998 * m[key2] #=> #<Object:0x0...>
1000 * val1 = nil # remove the other reference to value
1001 * GC.start
1003 * m[key1] #=> nil
1004 * m.keys #=> ["bar"]
1006 * key2 = nil # remove the other reference to key
1007 * GC.start
1009 * m[key2] #=> nil
1010 * m.keys #=> []
1012 * (Note that GC.start is used here only for demonstrational purposes and might
1013 * not always lead to demonstrated results.)
1016 * See also ObjectSpace::WeakKeyMap map class, which compares keys by value,
1017 * and holds weak references only to the keys.
1021 * Document-class: ObjectSpace::WeakKeyMap
1023 * An ObjectSpace::WeakKeyMap is a key-value map that holds weak references
1024 * to its keys, so they can be garbage collected when there is no more references.
1026 * Unlike ObjectSpace::WeakMap:
1028 * * references to values are _strong_, so they aren't garbage collected while
1029 * they are in the map;
1030 * * keys are compared by value (using Object#eql?), not by identity;
1031 * * only garbage-collectable objects can be used as keys.
1033 * map = ObjectSpace::WeakKeyMap.new
1034 * val = Time.new(2023, 12, 7)
1035 * key = "name"
1036 * map[key] = val
1038 * # Value is fetched by equality: the instance of string "name" is
1039 * # different here, but it is equal to the key
1040 * map["name"] #=> 2023-12-07 00:00:00 +0200
1042 * val = nil
1043 * GC.start
1044 * # There are no more references to `val`, yet the pair isn't
1045 * # garbage-collected.
1046 * map["name"] #=> 2023-12-07 00:00:00 +0200
1048 * key = nil
1049 * GC.start
1050 * # There are no more references to `key`, key and value are
1051 * # garbage-collected.
1052 * map["name"] #=> nil
1054 * (Note that GC.start is used here only for demonstrational purposes and might
1055 * not always lead to demonstrated results.)
1057 * The collection is especially useful for implementing caches of lightweight value
1058 * objects, so that only one copy of each value representation would be stored in
1059 * memory, but the copies that aren't used would be garbage-collected.
1061 * CACHE = ObjectSpace::WeakKeyMap
1063 * def make_value(**)
1064 * val = ValueObject.new(**)
1065 * if (existing = @cache.getkey(val))
1066 * # if the object with this value exists, we return it
1067 * existing
1068 * else
1069 * # otherwise, put it in the cache
1070 * @cache[val] = true
1071 * val
1072 * end
1073 * end
1075 * This will result in +make_value+ returning the same object for same set of attributes
1076 * always, but the values that aren't needed anymore woudn't be sitting in the cache forever.
1079 void
1080 Init_WeakMap(void)
1082 VALUE rb_mObjectSpace = rb_define_module("ObjectSpace");
1084 VALUE rb_cWeakMap = rb_define_class_under(rb_mObjectSpace, "WeakMap", rb_cObject);
1085 rb_define_alloc_func(rb_cWeakMap, wmap_allocate);
1086 rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2);
1087 rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1);
1088 rb_define_method(rb_cWeakMap, "delete", wmap_delete, 1);
1089 rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1);
1090 rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1);
1091 rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1);
1092 rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0);
1093 rb_define_method(rb_cWeakMap, "each", wmap_each, 0);
1094 rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0);
1095 rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0);
1096 rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0);
1097 rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0);
1098 rb_define_method(rb_cWeakMap, "values", wmap_values, 0);
1099 rb_define_method(rb_cWeakMap, "size", wmap_size, 0);
1100 rb_define_method(rb_cWeakMap, "length", wmap_size, 0);
1101 rb_include_module(rb_cWeakMap, rb_mEnumerable);
1103 VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjectSpace, "WeakKeyMap", rb_cObject);
1104 rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate);
1105 rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2);
1106 rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1);
1107 rb_define_method(rb_cWeakKeyMap, "delete", wkmap_delete, 1);
1108 rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1);
1109 rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1);
1110 rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0);
1111 rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0);