-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Symbols can't be used as WeakMap keys #1194
Comments
Global symbols ( |
Hi Jordan, yes, I went down the same chain of reasoning.
…On Tue, May 15, 2018 at 11:59 AM, Jordan Harband ***@***.***> wrote:
Global symbols (Symbol.for, Symbol.keyFor) are not unique, and as
primitives, can't ever be collected - I assume that since these kinds of
symbols can't be WeakMap keys, and because it would be confusing for some
symbols to be usable as WeakMap keys but not others, that it makes the most
sense to disallow all symbols as WeakMap keys?
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#1194 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAQtzAoxX7S4Mmw_fFAStvveTlzCvXcLks5tyyWJgaJpZM4UADYc>
.
--
Cheers,
--MarkM
|
i would lean towards allowing global symbols since they aren't the only things that could prevent collection. any value used as a weakmap key can be attached places such that it won't be collected. beyond that, Symbol() symbols should definitely be allowed. |
I don't see a significant difference between a registry-Symbol and an object that's stored on the global. Both will never be collected, and thus will prevent a weak value from being collected. But if we really do think this is a footgun, then we can just disallow registry-Symbols from being used (those for whom It just seems silly that |
You can always prevent collection; the difference is that with objects you can always allow collection by dropping all refs to the object - or by having the realm itself collected. With global symbols - which are cross-realm - it would prevent ever collecting it. |
@ljharb If a WeakMap is reaped it does not prevent collecting values with eternal keys since that weak reference to the value is removed, even if the key itself is strongly held still. |
@erights On a tagent that might come back to this, is there any reason having keys mismatch between Weak collections and WeakRef might be problematic? I know for WeakRef it would never fire a finalizer for a Symbol from the SymbolRegistry if it were allowed as a key (but it would strongly keep the finalizer/holdings alive I think?). |
Isn't the same true of the outermost global object? |
@gibson042 if the realm is collected, the global object for it could also be (assuming it was a key in a WeakMap from a different realm) |
Right, but I was referring to the global object of the outermost realm (though I suppose such a statement reads as vacuously true). Still, the set of well-known Symbols shared across realms is necessarily bounded and small, and their uncollectability shouldn't be a concern. Or am I missing something? |
I'm not sure this talk about keys really matters if the WeakMap being reaped allows what it is holding be reaped? Even if I create a map using: let map = new WeakMap();
map.set(Symbol.iterator, BIG_OBJECT); // Symbol.iterator is shared between all realms as well
map = null;
|
One thing that comes to mind here is that doesn't seem to be polyfillable without leaking. Are there any concerns around that? |
@loganfsmyth isn't that a concern with any Weak collection polyfill? |
@bmeck traditionally the polyfills assign a "hidden" property on the key that holds the value. that can't be done with a Symbol. |
In a recent project, I resorted to implementing a class called Note: for this particular project, I didn't need to match the shared I mention this example as evidence that it would be genuinely useful to have fewer restrictions on what you can put in a To the specific question of whether In either case, a native |
forgable values can't be done because we want to prevent observing gc |
@devsnek Forgeable |
Observing GC isn’t a concern; WeakRefs are coming, either via JS or via WASM, so it will be observable. |
Especially once we have |
Observable gc absolutely is a concern. That's why we separate weakref from
weakmap and put weakref on the System object.
However, I agree it is not relevant to this thread. Non ref keys would
never be collected so there's nothing too observe.
…On Thu, May 24, 2018, 6:56 PM Jordan Harband ***@***.***> wrote:
Observing GC isn’t a concern; WeakRefs are coming, either via JS or via
WASM, so it will be observable.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1194 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAQtzGEaQDgObbLfCJFlAQLTAkw0PtKVks5t1zqLgaJpZM4UADYc>
.
|
Personally, I expect that if I put a thing in a WeakMap then the corresponding value can someday be GC'd. For example, I have tons of code which does stuff like const cache = new WeakMap;
function process(data) {
if (cache.has(data)) {
return cache.get(data);
}
const res = expensiveAlgorithm(data);
cache.set(data, res);
return res;
} with the expectation that this caching is acceptable because the caller can always choose to drop I really don't think it's a good idea to mix strong and weak holding in the same data structure. Yes, I know that sometimes the global object ends up being effectively strongly held, but that's an extremely minor and extremely edge-y edge-case. |
Sorry to sidetrack, but what do you mean by (un)forgeable values? |
@woess forgability refers to the ability to create a value that has the identity of another value without having access to the original value. two examples of this are numbers and strings. i can create on the other hand we have things like objects and symbols which are unforgable. if i have |
@devsnek Good explanation of unforgeable. However, only unnamed symbols --- those created by the |
@erights I don't think that is true, the GlobalSymbolRegistry is returning the exact same Symbol rather than creating new primitive values in https://tc39.github.io/ecma262/#sec-symbol.for . Those things are reachable but not forgeable. I cannot recreate them without access to that registry. |
Access to the registry is not deniable. Given that everyone has implicit access to the registry, they can obtain access to any named symbol given only knowledge of the string. IOW, access to a named symbol is "knowledge limited" rather than "access limited". We were very careful to design the semantics of the registry so that it would not be a global communications channel. Given the way the semantics of the registry are stated, this safety property is hard to see. A better way to describe its semantics is that a named symbol is a value, without identity, that wraps a string. All the equality comparison operators, given two named symbols, judges them to be equal iff their wrapped strings are equal. This account is not observably different and need not hypothesize any registry or any other form of shared state. In this account, it is obvious there is no global communications channel. |
@erights https://tc39.github.io/ecma262/#sec-samevaluenonnumber appears to compare that they are the same value, not based upon any internal string that I can tell. It could be implemented as potentially multiple values being checked by some internal string that appear to act as a single value, but is not what the spec appears to be saying. Do you think this a bug in the specification? |
@devsnek Yes, I understand that this is a disadvantage; how should we weigh the cost of that against the cost of not having some kind of capability in this area? (Making a separate, parallel type was another way of avoiding the mismatch.) It feels to me, overall, like this is a solvable problem, and we just have various tradeoffs that we can make about the solution. |
@littledan i think its less about specific tradeoffs and more that we're in deadlock (some people don't want all symbols, some people don't want only specific kinds of symbols) |
Lots of times, you can get through deadlock by considering the whole space, the value of the proposal overall, and weighing it against the cost of various alternatives. We've gotten through various controversial/deadlocked things at TC39 before this way. |
Overall, I think Symbols as WeakMap keys would be a very useful base for being able reference objects from primitives (in the context of the Records and Tuples proposal), by indirecting through the WeakMap. The implication chain goes like this:
Symbols as WeakMap keys seems simpler than adding a "box" type, even though we don't need to use these things as property keys. Such a system would preserve our typical invariants protecting membranes/ocap systems, since object operations are used to access the WeakMap. While it would be ergonomically nice and composition-promoting to have a single built-in mapping from Symbols (or some other primitive) to objects, this wouldn't meet ocap goals, since it would constitute built-in shared state, providing a cross-compartment communication channel for multiple compartments sharing the same frozen Realm containing all of TC39's unmodified primordials. The open questions I see are:
I hope we can work out a solution here among the available options.
I plan to propose "Symbols as WeakMap keys" for Stage 1 at the June 2020 TC39 meeting. By going through the stage process, I hope we can develop consensus on answers to these questions little by little. |
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. Closes tc39#1194
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. Closes tc39#1194
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. Closes tc39#1194
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. Closes tc39#1194
It would be nice if Then, if a symbol returned by This would cancel out the concern of foregeable vs unforgeable symbols, if I understand correctly. I think this would be fine for Breaking change:This would break code that currently relies purely on
Maybe solutions:An idea could be to make Another idea is to at least introduce an API for cleanup like As usual, I always proclaim that all APIs should be designed with the ability to clean up whatever they create.
That's a requirement for well-designed re-usable APIs. |
I believe current implementations already are a sort of let wm = new WeakMap();
let s1 = Symbol.for('foo');
wm.set(s1, 8);
s1 = null;
// say a full gc happens here
let s2 = Symbol.for('foo');
console.log(wm.get(s2)); As I explained in tc39/proposal-symbols-as-weakmap-keys#19 (comment), the observation can only be made while the weak collections and weak refs stay reachable. Once these objects containing the registered symbol as key or target is unreachable, the implementation is free to unobservably collect the registered symbol.
Could you clarify what you mean? If the user program is not holding the symbol itself, by definition it is not using it for anything, and cannot observe if the implementation is actually using another symbol instance. For that reason, the |
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. Closes tc39#1194
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. Closes tc39#1194
Specification PR for https://github.com/tc39/proposal-symbols-as-weakmap-keys Note that both registered and unregistered Symbols are permitted here. rename AO to CanBeHeldWeakly and include WeakRef and FinilisationRegistry WIP: extend weakrefs to symbols Update liveness section Closes tc39#1194 Co-authored-by: Mathieu Hofman <[email protected]> Co-authored-by: Richard Gibson <[email protected]> Co-authored-by: Jordan Harband <[email protected]> Co-authored-by: Daniel Ehrenberg <[email protected]> Co-authored-by: Leo Balter <[email protected]> Co-authored-by: Ashley Claymore <[email protected]>
- Proposal: https://github.com/tc39/proposal-symbols-as-weakmap-keys - Also allows Symbols in WeakSet, WeakRef, and FinalizationRegistry - Adds new AO 'CanBeHeldWeakly' - Registered Symbols can not be held weakly Closes tc39#1194 Co-authored-by: Daniel Ehrenberg <[email protected]> Co-authored-by: Leo Balter <[email protected]> Co-authored-by: Mathieu Hofman <[email protected]> Co-authored-by: Richard Gibson <[email protected]> Co-authored-by: Jordan Harband <[email protected]> Co-authored-by: Shu-yu Guo <[email protected]> Co-authored-by: Michael Dyck <[email protected]> Co-authored-by: Michael Ficarra <[email protected]>
- Proposal: https://github.com/tc39/proposal-symbols-as-weakmap-keys - Also allows Symbols in WeakSet, WeakRef, and FinalizationRegistry - Adds new AO 'CanBeHeldWeakly' - Registered Symbols can not be held weakly Closes tc39#1194 Co-authored-by: Daniel Ehrenberg <[email protected]> Co-authored-by: Leo Balter <[email protected]> Co-authored-by: Mathieu Hofman <[email protected]> Co-authored-by: Richard Gibson <[email protected]> Co-authored-by: Jordan Harband <[email protected]> Co-authored-by: Shu-yu Guo <[email protected]> Co-authored-by: Michael Dyck <[email protected]> Co-authored-by: Michael Ficarra <[email protected]>
Excluding registered symbols as valid WeakMap keys compromises the feature so badly that I don't really consider this bug to actually be fixed since (e.g.) I can't write a library with an API that takes arbitrary symbols and uses a WeakMap to store associated information about them. Yes, it's a bit better that at least some private data indexed by symbol can be garbage collected, but it's still necessary to maintain parallel I'm really sorry that you didn't stick to your guns about treating all symbols the same, @ljharb. |
I don't understand the hypothetical implementation comment. There are a multitude of implementations, some that can collect registered symbols, some that can't. However in either case, these symbols are specified to be forgeable, which means that the program should not be able to observe their collection. That means that regardless of the implementation, if a registered symbol is used as a WeakMap key, it must in practice be held strongly and the entry cannot be collected (technically registered symbols can still be collected if their collection is not observed by a program making use of WeakRef and/or FinalizationRegistry, either directly, or indirectly through a chain of associated WeakMap values, but that would require an even more complex ephemeron-like logic). Instead of forcing the implementation to pay the cost of this dual Map/WeakMap logic internally, the onus is on the program to handle registered symbols if it absolutely must treat all symbols equally. I would be very curious to hear about the use case requiring storing blindly all symbols in a WeakMap, yet other forgeable values like string and numbers don't have the same requirement. Why can't your library require unique symbols? |
@cpcallen registered symbols shouldn't ever have existed - they're just globals v2 - so i thought it was a viable compromise to allow the good kinds of symbols to be held weakly. |
I have had a question for a long time --- what's the original motivation of registered symbols? |
I think you mean strings instead of globals. Basically allowing registered symbols is the same as allowing strings, which remains a bad idea for the reasons @mhofman says. @cpcallen If your issue is that you want to type |
You can already do: /** @param {symbol} sym */
const isRegisteredSymbol = (sym) => {
return Symbol.keyFor(sym) !== undefined;
} |
@ExE-Boss close - you'd also need |
Symbols cannot be used as WeakMap keys as they are primitives. However unlike other primitives they are unforgable and unique, so they should be completely safe for use as WeakMap keys.
The text was updated successfully, but these errors were encountered: