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