Skip to content

Commit 07d9c6e

Browse files
authored
Minor clarifications
1 parent 1fe3a3b commit 07d9c6e

File tree

1 file changed

+15
-9
lines changed

1 file changed

+15
-9
lines changed

text/000-prototypal-inheritance.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ In respect of the latter group, various related issues have been identified in t
3939
# Detailed Explanation
4040
[detailed-explanation]: #detailed-explanation
4141

42-
There are three key problems with the status quo that currently prevent WASM types participating in JavaScript's prototypal inheritance:
42+
There are three key problems with the status quo that currently prevent exported types from participating in JavaScript's prototypal inheritance:
4343

44-
1. There is currently no way in Rust to specify the inheritance relationship and thereby have an `extends` clause generated on the JavaScript shim class, which makes it more difficult (if not impossible) to construct objects in the manner required by many APIs; indeed, Web Components' customized built-in elements explicitly require use of the `extends` clause, per the normative [core concepts](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts) section of their specification.
44+
1. There is currently no way in Rust to specify the inheritance relationship and thereby guide what [`extends`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends) clause should be generated on the JavaScript shim class, which makes it more difficult (if not impossible) to construct objects in the manner required by many APIs; indeed, Web Components' customized built-in elements explicitly require use of the `extends` clause, per the normative [core concepts](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts) section of their specification.
4545

46-
2. Even if an `extends` clause is somehow generated, the shim's `constructor()` (which presently just delegates to the WASM function) **must** invoke its super-constructor before returning: there is presently no way to make this invocation from within WASM, nor even any way that an imported JavaScript function, to which the WASM could then call-back, can invoke `super()` (as such call must be explicitly made, with whatever arguments are appropriate, from within the `constructor()` itself). Moreover, the shim's `constructor()` presently returns an object other than `this`, which violates the expectations of many APIs (including the explicit [conformance requirements of Web Components' custom elements](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance)).
46+
2. Even if an `extends` clause is somehow generated, the shim's `constructor()` (which presently just wraps whatever WASM function was annotated with `#[wasm_bindgen(constructor)`) **must** invoke its super-constructor before returning: there is presently no way to make this invocation from within WASM, nor even any way that an imported JavaScript function, to which the WASM could then call-back, can invoke `super()` (as such calls must be explicitly made, with whatever arguments are appropriate, from within the `constructor()` itself). Moreover, the shim's `constructor()` presently returns an object other than `this`, which violates the expectations of many APIs (including the explicit [conformance requirements of Web Components' custom elements](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance)).
4747

4848
3. `wasm-bindgen`'s existing approach to passing WASM objects to JavaScript does not provide for their polymorphism into parent types on their return to WASM. In particular, when WASM sends out an opaque pointer (in fact the address of a boxed `WasmRefCell`) to a `Child` instance and later receives that pointer back as part of an incoming FFI that expects a `Parent`, the resulting attempt to dereference that pointer as a `Parent` when it is in fact a `Child` will of course be undefined behaviour—despite the fact that this *should* be permitted under the inheritance relationship.
4949

5050
This RFC proposes a solution to all of these points, and furthermore also proposes a solution to the related problem of obtaining a [`JsValue`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html) for any given export or import which could be used, for example, to pass a `struct` to JS APIs that expect a constructor function (such as [`web_sys::CustomElementRegistry::define`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.CustomElementRegistry.html#method.define)).
5151

52-
These problems are addressed below in the above order, but consideration is only given at first to how exported types are instantiated from JavaScript; the problem of correctly instantiating exported types from within WASM is not visited until the third section (on FFI polymorphism).
52+
These problems are addressed in this order below, but consideration is at first only given to how exported types are instantiated from JavaScript; the problem of correctly instantiating exported types from within WASM is not visited until the third section (on FFI polymorphism).
5353

5454
### Generating `extends` clause in shim classes
5555

@@ -210,13 +210,15 @@ Two issues do however arise, regarding the internal pointers these shim objects
210210
211211
### FFI polymorphism
212212
213-
*All* objects are to be instantiated by JavaScript, irrespective of whether their type is actually exported from WASM.
213+
*All* objects are to be instantiated from JavaScript, irrespective of whether their type is actually exported from WASM.
214214
215-
This means that instantiating a struct defined in Rust would *always* entail using JavaScript's [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) operator on the shim class, irrespective of whether the object is being instantiated from within JavaScript or WASM (if the latter, there must necessarily be a call to an imported function in order to trigger such behaviour; the result would therefore produce a wrapped `JsValue` that indirectly represents the instantiated WASM-native object: this wrapper should be a [smart pointer](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html), implementing [`std::ops::Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) for the underlying struct in a manner that avoids FFI roundtrips to obtain its boxed `WasmRefCell`).
215+
This means that instantiating a struct defined in Rust would *always* entail using JavaScript's [`new`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) operator on the shim class, irrespective of whether the object is being instantiated from within JavaScript or WASM (if the latter, there must necessarily be a call to an imported function in order to trigger such behaviour; the result would therefore produce a wrapped `JsValue` that indirectly represents the instantiated WASM-native object: this wrapper should be a [smart pointer](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html), implementing [`std::ops::Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) for the underlying struct in a manner that avoids FFI roundtrips in order to obtain the boxed `WasmRefCell`).
216216
217-
Accordingly, not only would the associated JavaScript shim objects always be properly instantiated (with not only the correct prototype chains but also the correct sequence of superconstructor calls), but furthermore any transfer of such objects over the FFI (from JavaScript to WASM) would reference the correct WASM object from their prototype chain (via the namespacing of the internal pointers described above).
217+
Accordingly, not only would the associated JavaScript shim objects always be properly instantiated (with both the correct prototype chains and also the correct sequence of superconstructor calls), but furthermore any transfer of such objects over the FFI (from JavaScript to WASM) would reference the correct WASM object from their prototype chain (via the namespacing of the internal pointers described above)—thereby achieving instant polymorphism of such objects to parent types when they are sent back over the FFI.
218218
219-
This also means that any exported initialisation functions, currently annotated with `#[wasm_bindgen(constructor)]`, should only ever be invoked *from JavaScript* after the shim object has been initialised and any superconstructors have returned. Therefore they are essentially instance methods that will be invoked with a reference to their instantiated object `&self`, much like the shim `constructor()` at that time has access to `this`. Such initialisation functions would be expected not to return anything.
219+
Note this also means that any exported initialisation functions, e.g. those currently annotated with `#[wasm_bindgen(constructor)]`, should only ever be invoked *from JavaScript* after the shim object has been initialised and any superconstructors have returned (see unanswered question #3). As such, they are essentially instance methods that will receive with a reference to their (partially instantiated) wrapped `JsValue`, much like the shim `constructor()` at that time has access to a (partially constructed) `this` object: this could be essential if, for example, the initialisation function needs to inspect state set already set on the object by a superconstructor.
220+
221+
*(This section has not yet been implemented in the prototype and will undoubtedly be fleshed out in more detail as experiments provide greater insight).*
220222

221223
### Obtaining `JsValue` of imports and exports
222224

@@ -290,6 +292,8 @@ This also means that any exported initialisation functions, currently annotated
290292
# Drawbacks
291293
[drawbacks]: #drawbacks
292294
295+
* Having to call from WASM to JavaScript (and back) in order to instantiate objects of exported types adds runtime overhead. Perhaps this should occur for derived types only?
296+
293297
* In order for the `js_function!` macro (and indeed the `fn_name` form of the `super_args` attribute to the `#[wasm_bindgen]` macro) to correctly find the identifier for the specificied function, the user-provided reference must either be defined in the same scope or else suitably namespace qualified with the struct/trait on which it is implemented or the module in which it is defined (as appropriate); names bound to functions that have been brought directly into scope via a `use` declaration cannot be used in `js_function!` (or `super_args`), as it will not be possible to find the function's identifier constant during macro expansion. As a workaround to this problem, it is suggested that a procedural macro be defined for use as an outer attribute on such `use` declarations, thereby bringing the constant into scope (and renaming it too if the function is aliased).
294298
295299
# Rationale and Alternatives
@@ -317,11 +321,13 @@ Rather than reusing the 8-byte bindgen identifiers, some other identifier from t
317321
318322
Instead of inheriting the required behaviours via their prototype chain, objects returned to JavaScript could inherit them using composition instead. However, this might violate the API's expectations (it certainly violates the conformance requirements of Web Components' custom elements): either because the library explicitly searches the prototype chain for the sought behaviour, or because it modifies the inherited behaviour after object creation. Such an approach could therefore introduce outright failures through to subtle or difficult-to-trace bugs, and is therefore not considered a viable alternative.
319323
320-
Instead of setting the prototype of constructed objects via the [`extends`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends) clause, they could instead be set with [`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) or similar. This would enable one to specify as prototypes objects that are not themselves classes/constructors, but prevents one from invoking the parent's [`[[Construct]]`](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget) internal method, which `extends` enables via `super()`. Calling the parent's `[[Construct]]` internal method is, in the author's view, a more common requirement than setting the prototype to a non-constructor object (and, again, it is indeed required of Web Components' custom elements).
324+
Instead of setting the prototype of constructed objects via the `extends` clause, they could instead be set with [`Object.setPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) or similar. This would enable one to specify as prototypes objects that are not themselves classes/constructors, but prevents one from invoking the parent's [`[[Construct]]`](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget) internal method, which `extends` enables via `super()`. Calling the parent's `[[Construct]]` internal method is, in the author's view, a more common requirement than setting the prototype to a non-constructor object (and, again, it is indeed required of Web Components' custom elements).
321325
322326
# Unresolved Questions
323327
[unresolved]: #unresolved-questions
324328
325329
1. Should `super_args` support even more exotic formats, e.g. mixing function calls with literals `[1, "hello", fn_three, false]` (after all, these could always be manually implemented with a single function)?
326330
327331
2. Rather than using synthetic identifiers (8-byte raw `ShortHash` values), should we instead use natural identifiers such as the item's import/export name? This would aid debugging and, at least in modules with relatively few references (suspected to be the majority), smaller file sizes; however, calls to `__wbindgen_export_get` would involve more data being copied over the ABI (albeit without then any need to decode into suitable property keys in the lookup maps). Are the import/export names guaranteed to be unique?
332+
333+
3. How can we prevent exported types from being instantiated directly (i.e. without going via JavaScript for proper instantiation of the shim)? Or perhaps better yet, prevent such objects from touching the FFI (they could perhaps be instantiated directly for performance reasons, and then copied to a newly instantiated object if and when required for an FFI call)?

0 commit comments

Comments
 (0)