Ruby 4.1.0dev (2026-09-07 revision b57404b461ba8bf34e802d86b0db78388216e182)
weakmap.c (b57404b461ba8bf34e802d86b0db78388216e182)
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"
7
8/* ===== WeakMap =====
9 *
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 *`.
13 *
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.
18 *
19 * WeakMap hashes and compares using the pointer address of the object.
20 *
21 * For performance and memory efficiency reasons, the key and value
22 * are allocated at the same time and adjacent to each other.
23 *
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.
26 */
27
28struct weakmap {
29 st_table *table;
30};
31
33 VALUE key;
34 VALUE val;
35};
36
37static void
38wmap_free(void *ptr)
39{
40 struct weakmap *w = ptr;
41
42 st_free_table(w->table);
43}
44
45static size_t
46wmap_memsize(const void *ptr)
47{
48 const struct weakmap *w = ptr;
49
50 size_t size = 0;
51 if (w->table) {
52 size += st_memsize(w->table);
53 }
54
55 return size;
56}
57
59 st_table *table;
60 struct weakmap_entry *dead_entry;
61};
62
63static int
64wmap_compact_table_each_i(st_data_t k, st_data_t v, st_data_t d, int error)
65{
66 st_table *table = (st_table *)d;
67
68 VALUE key = (VALUE)k;
69 VALUE val = (VALUE)v;
70
71 VALUE moved_key = rb_gc_location(key);
72 VALUE moved_val = rb_gc_location(val);
73
74 /* If the key object moves, then we must reinsert because the hash is
75 * based on the pointer rather than the object itself. */
76 if (key != moved_key) {
77 st_insert(table, (st_data_t)moved_key, (st_data_t)moved_val);
78
79 return ST_DELETE;
80 }
81 else if (val != moved_val) {
82 return ST_REPLACE;
83 }
84 else {
85 return ST_CONTINUE;
86 }
87}
88
89static int
90wmap_compact_table_replace_i(st_data_t *k, st_data_t *v, st_data_t d, int existing)
91{
92 RUBY_ASSERT((VALUE)*k == rb_gc_location((VALUE)*k));
93
94 *v = (st_data_t)rb_gc_location((VALUE)*v);
95
96 return ST_CONTINUE;
97}
98
99static void
100wmap_compact(void *ptr)
101{
102 struct weakmap *w = ptr;
103
104 if (w->table) {
105 DURING_GC_COULD_MALLOC_REGION_START();
106 {
107 st_foreach_with_replace(w->table, wmap_compact_table_each_i, wmap_compact_table_replace_i, (st_data_t)w->table);
108 }
109 DURING_GC_COULD_MALLOC_REGION_END();
110 }
111}
112
113static int
114rb_wmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg)
115{
116 if (rb_gc_handle_weak_references_alive_p(key) &&
117 rb_gc_handle_weak_references_alive_p(val)) {
118 return ST_CONTINUE;
119 }
120 else {
121 return ST_DELETE;
122 }
123}
124
125static void
126wmap_handle_weak_references(void *ptr)
127{
128 struct weakmap *w = ptr;
129
130 st_foreach(w->table, rb_wmap_handle_weak_references_i, (st_data_t)0);
131}
132
133static const rb_data_type_t rb_weakmap_type = {
134 "weakmap",
135 {
136 NULL,
137 wmap_free,
138 wmap_memsize,
139 wmap_compact,
140 wmap_handle_weak_references,
141 },
142 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
143};
144
145static int
146wmap_cmp(st_data_t x, st_data_t y)
147{
148 return x != y;
149}
150
151static st_index_t
152wmap_hash(st_data_t n)
153{
154 return st_numhash(n);
155}
156
157static const struct st_hash_type wmap_hash_type = {
158 wmap_cmp,
159 wmap_hash,
160};
161
162static VALUE
163wmap_allocate(VALUE klass)
164{
165 struct weakmap *w;
166 VALUE obj = TypedData_Make_Struct(klass, struct weakmap, &rb_weakmap_type, w);
167
168 w->table = st_init_table(&wmap_hash_type);
169
170 rb_gc_declare_weak_references(obj);
171
172 return obj;
173}
174
175static VALUE
176wmap_inspect_append(VALUE str, VALUE obj)
177{
178 if (SPECIAL_CONST_P(obj)) {
179 return rb_str_append(str, rb_inspect(obj));
180 }
181 else {
182 return rb_str_append(str, rb_any_to_s(obj));
183 }
184}
185
186static int
187wmap_inspect_i(st_data_t k, st_data_t v, st_data_t data)
188{
189 VALUE key = (VALUE)k;
190 VALUE val = (VALUE)v;
191 VALUE str = (VALUE)data;
192
193 if (RSTRING_PTR(str)[0] == '#') {
194 rb_str_cat2(str, ", ");
195 }
196 else {
197 rb_str_cat2(str, ": ");
198 RSTRING_PTR(str)[0] = '#';
199 }
200
201 wmap_inspect_append(str, key);
202 rb_str_cat2(str, " => ");
203 wmap_inspect_append(str, val);
204
205 return ST_CONTINUE;
206}
207
208/* call-seq:
209 * inspect -> new_string
210 *
211 * Returns a new string containing the \WeakMap entries:
212 *
213 * m = ObjectSpace::WeakMap.new
214 * m["one"] = 1
215 * m["two"] = 2
216 * m.inspect
217 * # => "#<ObjectSpace::WeakMap:0x00007c457b2523e8: #<String:0x00007c457b2674f0> => 1, #<String:0x00007c457b27b8d8> => 2>"
218 */
219static VALUE
220wmap_inspect(VALUE self)
221{
222 VALUE c = rb_class_name(CLASS_OF(self));
223 struct weakmap *w;
224 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
225
226 VALUE str = rb_sprintf("-<%"PRIsVALUE":%p", c, (void *)self);
227
228 st_foreach(w->table, wmap_inspect_i, (st_data_t)str);
229
230 RSTRING_PTR(str)[0] = '#';
231 rb_str_cat2(str, ">");
232
233 return str;
234}
235
236static int
237wmap_each_i(st_data_t k, st_data_t v, st_data_t _)
238{
239 rb_yield_values(2, (VALUE)k, (VALUE)v);
240
241 return ST_CONTINUE;
242}
243
244/*
245 * call-seq:
246 * map.each {|key, val| ... } -> self
247 *
248 * Iterates over keys and values. Note that unlike other collections,
249 * +each+ without block isn't supported.
250 *
251 */
252static VALUE
253wmap_each(VALUE self)
254{
255 struct weakmap *w;
256 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
257
258 st_foreach(w->table, wmap_each_i, (st_data_t)0);
259
260 return self;
261}
262
263static int
264wmap_each_key_i(st_data_t k, st_data_t _v, st_data_t _data)
265{
266 rb_yield((VALUE)k);
267
268 return ST_CONTINUE;
269}
270
271/*
272 * call-seq:
273 * map.each_key {|key| ... } -> self
274 *
275 * Iterates over keys. Note that unlike other collections,
276 * +each_key+ without block isn't supported.
277 *
278 */
279static VALUE
280wmap_each_key(VALUE self)
281{
282 struct weakmap *w;
283 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
284
285 st_foreach(w->table, wmap_each_key_i, (st_data_t)0);
286
287 return self;
288}
289
290static int
291wmap_each_value_i(st_data_t k, st_data_t v, st_data_t _data)
292{
293 rb_yield((VALUE)v);
294
295 return ST_CONTINUE;
296}
297
298/*
299 * call-seq:
300 * map.each_value {|val| ... } -> self
301 *
302 * Iterates over values. Note that unlike other collections,
303 * +each_value+ without block isn't supported.
304 *
305 */
306static VALUE
307wmap_each_value(VALUE self)
308{
309 struct weakmap *w;
310 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
311
312 st_foreach(w->table, wmap_each_value_i, (st_data_t)0);
313
314 return self;
315}
316
317static int
318wmap_keys_i(st_data_t k, st_data_t v, st_data_t data)
319{
320 VALUE ary = (VALUE)data;
321
322 rb_ary_push(ary, (VALUE)k);
323
324 return ST_CONTINUE;
325}
326
327/*
328 * call-seq:
329 * map.keys -> new_array
330 *
331 * Returns a new Array containing all keys in the map.
332 *
333 */
334static VALUE
335wmap_keys(VALUE self)
336{
337 struct weakmap *w;
338 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
339
340 VALUE ary = rb_ary_new();
341 st_foreach(w->table, wmap_keys_i, (st_data_t)ary);
342
343 return ary;
344}
345
346static int
347wmap_values_i(st_data_t k, st_data_t v, st_data_t data)
348{
349 VALUE ary = (VALUE)data;
350
351 rb_ary_push(ary, (VALUE)v);
352
353 return ST_CONTINUE;
354}
355
356/*
357 * call-seq:
358 * map.values -> new_array
359 *
360 * Returns a new Array containing all values in the map.
361 *
362 */
363static VALUE
364wmap_values(VALUE self)
365{
366 struct weakmap *w;
367 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
368
369 VALUE ary = rb_ary_new();
370 st_foreach(w->table, wmap_values_i, (st_data_t)ary);
371
372 return ary;
373}
374
375/*
376 * call-seq:
377 * map[key] = value -> value
378 *
379 * Associates the given +value+ with the given +key+.
380 *
381 * If the given +key+ exists, replaces its value with the given +value+;
382 * the ordering is not affected.
383 */
384static VALUE
385wmap_aset(VALUE self, VALUE key, VALUE val)
386{
387 struct weakmap *w;
388 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
389
390 st_insert(w->table, (st_data_t)key, (st_data_t)val);
391
392 RB_OBJ_WRITTEN(self, Qundef, key);
393 RB_OBJ_WRITTEN(self, Qundef, val);
394
395 return val;
396}
397
398/* Retrieves a weakly referenced object with the given key */
399static VALUE
400wmap_lookup(VALUE self, VALUE key)
401{
402 struct weakmap *w;
403 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
404
405 st_data_t data;
406 if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef;
407
408 return (VALUE)data;
409}
410
411/*
412 * call-seq:
413 * map[key] -> value
414 *
415 * Returns the value associated with the given +key+ if found.
416 *
417 * If +key+ is not found, returns +nil+.
418 */
419static VALUE
420wmap_aref(VALUE self, VALUE key)
421{
422 VALUE obj = wmap_lookup(self, key);
423 return !UNDEF_P(obj) ? obj : Qnil;
424}
425
426/*
427 * call-seq:
428 * map.delete(key) -> value or nil
429 * map.delete(key) {|key| ... } -> object
430 *
431 * Deletes the entry for the given +key+ and returns its associated value.
432 *
433 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
434 * m = ObjectSpace::WeakMap.new
435 * key = "foo"
436 * m[key] = 1
437 * m.delete(key) # => 1
438 * m[key] # => nil
439 *
440 * If no block is given and +key+ is not found, returns +nil+.
441 *
442 * If a block is given and +key+ is found, ignores the block,
443 * deletes the entry, and returns the associated value:
444 * m = ObjectSpace::WeakMap.new
445 * key = "foo"
446 * m[key] = 2
447 * m.delete(key) { |key| raise 'Will never happen'} # => 2
448 *
449 * If a block is given and +key+ is not found,
450 * yields the +key+ to the block and returns the block's return value:
451 * m = ObjectSpace::WeakMap.new
452 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
453 */
454static VALUE
455wmap_delete(VALUE self, VALUE key)
456{
457 struct weakmap *w;
458 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
459
460 st_data_t orig_key = (st_data_t)key;
461 st_data_t orig_val;
462 if (st_delete(w->table, &orig_key, &orig_val)) {
463 return (VALUE)orig_val;
464 }
465
466 if (rb_block_given_p()) {
467 return rb_yield(key);
468 }
469 else {
470 return Qnil;
471 }
472}
473
474/*
475 * call-seq:
476 * map.key?(key) -> true or false
477 *
478 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
479 */
480static VALUE
481wmap_has_key(VALUE self, VALUE key)
482{
483 return RBOOL(!UNDEF_P(wmap_lookup(self, key)));
484}
485
486/*
487 * call-seq:
488 * map.size -> number
489 *
490 * Returns the number of referenced objects
491 */
492static VALUE
493wmap_size(VALUE self)
494{
495 struct weakmap *w;
496 TypedData_Get_Struct(self, struct weakmap, &rb_weakmap_type, w);
497
498 st_index_t n = st_table_size(w->table);
499
500#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
501 return ULONG2NUM(n);
502#else
503 return ULL2NUM(n);
504#endif
505}
506
507/* ===== WeakKeyMap =====
508 *
509 * WeakKeyMap contains one ST table which contains a pointer to the object as
510 * the key and the object as the value. This means that the key is of the type
511 * `VALUE *` while the value is of the type `VALUE`.
512 *
513 * The object is not directly stored as keys in the table because
514 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
515 * when the object is reclaimed. Using a pointer into the ST table entry is not
516 * safe because the pointer can change when the ST table is resized.
517 *
518 * WeakKeyMap hashes and compares using the `#hash` and `#==` methods of the
519 * object, respectively.
520 *
521 * During GC and while iterating, reclaimed entries (i.e. the key points to
522 * `Qundef`) are removed from the ST table.
523 */
524
526 st_table *table;
527};
528
529static int
530wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t _data)
531{
532 rb_gc_mark_movable((VALUE)val_obj);
533
534 return ST_CONTINUE;
535}
536
537static void
538wkmap_mark(void *ptr)
539{
540 struct weakkeymap *w = ptr;
541 if (w->table) {
542 st_foreach(w->table, wkmap_mark_table_i, (st_data_t)0);
543 }
544}
545
546static void
547wkmap_free(void *ptr)
548{
549 struct weakkeymap *w = ptr;
550
551 st_free_table(w->table);
552}
553
554static size_t
555wkmap_memsize(const void *ptr)
556{
557 const struct weakkeymap *w = ptr;
558
559 size_t size = 0;
560 if (w->table) {
561 size += st_memsize(w->table);
562 }
563
564 return size;
565}
566
567static int
568wkmap_compact_table_i(st_data_t key, st_data_t val, st_data_t _data, int _error)
569{
570 if ((VALUE)key != rb_gc_location((VALUE)key) || (VALUE)val != rb_gc_location((VALUE)val)) {
571 return ST_REPLACE;
572 }
573
574 return ST_CONTINUE;
575}
576
577static int
578wkmap_compact_table_replace(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t _data, int existing)
579{
580 RUBY_ASSERT(existing);
581
582 *key_ptr = (st_data_t)rb_gc_location((VALUE)*key_ptr);
583 *val_ptr = (st_data_t)rb_gc_location((VALUE)*val_ptr);
584
585 return ST_CONTINUE;
586}
587
588static void
589wkmap_compact(void *ptr)
590{
591 struct weakkeymap *w = ptr;
592
593 if (w->table) {
594 st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)0);
595 }
596}
597
598static int
599rb_wkmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg)
600{
601 if (rb_gc_handle_weak_references_alive_p(key)) {
602 return ST_CONTINUE;
603 }
604 else {
605 return ST_DELETE;
606 }
607}
608
609static void
610wkmap_handle_weak_references(void *ptr)
611{
612 struct weakkeymap *w = ptr;
613
614 st_foreach(w->table, rb_wkmap_handle_weak_references_i, (st_data_t)0);
615}
616
617static const rb_data_type_t rb_weakkeymap_type = {
618 "weakkeymap",
619 {
620 wkmap_mark,
621 wkmap_free,
622 wkmap_memsize,
623 wkmap_compact,
624 wkmap_handle_weak_references,
625 },
626 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
627};
628
629static int
630wkmap_cmp(st_data_t x, st_data_t y)
631{
632 VALUE x_obj = (VALUE)x;
633 VALUE y_obj = (VALUE)y;
634
635 return rb_any_cmp(x_obj, y_obj);
636}
637
638static st_index_t
639wkmap_hash(st_data_t n)
640{
641 VALUE obj = (VALUE)n;
642
643 return rb_any_hash(obj);
644}
645
646static const struct st_hash_type wkmap_hash_type = {
647 wkmap_cmp,
648 wkmap_hash,
649};
650
651static VALUE
652wkmap_allocate(VALUE klass)
653{
654 struct weakkeymap *w;
655
656 VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &rb_weakkeymap_type, w);
657
658 w->table = st_init_table(&wkmap_hash_type);
659
660 rb_gc_declare_weak_references(obj);
661
662 return obj;
663}
664
665static VALUE
666wkmap_lookup(VALUE self, VALUE key)
667{
668 struct weakkeymap *w;
669 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
670
671 st_data_t data;
672 if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef;
673
674 return (VALUE)data;
675}
676
677/*
678 * call-seq:
679 * map[key] -> value
680 *
681 * Returns the value associated with the given +key+ if found.
682 *
683 * If +key+ is not found, returns +nil+.
684 */
685static VALUE
686wkmap_aref(VALUE self, VALUE key)
687{
688 VALUE obj = wkmap_lookup(self, key);
689 return !UNDEF_P(obj) ? obj : Qnil;
690}
691
693 VALUE new_key;
694 VALUE new_val;
695};
696
697/*
698 * call-seq:
699 * map[key] = value -> value
700 *
701 * Associates the given +value+ with the given +key+
702 *
703 * The reference to +key+ is weak, so when there is no other reference
704 * to +key+ it may be garbage collected.
705 *
706 * If the given +key+ exists, replaces its value with the given +value+;
707 * the ordering is not affected
708 */
709static VALUE
710wkmap_aset(VALUE self, VALUE key, VALUE val)
711{
712 struct weakkeymap *w;
713 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
714
715 if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) {
716 rb_raise(rb_eArgError, "WeakKeyMap keys must be garbage collectable");
718 }
719
720 st_insert(w->table, (st_data_t)key, (st_data_t)val);
721
722 RB_OBJ_WRITTEN(self, Qundef, key);
723 RB_OBJ_WRITTEN(self, Qundef, val);
724
725 return val;
726}
727
728/*
729 * call-seq:
730 * map.delete(key) -> value or nil
731 * map.delete(key) {|key| ... } -> object
732 *
733 * Deletes the entry for the given +key+ and returns its associated value.
734 *
735 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
736 * m = ObjectSpace::WeakKeyMap.new
737 * key = "foo" # to hold reference to the key
738 * m[key] = 1
739 * m.delete("foo") # => 1
740 * m["foo"] # => nil
741 *
742 * If no block given and +key+ is not found, returns +nil+.
743 *
744 * If a block is given and +key+ is found, ignores the block,
745 * deletes the entry, and returns the associated value:
746 * m = ObjectSpace::WeakKeyMap.new
747 * key = "foo" # to hold reference to the key
748 * m[key] = 2
749 * m.delete("foo") { |key| raise 'Will never happen'} # => 2
750 *
751 * If a block is given and +key+ is not found,
752 * yields the +key+ to the block and returns the block's return value:
753 * m = ObjectSpace::WeakKeyMap.new
754 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
755 */
756
757static VALUE
758wkmap_delete(VALUE self, VALUE key)
759{
760 struct weakkeymap *w;
761 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
762
763 st_data_t orig_key = (st_data_t)key;
764 st_data_t orig_val;
765 if (st_delete(w->table, &orig_key, &orig_val)) {
766 return (VALUE)orig_val;
767 }
768
769 if (rb_block_given_p()) {
770 return rb_yield(key);
771 }
772 else {
773 return Qnil;
774 }
775}
776
777/*
778 * call-seq:
779 * map.getkey(key) -> existing_key or nil
780 *
781 * Returns the existing equal key if it exists, otherwise returns +nil+.
782 *
783 * This might be useful for implementing caches, so that only one copy of
784 * some object would be used everywhere in the program:
785 *
786 * value = {amount: 1, currency: 'USD'}
787 *
788 * # Now if we put this object in a cache:
789 * cache = ObjectSpace::WeakKeyMap.new
790 * cache[value] = true
791 *
792 * # ...we can always extract from there and use the same object:
793 * copy = cache.getkey({amount: 1, currency: 'USD'})
794 * copy.object_id == value.object_id #=> true
795 */
796static VALUE
797wkmap_getkey(VALUE self, VALUE key)
798{
799 struct weakkeymap *w;
800 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
801
802 st_data_t orig_key;
803 if (!st_get_key(w->table, (st_data_t)key, &orig_key)) return Qnil;
804
805 return (VALUE)orig_key;
806}
807
808/*
809 * call-seq:
810 * map.key?(key) -> true or false
811 *
812 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
813 */
814static VALUE
815wkmap_has_key(VALUE self, VALUE key)
816{
817 return RBOOL(!UNDEF_P(wkmap_lookup(self, key)));
818}
819
820/*
821 * call-seq:
822 * map.clear -> self
823 *
824 * Removes all map entries; returns +self+.
825 */
826static VALUE
827wkmap_clear(VALUE self)
828{
829 struct weakkeymap *w;
830 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
831
832 st_clear(w->table);
833
834 return self;
835}
836
837/*
838 * call-seq:
839 * map.inspect -> new_string
840 *
841 * Returns a new String containing information about the map:
842 *
843 * m = ObjectSpace::WeakKeyMap.new
844 * m[key] = value
845 * m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
846 *
847 */
848static VALUE
849wkmap_inspect(VALUE self)
850{
851 struct weakkeymap *w;
852 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
853
854 st_index_t n = st_table_size(w->table);
855
856#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
857 const char * format = "#<%"PRIsVALUE":%p size=%lu>";
858#else
859 const char * format = "#<%"PRIsVALUE":%p size=%llu>";
860#endif
861
862 VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n);
863 return str;
864}
865
866/*
867 * Document-class: ObjectSpace::WeakMap
868 *
869 * An ObjectSpace::WeakMap is a key-value map that holds weak references
870 * to its keys and values, so they can be garbage-collected when there are
871 * no more references left.
872 *
873 * Keys in the map are compared by identity.
874 *
875 * m = ObjectSpace::WeakMap.new
876 * key1 = "foo"
877 * val1 = Object.new
878 * m[key1] = val1
879 *
880 * key2 = "bar"
881 * val2 = Object.new
882 * m[key2] = val2
883 *
884 * m[key1] #=> #<Object:0x0...>
885 * m[key2] #=> #<Object:0x0...>
886 *
887 * val1 = nil # remove the other reference to value
888 * GC.start
889 *
890 * m[key1] #=> nil
891 * m.keys #=> ["bar"]
892 *
893 * key2 = nil # remove the other reference to key
894 * GC.start
895 *
896 * m[key2] #=> nil
897 * m.keys #=> []
898 *
899 * (Note that GC.start is used here only for demonstrational purposes and might
900 * not always lead to demonstrated results.)
901 *
902 *
903 * See also ObjectSpace::WeakKeyMap map class, which compares keys by value,
904 * and holds weak references only to the keys.
905 */
906
907/*
908 * Document-class: ObjectSpace::WeakKeyMap
909 *
910 * An ObjectSpace::WeakKeyMap is a key-value map that holds weak references
911 * to its keys, so they can be garbage collected when there is no more references.
912 *
913 * Unlike ObjectSpace::WeakMap:
914 *
915 * * references to values are _strong_, so they aren't garbage collected while
916 * they are in the map;
917 * * keys are compared by value (using Object#eql?), not by identity;
918 * * only garbage-collectable objects can be used as keys.
919 *
920 * map = ObjectSpace::WeakKeyMap.new
921 * val = Time.new(2023, 12, 7)
922 * key = "name"
923 * map[key] = val
924 *
925 * # Value is fetched by equality: the instance of string "name" is
926 * # different here, but it is equal to the key
927 * map["name"] #=> 2023-12-07 00:00:00 +0200
928 *
929 * val = nil
930 * GC.start
931 * # There are no more references to `val`, yet the pair isn't
932 * # garbage-collected.
933 * map["name"] #=> 2023-12-07 00:00:00 +0200
934 *
935 * key = nil
936 * GC.start
937 * # There are no more references to `key`, key and value are
938 * # garbage-collected.
939 * map["name"] #=> nil
940 *
941 * (Note that GC.start is used here only for demonstrational purposes and might
942 * not always lead to demonstrated results.)
943 *
944 * The collection is especially useful for implementing caches of lightweight value
945 * objects, so that only one copy of each value representation would be stored in
946 * memory, but the copies that aren't used would be garbage-collected.
947 *
948 * CACHE = ObjectSpace::WeakKeyMap
949 *
950 * def make_value(**)
951 * val = ValueObject.new(**)
952 * if (existing = @cache.getkey(val))
953 * # if the object with this value exists, we return it
954 * existing
955 * else
956 * # otherwise, put it in the cache
957 * @cache[val] = true
958 * val
959 * end
960 * end
961 *
962 * This will result in +make_value+ returning the same object for same set of attributes
963 * always, but the values that aren't needed anymore wouldn't be sitting in the cache forever.
964 */
965
966void
967Init_WeakMap(void)
968{
969 VALUE rb_mObjectSpace = rb_define_module("ObjectSpace");
970
971 VALUE rb_cWeakMap = rb_define_class_under(rb_mObjectSpace, "WeakMap", rb_cObject);
972 rb_define_alloc_func(rb_cWeakMap, wmap_allocate);
973 rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2);
974 rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1);
975 rb_define_method(rb_cWeakMap, "delete", wmap_delete, 1);
976 rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1);
977 rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1);
978 rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1);
979 rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0);
980 rb_define_method(rb_cWeakMap, "each", wmap_each, 0);
981 rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0);
982 rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0);
983 rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0);
984 rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0);
985 rb_define_method(rb_cWeakMap, "values", wmap_values, 0);
986 rb_define_method(rb_cWeakMap, "size", wmap_size, 0);
987 rb_define_method(rb_cWeakMap, "length", wmap_size, 0);
988 rb_include_module(rb_cWeakMap, rb_mEnumerable);
989
990 VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjectSpace, "WeakKeyMap", rb_cObject);
991 rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate);
992 rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2);
993 rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1);
994 rb_define_method(rb_cWeakKeyMap, "delete", wkmap_delete, 1);
995 rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1);
996 rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1);
997 rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0);
998 rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0);
999}
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
Definition assert.h:219
#define rb_define_method(klass, mid, func, arity)
Defines klass#mid.
void rb_include_module(VALUE klass, VALUE module)
Includes a module to a class.
Definition class.c:1609
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1033
#define Qundef
Old name of RUBY_Qundef.
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h:1684
#define T_FLOAT
Old name of RUBY_T_FLOAT.
Definition value_type.h:64
#define SPECIAL_CONST_P
Old name of RB_SPECIAL_CONST_P.
#define ULONG2NUM
Old name of RB_ULONG2NUM.
Definition long.h:60
#define UNREACHABLE_RETURN
Old name of RBIMPL_UNREACHABLE_RETURN.
Definition assume.h:29
#define CLASS_OF
Old name of rb_class_of.
Definition globals.h:205
#define FL_ABLE
Old name of RB_FL_ABLE.
Definition fl_type.h:118
#define ULL2NUM
Old name of RB_ULL2NUM.
Definition long_long.h:31
#define Qnil
Old name of RUBY_Qnil.
#define SYMBOL_P
Old name of RB_SYMBOL_P.
Definition value_type.h:88
VALUE rb_cObject
Object class.
Definition object.c:60
VALUE rb_any_to_s(VALUE obj)
Generates a textual representation of the given object.
Definition object.c:657
VALUE rb_mEnumerable
Enumerable module.
Definition enum.c:27
VALUE rb_inspect(VALUE obj)
Generates a human-readable textual representation of the given object.
Definition object.c:668
#define RB_OBJ_WRITTEN(old, oldv, young)
Identical to RB_OBJ_WRITE(), except it doesn't write any values, but only a WB declaration.
Definition gc.h:468
VALUE rb_ary_new(void)
Allocates a new, empty array.
VALUE rb_ary_push(VALUE ary, VALUE elem)
Special case of rb_ary_cat() that it adds only one element.
VALUE rb_str_append(VALUE dst, VALUE src)
Identical to rb_str_buf_append(), except it converts the right hand side before concatenating.
Definition string.c:3898
VALUE rb_class_name(VALUE obj)
Queries the name of the given object's class.
Definition variable.c:515
void rb_define_alloc_func(VALUE klass, rb_alloc_func_t func)
Sets the allocator function of a class.
VALUE rb_yield_values(int n,...)
Identical to rb_yield(), except it takes variadic number of parameters and pass them to the block.
Definition vm_eval.c:1401
VALUE rb_yield(VALUE val)
Yields the block.
Definition vm_eval.c:1378
#define TypedData_Get_Struct(obj, type, data_type, sval)
Obtains a C struct from inside of a wrapper Ruby object.
Definition rtypeddata.h:773
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
Definition rtypeddata.h:604
#define _(args)
This was a transition path from K&R to ANSI.
Definition stdarg.h:35
This is the struct that holds necessary info for a struct.
Definition rtypeddata.h:238
Definition st.h:79
Definition weakmap.c:32
uintptr_t VALUE
Type that represents a Ruby object.
Definition value.h:40
static bool RB_TYPE_P(VALUE obj, enum ruby_value_type t)
Queries if the given object is of given type.
Definition value_type.h:376