# How to implement TwoWayWeakMap? Using WeakRef?

**URL:** <https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890>\
**Category:** I have questions\
**Created:** [July 27, 2021, 1:40am UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890 "2021-07-27T01:40:19Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![trusktr](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/trusktr/32/183_2.png) [@trusktr](https://es.discourse.group/u/trusktr)\
**Post date:** [July 27, 2021, 1:40am UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/1 "2021-07-27T01:40:19Z")

</div>

The idea is that `TwoWayWeakMap` would work like `WeakMap`, except that you would be able to get key objects by their value. For example, here is how the usage would look like:

```javascript
class TwoWayWeakMap {
  // What goes here?
}

class SomeClass {}

const map = new TwoWayWeakMap<SomeClass, number>()

function main() {
  const o = new SomeClass

  map.set(o, 42)

  console.log(map.get(o)) // logs "42"
  console.log(map.keyFrom(42)) // logs "SomeClass {}" (the `o` object)
}

main()

```

Once `main()` completes, I would expect the `o` key to be collected at some point when it is no longer referenced.

Calling `keyFrom` with a value that was mapped from the object would return `undefined`, which would be similar to `WeakRef` returning `undefined` after an object has been collected.

I gave it a shot using `WeakRef`, but it doesn't seem that the object is ever collected. Maybe I missed something silly. If the following example were to work as I intended, then eventually it would log "o was collected!", but you'll see in your console that the object persists, even after forcing garbage collection in Chrome's devtools:

https://codepen.io/trusktr/embed/preview/rNmJaLY?default-tabs=html%2Cresult&height=300&host=https%3A%2F%2Fcodepen.io&slug-hash=rNmJaLY

---

<div class="post-metadata">

**Author:** ![lightmare](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/lightmare/32/843_2.png) [@lightmare](https://es.discourse.group/u/lightmare)\
**Post date:** [July 27, 2021, 8:01am UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/2 "2021-07-27T08:01:24Z")

</div>

`console.log` keeps the object alive. Strangely enough, even if you only print it once, then clear the console. Adjust these two lines so that `console.log` doesn't get hold of the object:

```javascript
console.log(`o still exists? (${tick++})`, !!obj);
...
console.log(!!map.keyFrom(42));

```

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [July 27, 2021, 3:03pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/4 "2021-07-27T15:03:32Z")

</div>

I think there's also a bug/typo in this part:

```javascript
for (const ref of this.#refs) {
    const o = ref.deref();
    if (!o) {
        this.#refs.delete(ref);
        return undefined; // <<< I don't think returning here is correct, the ref could have been for a different key/value pair
    }
    if (this.get(o) === v) return o;
}

```

Maybe this:

```javascript
for (const ref of this.#refs) {
    const o = ref.deref();
    if (o === undefined) {
        this.#refs.delete(ref);
    } else {
        if (this.get(o) === v) return o;
    }
}

```

---

<div class="post-metadata">

**Author:** ![trusktr](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/trusktr/32/183_2.png) [@trusktr](https://es.discourse.group/u/trusktr)\
**Post date:** [November 17, 2021, 5:46pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/5 "2021-11-17T17:46:47Z")

</div>

> [@aclaymore](#):
>
> `return undefined`

Hey, thanks! I had since then already changed that `return undefined` to `continue`. That's what I meant.

The example in the original post works now.

I wonder if it would be a bad practice to use this though. Like, for example, if some piece of code keeps accessing some object by providing the key that it maps to, then this could prevent the GC from collecting the object if it always happens before GC has a chance to run.

Would it be better if the language provided a `LossyRef` feature that would be like `WeakRef`, except that `deref()` would return undefined if the object is no longer reachable (even if not yet collected)?

Seems a feature like that would make code more robust and fail proof.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 18, 2021, 5:07pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/6 "2021-11-18T17:07:58Z")

</div>

You probably just want to combine a WeakMap for object key to value and a WeakValueMap for value to object key which needs a finalization registry to clear things. You should never need to iterate over a set, or defer unless you actually mean to access that value. I'll try to write your `TwoWayWeakMap` a little later.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 20, 2021, 9:02am UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/7 "2021-11-20T09:02:18Z")

</div>

Alright it ended up being a little trickier than I thought, but here is a `TwoWayWeakMap` that should be rock solid: [TwoWayWeakMap for https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890 · GitHub](https://gist.github.com/mhofman/f527ec77e0caf6986c2fbf34dea86306)

I switched your `keyFrom` to a `keysFor` since there can be multiple values with the same key. Also supports `-0`, doesn't prevent collection if there are cycles between keys and values, and most importantly, doesn't need to perform any whole map iteration. The only iteration is on `keysFrom`, and on a set of keys specific to the given value. It's also the only time any WeakRef is dereferenced, so it doesn't hold any objects alive unnecessarily.

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [November 29, 2021, 10:14pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/8 "2021-11-29T22:14:49Z")

</div>

BTW, your gist will still leak because [you still keep a strong reference from the key to the value, just indirectly](https://gist.github.com/mhofman/f527ec77e0caf6986c2fbf34dea86306#file-two-way-weak-map-js-L79). [Here's a corrected (and cleaner) version, with a bunch of comments explaining why each bit is the way it is.](https://gist.github.com/isiahmeadows/584b4644745a52f3d194f836c0354338) (Note: I've omitted the extras suggested here for better algorithmic clarity, but it's pretty straightforward how to alter it to support those.)

Given how tricky this is to write correctly (you as a delegate making subtle mistakes implementing it is notable) and that there _is_ some existing language precedent for implementing it at the language level (Lua's ` __mode = "kv"` for tables - `WeakMap` would be `__ mode = "k"`, and `__mode = "v"` makes values weak for context), I feel it's definitely worth looking into as a new feature.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 29, 2021, 10:33pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/9 "2021-11-29T22:33:32Z")

</div>

I believe we're solving 2 different problems.

I understood the request as being able to find all the keys of a `WeakMap` that have a given value, but besides that for the map to still be a regular `WeakMap` where keys are held weakly, but the value strongly associated to the key. You are implementing a `WeakKeyValueMap`, where both the key and value are held weakly.

I thought I had a similar Weak key and value map implemented as a gist somewhere but I can't find it right now. It's roughly the same shape as yours, but I allowed the keys to be a primitive as well, so I used a `Map` instead of a `WeakMap` for them.

It should be noted that any new collection that holds the value weakly allows to observe the liveness of the value, which is the power granted by `WeakRef`/`FinalizationRegistry` and more powerful than `WeakMap`.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 29, 2021, 10:36pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/10 "2021-11-29T22:36:54Z")

</div>

> [@claudiameadows](#):
>
> Given how tricky this is to write correctly

Agreed it's extremely tricky to write those right, and I'm surprised no-one has published various weak collections on npm yet.

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [November 29, 2021, 10:36pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/11 "2021-11-29T22:36:55Z")

</div>

I'm used to the following taxonomy:

- Strong key, strong value = `Map`
- Strong key, weak value = what I normally think when hearing weak value map
- Weak key, strong value = JS's `WeakMap`
- Weak key, weak value = my `WeakKeyValueMap` (what I perceived the proposal to be)

Hope this resolves the communication disconnect.

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [November 29, 2021, 10:37pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/12 "2021-11-29T22:37:56Z")

</div>

I stumbled across [weak-value - npm](https://www.npmjs.com/package/weak-value) when looking for language precedent, and that aligns with my taxonomy as well.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 29, 2021, 11:09pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/13 "2021-11-29T23:09:31Z")

</div>

> [@claudiameadows](#):
>
> Hope this resolves the communication disconnect.

I believe we're on the same page. Sorry I originally mistyped and edited my post immediately. TBH, I don't quite remember the variety of specialized weak collections I've implemented over time, and mangled things at first.

> [@trusktr](#):
>
> The idea is that `TwoWayWeakMap` would work like `WeakMap`, except that you would be able to get key objects by their value

I understood the original request to be for a regular `WeakMap`, with the addition of a key lookup by value, which is basically a reverse "weak map" from values to the set of corresponding keys. Since "values" can either be objects or primitives, it actuality has both a `WeakMap` for object values, and a `WeakValueMap` for primitives values.

> [@claudiameadows](#):
>
> I stumbled across [weak-value - npm](https://www.npmjs.com/package/weak-value) when looking for language precedent, and that aligns with my taxonomy as well.

There is also @devsnek's [GitHub - devsnek/weak-value-map: The WeakValueMap you deserve](https://github.com/devsnek/weak-value-map). In general a `WeakValueMap` is pretty straight-forward once you remember the trick to use the `WeakRef` as the unregister token to do the `FinalizationRegistry` book-keeping.

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [November 29, 2021, 11:31pm UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/14 "2021-11-29T23:31:29Z")

</div>

> [@mhofman](#):
>
> I understood the original request to be for a regular `WeakMap`, with the addition of a key lookup by value, which is basically a reverse "weak map" from values to the set of corresponding keys. Since "values" can either be objects or primitives, it actuality has both a `WeakMap` for object values, and a `WeakValueMap` for primitives values.

Oh, okay, I see now. And I can't say I'm a fan of mixing weak map types - just seems too unpredictable in practice.

---

<div class="post-metadata">

**Author:** ![trusktr](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/trusktr/32/183_2.png) [@trusktr](https://es.discourse.group/u/trusktr)\
**Post date:** [December 31, 2021, 12:29am UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/15 "2021-12-31T00:29:45Z")

</div>

The problem all of the implementations have motivated me to open this issue:

> [@LossyRef, better than WeakRef](https://es.discourse.group/t/lossyref-better-than-weakref/1096):
>
> In [How to implement TwoWayWeakMap? Using WeakRef?](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890) there is an example of a TwoWayWeakMap. However a problem is that, if someone tries to get an object key using a non-object value with the keyFrom method, if they do this often enough before GC ever has a chance to kick in, then it is possible they can inadvertently prevent the object from being collected (depending on the runtime, and depending on random timing of a program). It seems that a new feature like LossyRef, whose deref() method woul…

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [December 31, 2021, 12:47am UTC](https://es.discourse.group/t/how-to-implement-twowayweakmap-using-weakref/890/16 "2021-12-31T00:47:13Z")

</div>

Does my implementation not satisfy your requirements? And if it doesn't, in what way?
