Typescript: Use symbols instead of WeakMap for private names

Created on 1 Mar 2020  路  8Comments  路  Source: microsoft/TypeScript

Search Terms


private

Suggestion

Use symbol instead of WeakMap to simulate private names (#name).
Example:

class Test {
  #foo = 1;
}

should become:

const _foo = Symbol('foo');
class Test {
  [_foo] = 1;
}

Use Cases

  1. It uses the same syntax as accessing other non-private field, for example:
  2. this.#foo is this[_foo]
  3. this.#foo = 2 is this[_foo] = 2
  4. this.#foo++ is this[_foo]++
  5. class Test { #foo = 1 } is class Test { [_foo] = 1 }

  6. Less boilerplate. Not only accessing a field is less verbose and more familiar, but it doesn't require additional helpers like __classPrivateFieldGet

  7. Most importantly. You can debug the instance with private names more easily. Right now, it's a real pain.
    The private names with symbols will be visible and accessible using developer console in browser, as the native private fields are.
    There no real reason why they shouldn't be. So called "hard" privacy can be easily broken with monkey patching WeakMap and there no real benefit from hiding it from user.
    Note that when using symbols, you will still get all that actual benefits of using private names, like uniqueness (they cannot be overwritten).
    Here's a POC how you can access the private names:
    http://www.typescriptlang.org/play/index.html#code/PTAEFsHsDsGsFMCeoAOBDALgYwBagOrxqwCyaKAsAFBYwDOGqATgJYBum8ZKdoAvKGjwA7gSKlyAHjTREAGlABleBkmFi3abIUzEAPgMAKAJQBuarWgNQw8d2WMB6iSgB0KJpAxfEKeK7oVcypnbndPbwxff0DHUAAzAFdoLAwWGFBYwwREAC5QXQUOABtE+HzdY1AAb2pQetBLa1iAMUgmAHkAIwAreFT+ZnZObjpXAHMVbKQzOoaWeNBDAEJW9u6+1Kraqga9mztyB1csNGLiww9hjC5yOgUchSFRB0MAbVAMHBZeAF1jWa7BoAX1A8GKgRqc32mRUbU6vX6GFcaAAJqjDF8foC9sDoaAmCpEkxoAcNEcVCczhcsfdQI9QCUyoDgcFqCBQJNGGgsFh4HQ6O0EkKrhwbgkWODUXRqEkUmkMjy+QKAAqsMXwFqS4rSyQAFT0hkgvQq2lArgt8W10vy00QkEWeuMb3+UKB9SajDWCM2cVFIzuEymxp6OPmi0M3o2SO2+L2noKvP5gqYg2qoLQvD1wRhHvojBYNyYmCFAijiNSriZ-JMOdz8SFhgTVqloAdEqldFj7tzeasjH9N24g0L8GL3iYriEAA8MCYq2cynXe-Vo5XUfArUI1ZA-EwooYlcn2goWzqFDsV7mubaqnw9EMNWEuUbesY5HGr7CMPlq-wH5eX69oOtxuFkIZFIu8BhkB9TAh+Pa9sCMH7HiiENISGDEqSR4Cu0y6gGheyYdhwRoeyYDwNOaDgCgxTwI0xSZjKNBMQKoB6vyjCAfUADEDaQIMACMBG8V0aCpgIADkaBSQR+JcsKkAmG6vYkSSnzfGM-GQJABHkT2iniUwKk8fs6mkrSrhiRJ+nUAZCY3NYAjPBxXG1tQFj5omyopoMuF0Gq1yatadCYlxChSQJUmRcZUmAk0kD0a4xSQOM4UMK4AkKE5yLGToSZ4UwyxZbpBW+e0JXGWYoAckJOgKPVBTUAFlWlYJAgAEzBK1xWuMZgxSV0cleVYSX+Kl6W5e1OVcf1EnlcefXZT5S1VRJNUcp1ChdAo22gF0QA

Examples

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.
Working as Intended

All 8 comments

it doesn't require additional helpers like __classPrivateFieldGet

Doesn't it? In order to have accurate runtime semantics, you need someExpression.#foo to throw if someExpression doesn't have #foo. I think you can get everything you need with const _fooSym = Symbol() and a _fooSym in someExpression test, but you still have to throw somewhere; either inline at every access site or in a helper.

@nmain yep, my bad. But the most important part is the ability to debug and the argument still stands. Private fields bring nothing to the table for the user, it only improves the developer experience. And if current implementation actually makes the DX worse, why use it?

You can use Object.getOwnPropertySymbols to fetch the symbols associated with the property names, and then use them to access the props - unfortunately, using symbol named properties doesn't get you real privates, just harder-to-mangle names.

And if current implementation actually makes the DX worse, why use it?

If you believe that, you should use private keyword privates with symbol named properties yourself. Unfortunately, # privates come with the expectation of a best effort at hard runtime privacy.

You can use Object.getOwnPropertySymbols to fetch the symbols associated with the property names, and then use them to access the props - unfortunately, using symbol named properties doesn't get you real privates, just harder-to-mangle names.

As I said, you can break the encapsulation when using WeakMap. It's a little bit harder, but either way the true encapsulation is impossible. So why not choose a solution that is more friendly for developers in other manners?
What really is the end goal of private name? Is it to make it truly impossible to access private variables? Many mainstream programming languages allow you to do access them (including ECMAScript to some extend via developer tools). Some of them even provide the interface to do it like reflections and their are available for inspection for debugging purpose.

EDIT:

You can use Object.getOwnPropertySymbols to fetch the symbols associated with the property names,

I don't see it as a problem. That's the purpose to the symbols. You should always expect that the user or some other library will add a symbol property to an object you created. For all intents and purposes, those symbols that are "privates fields" are just some data which user added to object.

As I said, you can break the encapsulation when using WeakMap. It's a little bit harder, but either way the true encapsulation is impossible.

Yes and no. Yes, prototype mangling and monkey patching means it's not perfect, yes, but at least it doesn't interfere with any symbol property manipulation your code may be doing (as implicit symbol names would). Eg, if you implemented a symbol-based Map, using # privates to store implementation-internal data. So it's much better from a "looks like a real # private to an author" perspective. And if you actually wanted to get defensive around WeakMap, you could use a custom helper that either loads a custom, unmangleable WeakMap, or uses a memory-leaking object to back the property storage, if your threat model actually includes actively hostile code (and not just code that expects standard-compliant behavior).

Again, if you want symbolic named privates, just use symbolic named private keyword privates.

What really is the end goal of private name? Is it to make it truly impossible to access private variables?

This is the motivating scenario for all of private fields's behavior, yes.

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Roam-Cooper picture Roam-Cooper  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments

Zlatkovsky picture Zlatkovsky  路  3Comments

dlaberge picture dlaberge  路  3Comments

siddjain picture siddjain  路  3Comments