wasm-bindgen icon indicating copy to clipboard operation
wasm-bindgen copied to clipboard

Add support for `#[repr(transparent)]` on exported structs

Open nasso opened this issue 3 years ago • 5 comments

This PR adds support for #[repr(transparent)] on exported types:

#[repr(transparent)]
#[wasm_bindgen]
pub struct TransparentU32(u32);

#[wasm_bindgen]
pub fn make_transparent_u32(value: u32) -> TransparentU32 {
    TransparentU32(value)
}

#[wasm_bindgen]
pub fn get_transparent_u32(value: TransparentU32) -> u32 {
    value.0
}
const r = wasm.make_transparent_u32(42);
assert.strictEqual(r, 42);
assert.strictEqual(wasm.get_transparent_u32(r), 42);
assert.strictEqual(r + 1, 43);
assert.strictEqual(wasm.get_transparent_u32(r) + 1, 43);
assert.strictEqual(typeof r, "number");

See #2156 and #1474 for the motivation behind this.

  • [x] Transparent primitives (u8, u16, u32, f32...)
  • [x] Transitive transparency (Transparent(Transparent(a)) == a)
  • [x] Zero-sized field(s) after the only non-zero-sized field (Transparent(u32, ()))
  • [ ] Zero-sized field(s) before the only non-zero-sized field (Transparent((), u32))
  • [ ] Transparent Option<T>, Box<T>, Result<T, E>...
  • [ ] Transparent classes, enums...
  • [ ] Typescript type definitions

Fixes #1474.

nasso avatar Dec 28 '21 04:12 nasso

By using this implementation it forces structs like char to consider everything valid because we can't control what JS gives Rust.

How does that usually work across the FFI? Is it a non-issue because the FFI is unsafe anyway?

nasso avatar Jan 05 '22 14:01 nasso

Yeah I would imagine that most other FFI is already "unsafe enough" that this concern doesn't come up. That being said I also don't personally see this much with FFI since the goal isn't to make it as nice as possible but rather get something working first.

alexcrichton avatar Jan 05 '22 19:01 alexcrichton

One of the original motivations behind supporting repr(transparent) was just to provide a safer API on the JS/TS side (instead of manipulating the wrapped type directly). This could be achieved with some Typescript magic.

Another major motivation (much more important, imo) is to allow some types to be represented as JavaScript primitives to allow some operations such as === equality checks or comparisons to be performed more naturally. It also allows values to be serialised (sometimes necessary, e.g. when using the Drag and Drop API).

If the invariants introduced by some repr(transparent) types are a pain point, maybe a similar bindgen-specific attribute could be used to provide similar functionality? Another solution to explore would be to allow the user to define their own validation/conversion functions between the wrapped and the new type, but I'm not sure if this is desirable.

nasso avatar Jan 06 '22 00:01 nasso

To me that only seems fair if From was already required when targeting platforms other than the Web

I don't know if forcing the user to write anything more than #[repr(transparent)] is a good idea. If wasm-bindgen needs more than that, I think it's better to have it as a separate bindgen attribute like #[wasm_bindgen(transparent)] or #[wasm_bindgen(transparent = Foo)]. This solution would solve the "which is the non-zero sized field?" problem by emulating type transparency at the bindgen level instead (with From/Into conversions at the FFI boundary). True transparency is still possible by adding #[repr(transparent)], but wasm-bindgen wouldn't care about it (just like it does today)

nasso avatar Jan 06 '22 22:01 nasso

This PR is quite old but it adds something valuable to wasm-bindgen that I would find quite useful.

prideout avatar May 17 '24 10:05 prideout