class ObjectSpace::WeakKeyMap - RDoc Documentation

# class ObjectSpace::WeakKeyMap

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.

### Public Instance Methods

map[key] → value click to toggle source

Returns the value associated with the given `key` if found.

If `key` is not found, returns `nil`.

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

map[key] = value → value click to toggle source

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

    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;
    }

clear → self click to toggle source

Removes all map entries; returns `self`.

    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) → value or nil click to toggle source
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"

    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) → existing_key or nil click to toggle source

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

    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;
    }

inspect → new_string click to toggle source

Returns a new `String` containing informations about the map:

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

    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) → true or false click to toggle source

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

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