Skip to content
This repository was archived by the owner on Aug 25, 2025. It is now read-only.

Commit a8bfb29

Browse files
committed
Proposal to support single inheritance and casting in wasm-bindgen
1 parent ec5fdd7 commit a8bfb29

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
- Start Date: 2018-07-10
2+
- RFC PR: (leave this empty)
3+
- Tracking Issue: (leave this empty)
4+
5+
# Summary
6+
[summary]: #summary
7+
8+
Support defining single inheritance relationships in `wasm-bindgen`'s imported
9+
types, and how to up- and downcast between concrete types in the emitted
10+
bindings. For the proc-macro frontend, we add the `#[wasm_bindgen(extends =
11+
Base)]` attribute to the derived type. For the WebIDL frontend, we will use
12+
WebIDL's existing interface inheritance syntax. Finally, we introduce the
13+
`wasm_bindgen::Upcast` and `wasm_bindgen::Downcast` traits, which can be used to
14+
up- and downcast concrete types at runtime. `wasm-bindgen` will emit
15+
implementations of these traits as in its generated bindings.
16+
17+
# Motivation
18+
[motivation]: #motivation
19+
20+
Prototype chains and ECMAScript classes allow JavaScript developers to define
21+
single inheritance relationships between types. [WebIDL interfaces can inherit
22+
from one another,][webidl-inheritance] and Web APIs make widespread use of this
23+
feature. We want to be able to support these features in `wasm-bindgen`.
24+
25+
# Stakeholders
26+
[stakeholders]: #stakeholders
27+
28+
Anyone who is using `wasm-bindgen` directly or transitively through the
29+
`web-sys` crate is affected. This does *not* affect the larger wasm ecosystem
30+
outside of Rust (eg Webpack). Therefore, the usual advertisement of this RFC on
31+
*This Week in Rust and WebAssembly* and at our working group meetings should
32+
suffice in soliciting feedback.
33+
34+
# Detailed Explanation
35+
[detailed-explanation]: #detailed-explanation
36+
37+
## Simplest Example
38+
39+
Consider the following JavaScript class definitions:
40+
41+
```js
42+
class MyBase { }
43+
class MyDerived extends MyBase { }
44+
```
45+
46+
We translate this into `wasm-bindgen` proc-macro imports like this:
47+
48+
```rust
49+
#[wasm_bindgen]
50+
extern {
51+
pub extern type MyBase;
52+
53+
#[wasm_bindgen(extends = MyBase)]
54+
pub extern type MyDerived;
55+
}
56+
```
57+
58+
Note the `#[wasm_bindgen(extends = MyBase)]` annotation on `extern type
59+
MyDerived`. This tells `wasm-bindgen` that `MyDerived` inherits from
60+
`MyBase`. In the generated bindings, `wasm-bindgen` will emit these trait
61+
implementations:
62+
63+
1. `Upcast for MyDerived`,
64+
2. `From<MyDerived> for MyBase`, and
65+
3. `Downcast<MyDerived> for MyBase`
66+
67+
Using these trait implementations, we can cast between `MyBase` and `MyDerived`
68+
types:
69+
70+
```rust
71+
// Get an instance of `MyDerived` from somewhere.
72+
let derived: MyDerived = get_derived();
73+
74+
// Upcast the `MyDerived` instance into a `MyBase` instance. This could also be
75+
// written `derived.into()`.
76+
let base: MyBase = derived.upcast();
77+
78+
// Downcast back into an instance of `MyDerived`. We `unwrap` because we know
79+
// that this instance of `MyBase` is also an instance of `MyDerived` in this
80+
// particular case.
81+
let derived: MyDerived = base.downcast().unwrap();
82+
```
83+
84+
## The Casting Traits
85+
86+
### `Upcast`
87+
88+
The `Upcast` trait allows one to cast a derived type "up" into its base
89+
type. The trait methods are safe because if a type implements `Upcast` then we
90+
statically know that it is also an `Upcast::Base`. Implementing the trait is
91+
`unsafe` because if you incorrectly implement it, it allows imported methods to
92+
be called with an incorrectly typed receiver.
93+
94+
```rust
95+
pub unsafe trait Upcast {
96+
type Base: From<Self>;
97+
98+
fn upcast(self) -> Self::Base;
99+
fn upcast_ref(&self) -> &Self::Base;
100+
fn upcast_mut(&mut self) -> &mut Self::Base;
101+
}
102+
```
103+
104+
Under the hood, all these upcasts are implemented with `transmute`s. Here is an
105+
example implementation of `Upcast` that might be emitted by `wasm-bindgen`:
106+
107+
```rust
108+
unsafe impl Upcast for MyDerived {
109+
type Base = MyBase;
110+
111+
#[inline]
112+
fn upcast(self) -> Self::Base {
113+
unsafe { std::mem::transmute(self) }
114+
}
115+
116+
#[inline]
117+
fn upcast_ref(&self) -> &Self::Base {
118+
unsafe { std::mem::transmute(self) }
119+
}
120+
121+
#[inline]
122+
fn upcast_mut(&mut self) -> &mut Self::Base {
123+
unsafe { std::mem::transmute(self) }
124+
}
125+
}
126+
```
127+
128+
### `Downcast`
129+
130+
Casting "down" from an instance of a base type into an instance of a derived
131+
type is fallible. It involves a dynamic check to see if the base instance is
132+
also an instance of the derived type. The `downcast_{ref,mut}` methods return an
133+
`Option` since casting failure doesn't withhold access to the original base
134+
instance. The `downcast(self)` method returns a `Result` whose `Err` variant
135+
gives ownership of the original base instance back to the caller. Because of the
136+
`T: Upcast<Self>` bound, the `Downcast` trait does not need to be `unsafe`;
137+
attempts to implement `Downcast<SomeTypeThatIsNotDerivedFromSelf>` will fail to
138+
compile.
139+
140+
```rust
141+
pub trait Downcast<T>
142+
where
143+
T: Upcast<Self>
144+
{
145+
fn downcast(self) -> Result<T, Self>;
146+
fn downcast_ref(&self) -> Option<&T>;
147+
fn downcast_mut(&mut self) -> Option<&mut T>;
148+
}
149+
```
150+
151+
The dynamic checks are implemented with `wasm-bindgen`-generated imported
152+
functions that use the `instanceof` JavaScript operator.
153+
154+
```js
155+
const __wbindgen_instance_of_MyDerived(idx) =
156+
idx => getObject(idx) instanceof MyDerived ? 1 : 0;
157+
```
158+
159+
```rust
160+
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
161+
#[wasm_import_module = "__wbindgen_placeholder__"]
162+
extern {
163+
fn __wbindgen_instance_of_MyDerived(idx: u32) -> u32;
164+
}
165+
166+
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
167+
unsafe extern fn __wbindgen_instance_of_MyDerived(_: u32) -> u32 {
168+
panic!("function not implemented on non-wasm32 targets")
169+
}
170+
171+
impl Downcast<MyDerived> for MyBase {
172+
#[inline]
173+
fn downcast(self) -> Result<MyDerived, MyBase> {
174+
unsafe {
175+
if __wbindgen_instance_of_MyDerived(self.obj.idx) == 1 {
176+
Ok(std::mem::transmute(self))
177+
} else {
178+
Err(self)
179+
}
180+
}
181+
}
182+
183+
#[inline]
184+
fn downcast_ref(&self) -> Option<&MyDerived> {
185+
unsafe {
186+
if __wbindgen_instance_of_MyDerived(self.obj.idx) == 1 {
187+
Some(std::mem::transmute(self))
188+
} else {
189+
None
190+
}
191+
}
192+
}
193+
194+
#[inline]
195+
fn downcast_mut(&mut self) -> Option<&mut MyDerived> {
196+
unsafe {
197+
if __wbindgen_instance_of_MyDerived(self.obj.idx) == 1 {
198+
Some(std::mem::transmute(self))
199+
} else {
200+
None
201+
}
202+
}
203+
}
204+
}
205+
```
206+
207+
# Drawbacks
208+
[drawbacks]: #drawbacks
209+
210+
* We might accidentally *encourage* using this inheritance instead of the more
211+
Rust-idiomatic usage of traits.
212+
213+
# Rationale and Alternatives
214+
[alternatives]: #rationale-and-alternatives
215+
216+
* We could instead use `From` and `TryFrom` instead of defining `Upcast` and
217+
`Downcast` traits. This would require more trait implementations, since we
218+
would be replacing `Upcast for MyDerived` with `From<MyDerived> for MyBase`,
219+
`From<&'a MyDerived> for &'a MyBase`, and `From<&'a mut MyDerived> for &'a mut
220+
MyBase`. Similar for downcasting.
221+
222+
* The `Upcast` trait could use type parameters for `Base` instead of an
223+
associated type. This would allow for defining multiple inheritance
224+
relationships. However, neither JavaScript nor WebIDL support multiple
225+
inheritance. Associated types also generally provide better type inference,
226+
and require less turbo fishing.
227+
228+
* Explicit casting does not provide very good ergonomics. There are a couple
229+
things we could do here:
230+
231+
* Use the `Deref` trait to hide upcasting. This is generally considered an
232+
anti-pattern.
233+
234+
* Automatically create a `MyBaseMethods` trait for base types that contain all
235+
the base type's methods and implement that trait for `MyBase` and
236+
`MyDerived`? Also emit a `MyDerivedMethods` trait that requires `MyBase` as
237+
a super trait, representing the inheritance at the trait level? This is the
238+
Rust-y thing to do and allows us to write generic functions with trait
239+
bounds. It is also orthogonal to casting between base and derived types! We
240+
leave exploring this design space to follow up RFCs.
241+
242+
# Unresolved Questions
243+
[unresolved]: #unresolved-questions
244+
245+
* Should the `Upcast` and `Downcast` traits be re-exported in
246+
`wasm_bindgen::prelude`?
247+
248+
* Basically everything has `Object` at the root of its inheritance / prototype
249+
chain -- are we going to run up against orphan rule violations?
250+
251+
* Should the `instanceof` helper functions be generated and exposed as public
252+
utility methods for every imported type?
253+
254+
[webidl-inheritance]: https://heycam.github.io/webidl/#dfn-inherit

0 commit comments

Comments
 (0)