Class: ObjectSpace::WeakKeyMap

Inherits:
Object
  • Object
show all
Defined in:
weakmap.c,
weakmap.c

Overview

An ObjectSpace::WeakKeyMap is a key-value map that holds weak references to its keys, so they can be garbage collected when there is no more references.

Unlike ObjectSpace::WeakMap:

  • references to values are strong, so they aren’t garbage collected while they are in the map;

  • keys are compared by value (using Object#eql?), not by identity;

  • only garbage-collectable objects can be used as keys.

    map = ObjectSpace::WeakKeyMap.new
    val = Time.new(2023, 12, 7)
    key = "name"
    map[key] = val
    
    # Value is fetched by equality: the instance of string "name" is
    # different here, but it is equal to the key
    map["name"] #=> 2023-12-07 00:00:00 +0200
    
    val = nil
    GC.start
    # There are no more references to `val`, yet the pair isn't
    # garbage-collected.
    map["name"] #=> 2023-12-07 00:00:00 +0200
    
    key = nil
    GC.start
    # There are no more references to `key`, key and value are
    # garbage-collected.
    map["name"] #=> nil
    

(Note that GC.start is used here only for demonstrational purposes and might not always lead to demonstrated results.)

The collection is especially useful for implementing caches of lightweight value objects, so that only one copy of each value representation would be stored in memory, but the copies that aren’t used would be garbage-collected.

CACHE = ObjectSpace::WeakKeyMap

def make_value(**)
   val = ValueObject.new(**)
   if (existing = @cache.getkey(val))
      # if the object with this value exists, we return it
      existing
   else
      # otherwise, put it in the cache
      @cache[val] = true
      val
   end
end

This will result in make_value returning the same object for same set of attributes always, but the values that aren’t needed anymore wouldn’t be sitting in the cache forever.

Instance Method Summary collapse

Instance Method Details

#[](key) ⇒ Object

Returns the value associated with the given key if found.

If key is not found, returns nil.



816
817
818
819
820
821
# File 'weakmap.c', line 816

static VALUE
wkmap_aref(VALUE self, VALUE key)
{
    VALUE obj = wkmap_lookup(self, key);
    return !UNDEF_P(obj) ? obj : Qnil;
}

#[]=(key) ⇒ Object

Associates the given value with the given key

The reference to key is weak, so when there is no other reference to key it may be garbage collected.

If the given key exists, replaces its value with the given value; the ordering is not affected



855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
# File 'weakmap.c', line 855

static VALUE
wkmap_aset(VALUE self, VALUE key, VALUE val)
{
    struct weakkeymap *w;
    TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);

    if (!FL_ABLE(key) || SYMBOL_P(key) || RB_BIGNUM_TYPE_P(key) || RB_TYPE_P(key, T_FLOAT)) {
        rb_raise(rb_eArgError, "WeakKeyMap must be garbage collectable");
        UNREACHABLE_RETURN(Qnil);
    }

    struct wkmap_aset_args args = {
        .new_key = key,
        .new_val = val,
    };

    st_update(w->table, (st_data_t)&key, wkmap_aset_replace, (st_data_t)&args);

    RB_OBJ_WRITTEN(self, Qundef, key);
    RB_OBJ_WRITTEN(self, Qundef, val);

    return val;
}

#clearself

Removes all map entries; returns self.

Returns:

  • (self)


995
996
997
998
999
1000
1001
1002
1003
1004
1005
# File 'weakmap.c', line 995

static VALUE
wkmap_clear(VALUE self)
{
    struct weakkeymap *w;
    TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);

    st_foreach(w->table, wkmap_clear_i, (st_data_t)self);
    st_clear(w->table);

    return self;
}

#delete(key) ⇒ nil #delete(key) {|key| ... } ⇒ Object

Deletes the entry for the given key and returns its associated value.

If no block is given and key is found, deletes the entry and returns the associated value:

m = ObjectSpace::WeakKeyMap.new
key = "foo" # to hold reference to the key
m[key] = 1
m.delete("foo") # => 1
m["foo"] # => nil

If no block given and key is not found, returns nil.

If a block is given and key is found, ignores the block, deletes the entry, and returns the associated value:

m = ObjectSpace::WeakKeyMap.new
key = "foo" # to hold reference to the key
m[key] = 2
m.delete("foo") { |key| raise 'Will never happen'} # => 2

If a block is given and key is not found, yields the key to the block and returns the block’s return value:

m = ObjectSpace::WeakKeyMap.new
m.delete("nosuch") { |key| "Key #{key} not found" } # => "Key nosuch not found"

Overloads:

  • #delete(key) ⇒ nil

    Returns:

    • (nil)
  • #delete(key) {|key| ... } ⇒ Object

    Yields:

    • (key)

    Returns:



908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
# File 'weakmap.c', line 908

static VALUE
wkmap_delete(VALUE self, VALUE key)
{
    struct weakkeymap *w;
    TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);

    VALUE orig_key = key;
    st_data_t orig_key_data = (st_data_t)&orig_key;
    st_data_t orig_val_data;
    if (st_delete(w->table, &orig_key_data, &orig_val_data)) {
        VALUE orig_val = (VALUE)orig_val_data;

        rb_gc_remove_weak(self, (VALUE *)orig_key_data);

        ruby_sized_xfree((VALUE *)orig_key_data, sizeof(VALUE));

        return orig_val;
    }

    if (rb_block_given_p()) {
        return rb_yield(key);
    }
    else {
        return Qnil;
    }
}

#getkey(key) ⇒ nil

Returns the existing equal key if it exists, otherwise returns nil.

This might be useful for implementing caches, so that only one copy of some object would be used everywhere in the program:

value = {amount: 1, currency: 'USD'}

# Now if we put this object in a cache:
cache = ObjectSpace::WeakKeyMap.new
cache[value] = true

# ...we can always extract from there and use the same object:
copy = cache.getkey({amount: 1, currency: 'USD'})
copy.object_id == value.object_id #=> true

Returns:

  • (nil)


954
955
956
957
958
959
960
961
962
963
964
# File 'weakmap.c', line 954

static VALUE
wkmap_getkey(VALUE self, VALUE key)
{
    struct weakkeymap *w;
    TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);

    st_data_t orig_key;
    if (!st_get_key(w->table, (st_data_t)&key, &orig_key)) return Qnil;

    return *(VALUE *)orig_key;
}

#inspectObject

Returns a new String containing informations about the map:

m = ObjectSpace::WeakKeyMap.new
m[key] = value
m.inspect # => "#<ObjectSpace::WeakKeyMap:0x00000001028dcba8 size=1>"


1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
# File 'weakmap.c', line 1018

static VALUE
wkmap_inspect(VALUE self)
{
    struct weakkeymap *w;
    TypedData_Get_Struct(self, struct weakkeymap, &weakkeymap_type, w);

    st_index_t n = st_table_size(w->table);

#if SIZEOF_ST_INDEX_T <= SIZEOF_LONG
    const char * format = "#<%"PRIsVALUE":%p size=%lu>";
#else
    const char * format = "#<%"PRIsVALUE":%p size=%llu>";
#endif

    VALUE str = rb_sprintf(format, rb_class_name(CLASS_OF(self)), (void *)self, n);
    return str;
}

#key?(key) ⇒ Boolean

Returns true if key is a key in self, otherwise false.

Returns:

  • (Boolean)


972
973
974
975
976
# File 'weakmap.c', line 972

static VALUE
wkmap_has_key(VALUE self, VALUE key)
{
    return RBOOL(!UNDEF_P(wkmap_lookup(self, key)));
}