Ruby 4.1.0dev (2026-10-10 revision 05917fd1e6d8539db013b087d4ea93cf068038e1)
weakmap.c (05917fd1e6d8539db013b087d4ea93cf068038e1)
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 rb_gc_update_moved((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
507VALUE
508rb_wmap_new_hidden(void)
509{
510 return wmap_allocate(0);
511}
512
513VALUE
514rb_wmap_lookup(VALUE self, VALUE key)
515{
516 return wmap_lookup(self, key);
517}
518
519void
520rb_wmap_aset(VALUE self, VALUE key, VALUE val)
521{
522 wmap_aset(self, key, val);
523}
524
525/* ===== WeakKeyMap =====
526 *
527 * WeakKeyMap contains one ST table which contains a pointer to the object as
528 * the key and the object as the value. This means that the key is of the type
529 * `VALUE *` while the value is of the type `VALUE`.
530 *
531 * The object is not directly stored as keys in the table because
532 * `rb_gc_mark_weak` requires a pointer to the memory location to overwrite
533 * when the object is reclaimed. Using a pointer into the ST table entry is not
534 * safe because the pointer can change when the ST table is resized.
535 *
536 * WeakKeyMap hashes and compares using the `#hash` and `#==` methods of the
537 * object, respectively.
538 *
539 * During GC and while iterating, reclaimed entries (i.e. the key points to
540 * `Qundef`) are removed from the ST table.
541 */
542
544 st_table *table;
545};
546
547static int
548wkmap_mark_table_i(st_data_t key, st_data_t val_obj, st_data_t _data)
549{
550 rb_gc_mark_movable((VALUE)val_obj);
551
552 return ST_CONTINUE;
553}
554
555static void
556wkmap_mark(void *ptr)
557{
558 struct weakkeymap *w = ptr;
559 if (w->table) {
560 st_foreach(w->table, wkmap_mark_table_i, (st_data_t)0);
561 }
562}
563
564static void
565wkmap_free(void *ptr)
566{
567 struct weakkeymap *w = ptr;
568
569 st_free_table(w->table);
570}
571
572static size_t
573wkmap_memsize(const void *ptr)
574{
575 const struct weakkeymap *w = ptr;
576
577 size_t size = 0;
578 if (w->table) {
579 size += st_memsize(w->table);
580 }
581
582 return size;
583}
584
585static int
586wkmap_compact_table_i(st_data_t key, st_data_t val, st_data_t _data, int _error)
587{
588 if ((VALUE)key != rb_gc_location((VALUE)key) || (VALUE)val != rb_gc_location((VALUE)val)) {
589 return ST_REPLACE;
590 }
591
592 return ST_CONTINUE;
593}
594
595static int
596wkmap_compact_table_replace(st_data_t *key_ptr, st_data_t *val_ptr, st_data_t _data, int existing)
597{
598 RUBY_ASSERT(existing);
599
600 rb_gc_update_moved((VALUE *)key_ptr);
601 rb_gc_update_moved((VALUE *)val_ptr);
602
603 return ST_CONTINUE;
604}
605
606static void
607wkmap_compact(void *ptr)
608{
609 struct weakkeymap *w = ptr;
610
611 if (w->table) {
612 st_foreach_with_replace(w->table, wkmap_compact_table_i, wkmap_compact_table_replace, (st_data_t)0);
613 }
614}
615
616static int
617rb_wkmap_handle_weak_references_i(st_data_t key, st_data_t val, st_data_t arg)
618{
619 if (rb_gc_handle_weak_references_alive_p(key)) {
620 return ST_CONTINUE;
621 }
622 else {
623 return ST_DELETE;
624 }
625}
626
627static void
628wkmap_handle_weak_references(void *ptr)
629{
630 struct weakkeymap *w = ptr;
631
632 st_foreach(w->table, rb_wkmap_handle_weak_references_i, (st_data_t)0);
633}
634
635static const rb_data_type_t rb_weakkeymap_type = {
636 "weakkeymap",
637 {
638 wkmap_mark,
639 wkmap_free,
640 wkmap_memsize,
641 wkmap_compact,
642 wkmap_handle_weak_references,
643 },
644 0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
645};
646
647static int
648wkmap_cmp(st_data_t x, st_data_t y)
649{
650 VALUE x_obj = (VALUE)x;
651 VALUE y_obj = (VALUE)y;
652
653 return rb_any_cmp(x_obj, y_obj);
654}
655
656static st_index_t
657wkmap_hash(st_data_t n)
658{
659 VALUE obj = (VALUE)n;
660
661 return rb_any_hash(obj);
662}
663
664static const struct st_hash_type wkmap_hash_type = {
665 wkmap_cmp,
666 wkmap_hash,
667};
668
669static VALUE
670wkmap_allocate(VALUE klass)
671{
672 struct weakkeymap *w;
673
674 VALUE obj = TypedData_Make_Struct(klass, struct weakkeymap, &rb_weakkeymap_type, w);
675
676 w->table = st_init_table(&wkmap_hash_type);
677
678 rb_gc_declare_weak_references(obj);
679
680 return obj;
681}
682
683static VALUE
684wkmap_lookup(VALUE self, VALUE key)
685{
686 struct weakkeymap *w;
687 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
688
689 st_data_t data;
690 if (!st_lookup(w->table, (st_data_t)key, &data)) return Qundef;
691
692 return (VALUE)data;
693}
694
695/*
696 * call-seq:
697 * map[key] -> value
698 *
699 * Returns the value associated with the given +key+ if found.
700 *
701 * If +key+ is not found, returns +nil+.
702 */
703static VALUE
704wkmap_aref(VALUE self, VALUE key)
705{
706 VALUE obj = wkmap_lookup(self, key);
707 return !UNDEF_P(obj) ? obj : Qnil;
708}
709
711 VALUE new_key;
712 VALUE new_val;
713};
714
715/*
716 * call-seq:
717 * map[key] = value -> value
718 *
719 * Associates the given +value+ with the given +key+
720 *
721 * The reference to +key+ is weak, so when there is no other reference
722 * to +key+ it may be garbage collected.
723 *
724 * If the given +key+ exists, replaces its value with the given +value+;
725 * the ordering is not affected
726 */
727static VALUE
728wkmap_aset(VALUE self, VALUE key, VALUE val)
729{
730 struct weakkeymap *w;
731 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
732
733 if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) {
734 rb_raise(rb_eArgError, "WeakKeyMap keys must be garbage collectable");
736 }
737
738 st_insert(w->table, (st_data_t)key, (st_data_t)val);
739
740 RB_OBJ_WRITTEN(self, Qundef, key);
741 RB_OBJ_WRITTEN(self, Qundef, val);
742
743 return val;
744}
745
746/*
747 * call-seq:
748 * map.delete(key) -> value or nil
749 * map.delete(key) {|key| ... } -> object
750 *
751 * Deletes the entry for the given +key+ and returns its associated value.
752 *
753 * If no block is given and +key+ is found, deletes the entry and returns the associated value:
754 * m = ObjectSpace::WeakKeyMap.new
755 * key = "foo" # to hold reference to the key
756 * m[key] = 1
757 * m.delete("foo") # => 1
758 * m["foo"] # => nil
759 *
760 * If no block given and +key+ is not found, returns +nil+.
761 *
762 * If a block is given and +key+ is found, ignores the block,
763 * deletes the entry, and returns the associated value:
764 * m = ObjectSpace::WeakKeyMap.new
765 * key = "foo" # to hold reference to the key
766 * m[key] = 2
767 * m.delete("foo") { |key| raise 'Will never happen'} # => 2
768 *
769 * If a block is given and +key+ is not found,
770 * yields the +key+ to the block and returns the block's return value:
771 * m = ObjectSpace::WeakKeyMap.new
772 * m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"
773 */
774
775static VALUE
776wkmap_delete(VALUE self, VALUE key)
777{
778 struct weakkeymap *w;
779 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
780
781 st_data_t orig_key = (st_data_t)key;
782 st_data_t orig_val;
783 if (st_delete(w->table, &orig_key, &orig_val)) {
784 return (VALUE)orig_val;
785 }
786
787 if (rb_block_given_p()) {
788 return rb_yield(key);
789 }
790 else {
791 return Qnil;
792 }
793}
794
795/*
796 * call-seq:
797 * map.getkey(key) -> existing_key or nil
798 *
799 * Returns the existing equal key if it exists, otherwise returns +nil+.
800 *
801 * This might be useful for implementing caches, so that only one copy of
802 * some object would be used everywhere in the program:
803 *
804 * value = {amount: 1, currency: 'USD'}
805 *
806 * # Now if we put this object in a cache:
807 * cache = ObjectSpace::WeakKeyMap.new
808 * cache[value] = true
809 *
810 * # ...we can always extract from there and use the same object:
811 * copy = cache.getkey({amount: 1, currency: 'USD'})
812 * copy.object_id == value.object_id #=> true
813 */
814static VALUE
815wkmap_getkey(VALUE self, VALUE key)
816{
817 struct weakkeymap *w;
818 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
819
820 st_data_t orig_key;
821 if (!st_get_key(w->table, (st_data_t)key, &orig_key)) return Qnil;
822
823 return (VALUE)orig_key;
824}
825
826/*
827 * call-seq:
828 * map.key?(key) -> true or false
829 *
830 * Returns +true+ if +key+ is a key in +self+, otherwise +false+.
831 */
832static VALUE
833wkmap_has_key(VALUE self, VALUE key)
834{
835 return RBOOL(!UNDEF_P(wkmap_lookup(self, key)));
836}
837
838/*
839 * call-seq:
840 * map.clear -> self
841 *
842 * Removes all map entries; returns +self+.
843 */
844static VALUE
845wkmap_clear(VALUE self)
846{
847 struct weakkeymap *w;
848 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
849
850 st_clear(w->table);
851
852 return self;
853}
854
855/*
856 * call-seq:
857 * map.inspect -> new_string
858 *
859 * Returns a new String containing information about the map:
860 *
861 * m = ObjectSpace::WeakKeyMap.new
862 * m[key] = value
863 * m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"
864 *
865 */
866static VALUE
867wkmap_inspect(VALUE self)
868{
869 struct weakkeymap *w;
870 TypedData_Get_Struct(self, struct weakkeymap, &rb_weakkeymap_type, w);
871
872 st_index_t n = st_table_size(w->table);
873
874#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
875 const char * format = "#<%"PRIsVALUE":%p size=%lu>";
876#else
877 const char * format = "#<%"PRIsVALUE":%p size=%llu>";
878#endif
879
880 VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n);
881 return str;
882}
883
884/*
885 * Document-class: ObjectSpace::WeakMap
886 *
887 * An ObjectSpace::WeakMap is a key-value map that holds weak references
888 * to its keys and values, so they can be garbage-collected when there are
889 * no more references left.
890 *
891 * Keys in the map are compared by identity.
892 *
893 * m = ObjectSpace::WeakMap.new
894 * key1 = "foo"
895 * val1 = Object.new
896 * m[key1] = val1
897 *
898 * key2 = "bar"
899 * val2 = Object.new
900 * m[key2] = val2
901 *
902 * m[key1] #=> #<Object:0x0...>
903 * m[key2] #=> #<Object:0x0...>
904 *
905 * val1 = nil # remove the other reference to value
906 * GC.start
907 *
908 * m[key1] #=> nil
909 * m.keys #=> ["bar"]
910 *
911 * key2 = nil # remove the other reference to key
912 * GC.start
913 *
914 * m[key2] #=> nil
915 * m.keys #=> []
916 *
917 * (Note that GC.start is used here only for demonstrational purposes and might
918 * not always lead to demonstrated results.)
919 *
920 *
921 * See also ObjectSpace::WeakKeyMap map class, which compares keys by value,
922 * and holds weak references only to the keys.
923 */
924
925/*
926 * Document-class: ObjectSpace::WeakKeyMap
927 *
928 * An ObjectSpace::WeakKeyMap is a key-value map that holds weak references
929 * to its keys, so they can be garbage collected when there is no more references.
930 *
931 * Unlike ObjectSpace::WeakMap:
932 *
933 * * references to values are _strong_, so they aren't garbage collected while
934 * they are in the map;
935 * * keys are compared by value (using Object#eql?), not by identity;
936 * * only garbage-collectable objects can be used as keys.
937 *
938 * map = ObjectSpace::WeakKeyMap.new
939 * val = Time.new(2023, 12, 7)
940 * key = "name"
941 * map[key] = val
942 *
943 * # Value is fetched by equality: the instance of string "name" is
944 * # different here, but it is equal to the key
945 * map["name"] #=> 2023-12-07 00:00:00 +0200
946 *
947 * val = nil
948 * GC.start
949 * # There are no more references to `val`, yet the pair isn't
950 * # garbage-collected.
951 * map["name"] #=> 2023-12-07 00:00:00 +0200
952 *
953 * key = nil
954 * GC.start
955 * # There are no more references to `key`, key and value are
956 * # garbage-collected.
957 * map["name"] #=> nil
958 *
959 * (Note that GC.start is used here only for demonstrational purposes and might
960 * not always lead to demonstrated results.)
961 *
962 * The collection is especially useful for implementing caches of lightweight value
963 * objects, so that only one copy of each value representation would be stored in
964 * memory, but the copies that aren't used would be garbage-collected.
965 *
966 * CACHE = ObjectSpace::WeakKeyMap
967 *
968 * def make_value(**)
969 * val = ValueObject.new(**)
970 * if (existing = @cache.getkey(val))
971 * # if the object with this value exists, we return it
972 * existing
973 * else
974 * # otherwise, put it in the cache
975 * @cache[val] = true
976 * val
977 * end
978 * end
979 *
980 * This will result in +make_value+ returning the same object for same set of attributes
981 * always, but the values that aren't needed anymore wouldn't be sitting in the cache forever.
982 */
983
984void
985Init_WeakMap(void)
986{
987 VALUE rb_mObjectSpace = rb_define_module("ObjectSpace");
988
989 VALUE rb_cWeakMap = rb_define_class_under(rb_mObjectSpace, "WeakMap", rb_cObject);
990 rb_define_alloc_func(rb_cWeakMap, wmap_allocate);
991 rb_define_method(rb_cWeakMap, "[]=", wmap_aset, 2);
992 rb_define_method(rb_cWeakMap, "[]", wmap_aref, 1);
993 rb_define_method(rb_cWeakMap, "delete", wmap_delete, 1);
994 rb_define_method(rb_cWeakMap, "include?", wmap_has_key, 1);
995 rb_define_method(rb_cWeakMap, "member?", wmap_has_key, 1);
996 rb_define_method(rb_cWeakMap, "key?", wmap_has_key, 1);
997 rb_define_method(rb_cWeakMap, "inspect", wmap_inspect, 0);
998 rb_define_method(rb_cWeakMap, "each", wmap_each, 0);
999 rb_define_method(rb_cWeakMap, "each_pair", wmap_each, 0);
1000 rb_define_method(rb_cWeakMap, "each_key", wmap_each_key, 0);
1001 rb_define_method(rb_cWeakMap, "each_value", wmap_each_value, 0);
1002 rb_define_method(rb_cWeakMap, "keys", wmap_keys, 0);
1003 rb_define_method(rb_cWeakMap, "values", wmap_values, 0);
1004 rb_define_method(rb_cWeakMap, "size", wmap_size, 0);
1005 rb_define_method(rb_cWeakMap, "length", wmap_size, 0);
1006 rb_include_module(rb_cWeakMap, rb_mEnumerable);
1007
1008 VALUE rb_cWeakKeyMap = rb_define_class_under(rb_mObjectSpace, "WeakKeyMap", rb_cObject);
1009 rb_define_alloc_func(rb_cWeakKeyMap, wkmap_allocate);
1010 rb_define_method(rb_cWeakKeyMap, "[]=", wkmap_aset, 2);
1011 rb_define_method(rb_cWeakKeyMap, "[]", wkmap_aref, 1);
1012 rb_define_method(rb_cWeakKeyMap, "delete", wkmap_delete, 1);
1013 rb_define_method(rb_cWeakKeyMap, "getkey", wkmap_getkey, 1);
1014 rb_define_method(rb_cWeakKeyMap, "key?", wkmap_has_key, 1);
1015 rb_define_method(rb_cWeakKeyMap, "clear", wkmap_clear, 0);
1016 rb_define_method(rb_cWeakKeyMap, "inspect", wkmap_inspect, 0);
1017}
#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:1771
int rb_block_given_p(void)
Determines if the current method is given a block.
Definition eval.c:1035
#define Qundef
Old name of RUBY_Qundef.
#define rb_str_cat2
Old name of rb_str_cat_cstr.
Definition string.h:1708
#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:658
VALUE rb_mEnumerable
Enumerable module.
Definition enum.c:28
VALUE rb_inspect(VALUE obj)
Generates a human-readable textual representation of the given object.
Definition object.c:669
#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:504
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:3913
VALUE rb_class_name(VALUE obj)
Queries the name of the given object's class.
Definition variable.c:518
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:242
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