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

Clarify the relationship to bindgen and stdweb #226

Closed
NeverGivinUp opened this Issue Aug 31, 2018 · 14 comments

Comments

Projects
None yet
6 participants
@NeverGivinUp

NeverGivinUp commented Aug 31, 2018

In my understanding, stdweb aims to only support Rust and thus can be very idiomatic, whereas bindgen aims to more or less be language independent and use webIDL. I don't know if stdweb too is planning to use webIDL in the future.

Bindgen is mentioned regularly in the weekly updates. stdweb is only mentioned once. Is the different treatment of the libraries by accident or by design? Which library do you reccoment for which use case in the short and the long run?

@Pauan

This comment has been minimized.

Member

Pauan commented Aug 31, 2018

Right now I definitely recommend stdweb, it's much more production ready: it has many features that wasm-bindgen lacks, and it's really stable (breaking changes are rare). I've written mediumish sized Rust programs with stdweb (~10,000 LOC) and overall it works great.

In the long run wasm-bindgen is probably the way to go, and it's improving at a very fast pace.

P.S. There are plans to add in full WebIDL support to stdweb.

@koute

This comment has been minimized.

koute commented Aug 31, 2018

Author of stdweb here.

Basically what @Pauan said. stdweb is more Rust-y and its emphasis is on providing:
1) A few highly idiomatic bindings as opposed to a bunch of raw, unidiomatic, autogenerated ones. For example, we convert exceptions which JS methods throw to Results, and this AFAIK cannot be autogenerated from IDL as IDL is missing information what throws what.
2) Comprehensive two-way JS interop, where you can easily embed JS code in your Rust code and convert the data between the two willy-nilly.
3) cargo-like experience where you don't have to manually mess with makefiles/build scripts.

wasm-bindgen definitely has more long term potential as it's officially supported by the Rust team while stdweb is only supported by... me (+ occasional contributors), and there is only so much I can do. (:

But if we're talking about the long term I would very much like to make stdweb compatible with wasm-bindgen in the future when/if wasm-bindgen will get the features I'd need.

@fitzgen

This comment has been minimized.

Member

fitzgen commented Aug 31, 2018

The Rust and WebAssembly domain working group aims to make surgically replacing hot JavaScript functions/modules/libraries with Rust-generated WebAssembly the best choice for fast, reliable code on the Web. We want to have an impact much larger and more widespread than on just the Rust community.

Rust, as a language, is ready to rise to this challenge in part because of its zero-cost abstractions. To leverage that on the Web, we need to make very sure that we aren't losing that zero-cost property, and introducing bloat and overhead.

We decided that the best way to realize these ideals was to create wasm-bindgen. It has been designed from the start such that you only pay for what you use, both at runtime and in code size. It has been designed from the start to be future compatible with the host bindings proposal, promising to eventually unlock even-faster-than-JavaScript calls into native DOM methods. It encourages using cheap handles to objects that straddle the JavaScript and wasm boundary (which will eventually become wasm anyrefs) instead of expensive deep copies and serialization.

In general, taking Rust's interoperability with C and the C ABI -- this core tenet that Rust plays well with others -- and lifting that into the JavaScript and WebAssembly domain is an educational lens for viewing the working group's efforts. It explains why wasm-bindgen emits ECMAScript modules and our collaboration with JavaScript bundlers like Webpack.

With these higher-level philosophical bits out of the way, I'll let @alexcrichton speak to some more of the technical specifics.

@alexcrichton

This comment has been minimized.

Contributor

alexcrichton commented Aug 31, 2018

Thanks for the question about this! I definitely sympathize with the confusion in that these two projects exist and there hasn't been a lot of "official" communication in one way or another about the relationship between the two.

In terms of clarifying the relationship between the two projects I think this is something that we can improve on as a working group! @Pauan has been actively participating in the working group but we haven't really talked specifically much about stdweb with respect to wasm-bindgen, and we as a working group haven't reached out to @koute yet. In that sense I think there's a lot of improvement for the working group to improve communication between the two projects, we've just been very focused on making sure wasm-bindgen, wasm-pack, and the tooling are ready for the edition release so far!

It sounds like there's also a technical part to this question with various claims as well. I'm not personally too familiar with the stdweb project, but I can speak to how wasm-bindgen fits in all this! At the risk of reiterating what @fitzgen has already mentioned, the main design goals of wasm-bindgen are:

  • Speed. Rust is a you-only-pay-for-what-you-use language, and Rust's story for WebAssembly should be no different. wasm-bindgen's goal is like that of C++, allowing you to write high-level code that optimizes the same as (or even better!) than what you would have written by hand otherwise (in this case raw *.wast files).
  • Size. Not only is Rust a fast language but Rust lacks no runtime. This means that Rust is ideal for writing tiny WebAssembly modules that execute blazingly fast and fit in a tiny module. With wasm-bindgen a "hello world" of addition is just a few hundred bytes, no extra fluff necessary.
  • Compatibility. The main use case for WebAssembly is on the web right now where JS is king. The ecosystem of JS programmers is the largest in the world and the web is the largest platform in the world. A major goal of wasm-bindgen is productively leveraging this ecosystem both in being able to import it (e.g. importing arbitrary node packages) as well as being able to integrate into it (being able to publish Rust code on NPM as WebAssembly and have users not even be aware that Rust is being used under the hood). Using wasm-bindgen is also just the same as using any other JS package, it's all an ES module.

Historically wasm-bindgen has required nightly Rust to work as we've figured out the best way for Rust and WebAssembly to work together. With the upcoming Rust 1.30 release (scheduled for late this October) the wasm-bindgen crate and its tooling will all compile on stable Rust. Additionally wasm-pack has always compiled on stable and is quickly maturing into a first-class tool designed for Cargo-like ease-of-use, mitigating a number of issues we've encountered while developing wasm-bindgen.

Some of the comments here look to be focusing on the WebIDL story for wasm-bindgen as well, so I think it's probalby also worth diving a bit into that. The web is quite a large and broad platform, and while wasm-bindgen has the various tools for importing functionality from the web it's tedious to do by hand. To make binding of the web platform simple we're developing a web-sys crate which will contain bindings for the entire web platform. These bindings are auto-generated from WebIDL provided by Firefox, so they're easy to modify and easy to update.

Naturally, however, with auto-generated APIs it's not always a perfect match with what you would have written by hand. The goal of web-sys is not to be the most ergonomic binding to the web platform but rather guarantee correct wrappers around the web platform. Just like how most crates are rarely using libc or winapi directly, it's unlikely most crates will directly use web-sys directly. Instead they'll likely use higher-level wrappers with more precise APIs.

It's absolutely critical to web-sys that it's auto-generated. I can't emphasize enough how important this is when binding large foreign platforms. For example libc uses verification and would be a near disaster without it (and auto-generation for us is achieving the same goal as auto-verification). Additionally we're doing what all browser vendors are already doing, generating code from WebIDL (only browsers generate C++, we generate Rust). This means we can leverage host bindings, have bindings for the entire platform, and guarantee correctness all in one go!

(Note that even with that being said, using web-sys is lightyears ahead of using libc, for example. It's all memory safe to start out with!)


To reiterate, I'm not personally too familiar with the details of stdweb. From what I do know, however, (and from the goals I've read online in various places) I would recommend wasm-bindgen. Adoption of WebAssembly and Rust on the web will critically rely on not replacing JS but rather augmenting JS and the existing ecosystem. To that end interop with NPM and the JS ecosystem is a top-priority of the working group as a whole (as mentioned by @fitzgen).

As for some super-nitty-gritty technical details, I've done some local benchmarking to show where the goals of wasm-bindgen really shine. When measuring the overhead of a function call (calling into JS), wasm-bindgen is 10x faster than stdweb. This does not mean that wasm-bindgen is 10x faster, only that it's definitely delivering on its goals of as-if-you-hand-wrote-it performance. Additionally the output for that benchmark has wasm-bindgen clocking in at ~2.5k with stdweb at around 65k, again showing how wasm-bindgen's goal of staying as minimal as possible really shines.

@fitzgen

This comment has been minimized.

Member

fitzgen commented Aug 31, 2018

Oh, also! As alluded to in an earlier comment, js-sys and web-sys aim to be the libc of JavaScript and the Web respectively, a conceivable path forward at some point in time could be for stdweb to stop maintaining its own set of raw bindings, use js-sys and web-sys instead, and focus solely on its higher-level APIs. That is, of course, your own decision to make @koute :)

@Diggsey

This comment has been minimized.

Diggsey commented Aug 31, 2018

@alexcrichton I've used stdweb quite a bit, and have tried to keep up with bindgen etc. as well.

I think stdweb should be viewed as having four components:

  1. The ability to embed raw javascript code from rust, akin to the include! macro.
  2. An interop/runtime layer, which automatically performs marshalling of types back and forth between javascript and rust, plus some syntax sugar via the js! macro to make interpolation of rust values particularly convenient.
  3. A set of rust bindings to various javascript APIs, built using the above two components.
  4. Additional tooling for building/running/testing stdweb-based projects.

What I would like to see is for part 1) to be incorporated as a feature of wasm-pack, so that both stdweb and bind-gen based projects can share a common foundation, and eventually remove the need for stdweb's custom build system (or at the very least have it use wasm-pack internally).

The main thing which stdweb provides which cannot be implemented with wasm-bindgen is the ability to work with references to javascript objects. What is needed is a global map from <integer rust ID> to <javascript reference> that all bindings can share, so that rust code can consistently refer to objects on the javascript heap, and this is one of the things stdweb provides out of the box.

Regarding performance, I fully expect wasm-bindgen to be much faster than stdweb. The interop/runtime layer (2) is not optimised at all right now, and also marshals data in a completely dynamic way, whereas wasm-bindgen can generate code at compile time. Stdweb also has to look up references in this global map, whereas wasm-bindgen does not support references at all.

@koute

This comment has been minimized.

koute commented Aug 31, 2018

As for some super-nitty-gritty technical details, I've done some local benchmarking to show where the goals of wasm-bindgen really shine. When measuring the overhead of a function call (calling into JS), wasm-bindgen is 10x faster than stdweb.

Yes, I'm not surprised by this at all. (: E.g. I haven't added any special casing for primitives, so currently the same codepath is used for types which are not natively serializable (like e.g. Vec<Node> for example) and for those which are, which means that everything is serialized non-natively through heap. And there are some other known inefficiencies. Those are definitely fixable - I just prioritized API features and in general figuring out how a minimal yet high level API would look like without going pedal-to-the-metal with the optimizations.

Oh, also! As alluded to in an earlier comment, js-sys and web-sys aim to be the libc of JavaScript and the Web respectively, a conceivable path forward at some point in time could be for stdweb to stop maintaining its own set of raw bindings, use js-sys and web-sys instead, and focus solely on its higher-level APIs. That is, of course, your own decision to make @koute :)

I'd definitely like to do that! However I still need to be able to emit raw JS snippets from inside of the Rust code.

If wasm-bindgen supported that I could probably port stdweb quiet easily (possibly with some extra elbow grease on wasm-bindgen's side?), even though it might be slightly inefficient, and then convert it to web-sys piece by piece. If I could strip away most of my runtime and rely on wasm-bindgen then perhaps we could have high-er level Web APIs of stdweb with the speed/minimalism of wasm-bindgen. (:

@fitzgen

This comment has been minimized.

Member

fitzgen commented Sep 1, 2018

Don't have time for a proper reply right now, but I will make one on Tuesday.

The main thing which stdweb provides which cannot be implemented with wasm-bindgen is the ability to work with references to javascript objects.

This is very much possible, see JsValue and js_sys::Object and importing custom JS classes. Is this not what you mean? Could you clarify?

@alexcrichton

This comment has been minimized.

Contributor

alexcrichton commented Sep 1, 2018

I think it's probably unlikely for wasm-bindgen itself to grow a js! macro, but it's definitely intended to have enough support to construct such a macro as a library! If wasm-bindgen is missing features to define js! as a library, it's definitely worth having issues for and tracking.

@Diggsey

This comment has been minimized.

Diggsey commented Sep 1, 2018

This is very much possible, see JsValue

Ah, I'm sorry, I didn't see this get added. Yes the internal slab allocator for objects covers what I was thinking of. In that case this is another area where stdweb could move to using the same underlying machinery as wasm-bindgen.

@koute

This comment has been minimized.

koute commented Sep 1, 2018

I think it's probably unlikely for wasm-bindgen itself to grow a js! macro, but it's definitely intended to have enough support to construct such a macro as a library!

Of course I didn't mean that wasm-bindgen should support a fully blown js! macro (as that is probably out of its scope); what I meant is something low-level and minimal like Emscripten's emscripten_asm_const_int.

Ideally we'd have a macro with a prototype of fn(&str, ...) -> T where the first &str argument is the raw JS snippet and the rest are WASM-native arguments.

let result: i32 = js_raw!("return $0 + $1;", 1, 2);
assert_eq!(result, 3);

This could result in the following import on the JS side:

let imports = {
    // ...
    "env": { // Or whatever else namespace.
        // ..
        "snippet_04d4c172203e9e30c9a301547926c07ffe64457f": function($0, $1) {
            return $0 + $1;
        }
    }
}

Is such a macro out-of-scope too?

I guess maybe this could be implemented right now with a procedural macro which would output the snippet into its own file in $crate/target/ and generate a #[wasm_bindgen(module = "/absolute/path/to/$crate/target/snippet_04d4c172203e9e30c9a301547926c07ffe64457f.js")] on Rust's side?

@alexcrichton

This comment has been minimized.

Contributor

alexcrichton commented Sep 2, 2018

@koute I think that's probably covered by rustwasm/wasm-bindgen#224 in the sense that what we'd like to do is have a more principled approach at importing local JS snippets (which isn't supported well today). I think it's reasonable though to consider alternative strategies before that's figured out though! In that sense I don't really know if such a macro is in or out of scope, but we could definitely scope it out and see how it goes!

It's definitely a goal of wasm-bindgen to support a js!-like macro that stdweb uses one day. We may have to figure out what the best way to do that is, but if it can't be done today that's definitely a bug in wasm-bindgen we'd like to fix.

@alexcrichton

This comment has been minimized.

Contributor

alexcrichton commented Sep 4, 2018

Ok so correct me if I'm wrong, but it sounds like this issue is technically resolved in that it sounds like stdweb would like to move towards a future which is built on top of wasm-bindgen and/or wasm-pack. This means that stdweb and all its features will likely continue to exists, only the veneer on the fringes may differ (such as the output of the cargo web subcommand I think?). The wasm-bindgen crate in the meantime needs to grow features necessary to support the js! macro in stdweb.

Does that sound right? If so is this perhaps resolved enough to close?

@NeverGivinUp

This comment has been minimized.

NeverGivinUp commented Sep 4, 2018

Yes! Perfectly resolved for me. Thank you everyone, for taking the time and explaining the philosophy, motivation and technical side of the relation between wasm-bindgen and stdweb. It made very clear to me that I can use stdweb shortterm and profit longterm all the work that goes into wasm-bindgen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment