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