-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Support async functions for wasm_bindgen(start) #1904
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
Comments
I was surprised to find that: #[wasm_bindgen(start)]
async fn main() { } isn't supported. |
@daxpedda It is supported, you have to do this: #[wasm_bindgen(start)]
pub async fn main_js() { } This is also supported: #[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> { Ok(()) } |
I mean specifically Currently if you put See #3076. |
wasm-bindgen does not support creating binaries, you must always create it as a lib. Binaries have a ton of extra code bloat, increasing the file size of your Wasm for no additional benefit. A wasm-bindgen app that uses |
I am a bit confused, what does that mean exactly?
I am aware of the size difference. There are tons of use-cases where they don't really matter. Personally I'm writing libraries and applications that are supposed to be as cross-platform as possible. In these cases these apps are so big that the bloat generated by having a binary target is something I don't really care about. I wish of course that the bloat could be removed, but sadly Cross-platform examples is another case where it requires extra code and effort by the user to write those, because they do require a Basically what I'm saying is, I know this is a complete convenience thing, it's has disadvantages, but considering it should be quiet simple for |
Yes, that's exactly what I mean. You should not be compiling your wasm-bindgen code as a bin, you should always be compiling it like this: [lib]
crate-type = ["cdylib"] If you use wasm-pack or the Rollup plugin (recommended) then it will always compile it as a lib, as intended.
In that case you should use // lib.rs
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub async fn main_js() {
...
} // main.rs
fn main() {
...
} You already need to use With the above code, it will use And the |
Minor correction. In order to have both a [lib]
crate-type = ["lib", "cdylib"] Now it will work correctly. Here is a zip file containing a working example which contains both Wasm and non-Wasm code: You can use And as a bonus, because it's using Rollup you get a ton of useful features for free:
If you don't use the Rollup plugin, then you'll have to do the above steps yourself, manually. It's a lot more work. |
I know of these Rust-side solutions, what I'm trying to get at here is that there is some room for improvement that don't require additional workarounds. Basically I'm trying to find a solution for Rust users that require zero additional work or reduce it to a minimum, it would be great if that has no overhead, but here we are. In a perfect world we solve the fact that compiling binaries has overhead on the Rust side, that's something I didn't look into. Thank you for your input anyway. |
The zip file I gave you is the minimal amount of work. Compiling to Wasm requires a lot of things, simply compiling as a bin is not enough, additional setup is always required. That's why the Rollup plugin automatically handles 99% of everything, so that it is as easy to use as possible. It is as easy as doing If you choose to not do that, then you'll have to manually build with the right flags, manually call wasm-bindgen with the right flags, download wasm-opt and call it with the right flags, handle That's why we created things like the Rollup plugin, to make the process as easy as possible. It minimizes the amount of work that the user has to do, and it minimizes the amount of extra knowledge that the user needs. The wasm-bindgen CLI was never intended to be used directly by users, it was always intended as a low-level tool which is used by higher-level tools. Think of wasm-bindgen as being similar to rustc, and the Rollup plugin as being similar to cargo. |
I'm gonna try to be as clear as possible. Take your zip file. Try to add an example. You have to add this workaround: #[wasm_bindgen(start)]
pub async fn real_main() {
// your code goes here
}
fn main() { } If |
If you are creating a cross-platform application, then you will have to use // lib.rs
#[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> {
call_init_function().await;
Ok(())
} // main.rs
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
call_init_function().await;
Ok(())
} And the code will be different if you're using async-std, etc. This is not a "workaround", this is just how Rust works. You have two different mains, with different attributes, different return types, different async runtimes. So it makes perfect sense that you will define it as two different functions. If you don't define it as two functions, but as just one When creating cross-platform libraries or applications, it is completely normal to have two implementations: one for Wasm and a different one for everything else. For example: #[cfg(feature = "wasm")]
pub fn foo() -> u32 {
// Wasm implementation goes here
}
#[cfg(not(feature = "wasm"))]
pub fn foo() -> u32 {
// non-Wasm implementation goes here
} This is not because of any limitation in wasm-bindgen, it's just how cross-platform libraries work in Rust in general: you provide a different implementation for each target platform. |
Uhm, let's say you don't want to make a cross-platform example, let's say you want a pure #[wasm_bindgen(start)]
pub async fn real_main() {
// your code goes here
}
fn main() { } is longer then this: #[wasm_bindgen(start)]
async fn main() {
// your code goes here
} As for cross-platform: #[wasm_bindgen(start)]
pub async fn main_js() {
call_init_function().await;
}
#[tokio::main]
async fn main() {
call_init_function().await;
} is longer then this (at least less duplicate code): #[cfg_attr(target_arch = "wasm", wasm_bindgen(start))]
#[cfg_attr(not(target_arch = "wasm"), tokio::main)]
async fn main_js() {
call_init_function().await;
}
This is not true at all in my experience, certainly this applies to a lot of libraries and applications. But there are tons of libraries that provide abstractions that make it seamless for users not to have two implementations. Really the example you have posted is great, I'm not sure why there is such a big misunderstanding here. Your arguments aren't wrong, I agree with almost all of them. I am arguing that there is an improvement that can be made here. It's small, but it does exist. If I understand your argument correctly, you are trying to tell me that there are ways to solve this without the improvement I'm suggesting here. I completely agree. Again, only because there are other ways, doesn't mean we shouldn't try to improve. Only because cross-platform code is inevitable, doesn't mean we shouldn't try to minimize it. |
If you compile it in the correct way (using wasm-pack or the Rollup plugin), then you don't need the #[wasm_bindgen(start)]
async fn main_js() {
// your code goes here
} The only reason why you added in the
That only works if you don't have a return type, wasm-bindgen must support So how will your suggestion work with a different return type?
Yes, that's because they provide 2+ implementations. So if you want a nice seamless experience for your library / application, then you must do the same. And in any case, I think your code is far less readable than just having two separate functions. Those attributes are a mess to understand. Having separate functions also makes it very easy to support different runtimes: // lib.rs
#[wasm_bindgen(start)]
pub async fn main_js() -> Result<(), JsValue> {
call_init_function().await;
Ok(())
} // main.rs
#[cfg(feature = "tokio")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
call_init_function().await;
Ok(())
}
#[cfg(feature = "async-std")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
async_std::task::block_on(async {
call_init_function().await;
Ok(())
});
} And in particular, it is idiomatic for async-std to use Your code is very brittle, unidiomatic, doesn't support In addition, your code does not work with wasm-pack, the Rollup plugin, the Webpack plugin, or other Wasm tools. But if you structure things in the correct idiomatic way, then everything works perfectly. |
Your suggestion is much more than just adding in support for Rust already has a reputation of producing large filesizes, we don't want to make that reputation worse by supporting bin crates until that rustc bug is fixed. You are trying to do things in a very weird, and unsupported way, and so of course you're going to run into problems. If you simply compile things in the correct way (the way that wasm-bindgen and the Wasm tools have been designed to work), then there is no issue. Like I said before, users are not supposed to be using the wasm-bindgen CLI, it is low-level and for internal use. You will have a much easier and happier time if you use the correct tools (wasm-pack, or the Rollup plugin). |
I don't think compiling binaries as a lib is the "correct" way. If you were to build a pure WASM binary, say with Trunk, it's not unreasonable to have a binary crate (side note: all Yew examples and trunk examples are structured this way). There are certainly valid cases where It's better to support this use case than not. Adding a
That's true in a world in a world where Trunk doesn't exist. There's no right or wrong way to do things. Rust's philosophy is to be unopinionated and we should stick to that. If one wants to drop down to wasm-bindgen CLI and use it, all the power to them |
I have specifically tried to avoid arguing about this, which is why I mentioned Cargo examples. Cargo examples can't be simply made libs, it's unsupported, it will create compile errors if you don't specify a
An error.
Winit is a good example of a huge library that is surprisingly seamless. Wgpu is not completely seamless, but the cross-platform code required to tack on is quiet minimal in comparison. Again, this is about best-effort, not about perfect.
Sure. I can agree with that actually.
I don't understand this argument. Sure, that's great, but you don't always need different runtimes.
I don't get at all what you are trying to say here.
I don't understand how it's brittle. It doesn't have to support
I don't use wasm-pack or Rollup plugins or any of the other stuff you mentioned. I use I understand if the way you use |
I'm sorry, but you're wrong. Even for creating applications, you are supposed to compile wasm-bindgen with wasm-pack (and the Rollup plugin / Webpack plugin) are the officially supported ways to compile Rust to Wasm. Trunk is not officially supported, it is a third-party system which does its own (unidiomatic) things, it is not compatible with the wider Rust Wasm ecosystem.
Every use case can use
We want to push users toward doing the correct thing. If users start compiling bin crates, they will complain about large file sizes (yes they have complained in the past).
Trunk has a lot of issues, it is not production ready. And it also produces very inefficient unoptimized code. I think it's unfortunate that some people are using Trunk, because then they blame Trunk problems on Rust Wasm, even though it's a problem with Trunk only. I have seen many people saying that "Rust Wasm produces huge files, Rust Wasm is super slow", even though that's not true, it's a problem specifically with Trunk and Yew.
It's the complete opposite, Rust is an extremely opinionated language. There is only one package manager, one build system (cargo). |
It is not possible to run Wasm code directly with Cargo, so things like In the long run it would be great to unify them, but that requires a lot of changes in rustc and cargo, it will not happen anytime soon.
So you think that wasm-bindgen should support That doesn't sound very good to me, that sounds incredibly unintuitive and inconsistent.
You don't seem to understand my point. You keep mentioning other libraries like as if it disproves my point, but in actuality you are reinforcing my point. My point is that all of those libraries and applications provide 2+ implementations. That's why they are seamless. You are creating a library / application, therefore your code will need to provide 2+ implementations. That's normal. I'm not talking about you using other libraries, I'm talking about your code. Having a second (tiny)
If you are creating a library or application to be used by other people, then having that option is good to have, it benefits your users. So why would you choose an implementation which makes that impossible? And also bloats up your filesize for no reason? There is so little benefit to your suggestion, but so many problems and limitations.
Even if you don't, your users will, because those are the officially supported and idiomatic ways to use Rust Wasm. And by using the official tools, you make life much easier on yourself, you don't need to re-implement their features, and the tools are automatically kept up-to-date, so you don't need to worry about breaking changes. A lot of work has gone into making the official tools good, third-party tools tend to be much more limited in features, and produce worse code.
It's not about "how I use it", I'm a former Rust Wasm Core team member, I've contributed heavily to wasm-bindgen. I understand very well how wasm-bindgen works, and how it is intended to be used. wasm-bindgen was never intended to be used by users, wasm-pack (and the plugins) were always the official tool that users are supposed to use. If you deviate from that, you will have issues, and those issues are not the fault of wasm-bindgen. |
I'm gonna stop arguing about the other points, I think we are not going to reach any form of conclusion on any of them. I think the big point of contention here is that in your opinion I will say that I wish I could use This doesn't mean I believe opinionated libraries or applications don't make sense, I'm not making blanket statements here. |
What issues? I've used both trunk and wasm-pack and have not experienced any issues. I just tested the binary size and the trunk binary was smaller in my test. You can test that by:
The binary size in debug mode, where rustc produces huge binaries, doesn't matter and should not be used as a point of comparison.
Is the existence of frameworks/libraries like Yew, Seed, Dioxus incorrect? These frameworks/libraries create binaries which are the entire web application. That is completely different from creating libraries - using I do not believe we are going to reach a conclusion on this and calling each other "wrong" or saying that one and only one way of doing things is the correct way is productive so I'm going to end this discussion here. |
That's a very serious issue! If you're having problems with the official tools, then we want to know about it so we can fix it! What issues have you had?
As I said before, the issue isn't really with wasm-bindgen. We would like to support bin crates, but it requires changes/fixes in rustc. It's an issue with rustc itself. Similarly, we would like for everything to work seamlessly with cargo (such as |
Personally back then I had issues with getting
I agree of course, but I'm getting the impression that you have a very non-compromising stance. In the meantime people like me just don't use the "official" way of doing things to get by. This is what I'm seeking support for. |
JS tools like Rollup and Webpack have had an absurd amount of effort put into them. Trunk is very new, very immature. I hope that one day it will become great, but right now it's just missing too many features that you need in production.
I checked out that git repo and ran Trunk on the router example. I immediately got this error:
So instead I tested it out on my own example: https://github.com/Pauan/rust-dominator/tree/master/examples/counter With Trunk I got this result: With the Rollup plugin I got this result:
You are very mistaken. Tools like wasm-pack and the plugins are not only for creating libraries, they are for applications too. In fact the Rollup plugin has explicit support for being used as the entry point in Rollup, which is specifically for applications. The Rollup and Webpack plugins are primarily used for applications, not libraries.
And that is also what wasm-pack and the plugins do, they can also create entire web applications, they are not only for libraries. I personally use the Rollup plugin for all of my web applications. I've even used it to create Chrome / Firefox extensions (which is something that Trunk cannot do).
No, I never said that. I said that Trunk has a lot of issues and produces extremely unoptimized code. If you compile Yew / Seed / Dioxus in the official way (e.g. using the Rollup plugin) then you will get a much better result. |
Yes, wasm-pack has in the past had issues with wasm-opt. That has been completely fixed by the Rollup plugin. Unit testing is very complicated, it's not very well supported by wasm-bindgen right now, unfortunately. I actually suggest writing your unit tests with something like Karma or Jest or Playwright or something like that. I think it should be possible to create a system which would convert
Yes, there was some bad personal drama that happened with wasm-pack. I believe most of the issues have been fixed, and the Rollup plugin is well maintained and does not have any known issues.
Imagine that you encountered a bug with cargo. Instead of reporting that bug, you decided to create your own custom build tool which calls rustc manually. Then you encounter some issues, because you're using rustc in very strange ways that the Rust developers never intended. So you go onto the Rust bug tracker and file a bug report asking for them to support your specific way of using rustc. Of course the Rust developers will be very confused, and they're going to ask why you're not using cargo, because cargo is what users are supposed to use. Everything is designed around cargo. And instead of changing rustc to support your weird way of doing things, the Rust developers would much rather just fix the cargo bug which was preventing you from using cargo in the first place. The situation is very similar with wasm-bindgen. Have you tried using the zip that I gave you earlier? I have put in quite a lot of effort into making sure that the Rollup plugin is fast and bug-free. |
For some perspective, I think the use-cases that you have are just completely different then what, for example, I have. Like more then half of the things you listed in response to hamza1311 is stuff that I don't require and don't want to pay the cost for. I'm most definitely not going to use Rollup unless somebody puts in the effort to convince me of this, just taking a short glance at it makes it very unattractive to me. The same really applies to most of the tools you talked about and the zip file you linked. This is why I find these tools unattractive:
As I mentioned before, I think your argument about Cargo was poor and I honestly consider it being done in bad faith. I use Cargo because it works for me, if it wouldn't I would fork it and use that. I don't use As you can see here I'm quite active GitHub, I write up issues and provide PRs almost on a daily basis. But this is really irrelevant. I'm privileged to have this kind of time and motivation, others don't, they just fork stuff or look for alternatives when they need it. There is nothing wrong with that in my opinion. I consider it also alright that you don't share this opinion, clearly you have a vision for |
@daxpedda I can definitely sympathize with the desire to do everything in Rust. Before wasm-bindgen existed, I contributed to cargo-web, which was an all-Rust toolchain for compiling Rust to Wasm. It was a very nice experience. Originally our plan was to do a lot more in Rust. But the unavoidable fact is that you are compiling to Wasm + JS. Although I personally quite dislike the JS ecosystem (and JS tooling in general), they are currently the best tools available for the compilation and optimization of Wasm/JS. An incredible amount of effort has gone into the creation of JS tooling, the Rust Wasm tools can never compare to it. So if you want to do anything more complicated than just "compile Rust to Wasm and run it", then you're going to need JS tools. JS tooling is basically mandatory in any sort of production setting. You can only avoid JS tools in very simple use cases. And so after examining the available options, the design that we settled on was to use wasm-bindgen to handle the Rust side of things, and to use JS tools to handle the JS side of things. That means each tool can do what it does best, and so users get the best end result. You talk about "supporting every use case", but that's exactly what we are doing. There are tons and tons and tons of use cases that just cannot be supported by simple tools like Trunk (or custom build systems). By leveraging the existing JS tools we can support those use cases basically "for free". Unlike your custom build system, which is only for you, we have to support a lot of people, and a lot of different use cases.
That's not a bad position to take, but I think bloat in build tools is far less of a problem compared to bloat in the actual compiled code. Cargo and the Rust stdlib have a ton of features that I've never used, but I wouldn't call them "bloat", because none of that bloat makes it into the final compiled code. And in many cases that extra "bloat" in the build tools means smaller or faster compiled code, which is ultimately a good thing for the users.
I don't really understand that, the situations are almost identical, it's a very good analogy. rustc is a low-level tool that just compiles individual Rust code, it doesn't do anything fancy. cargo is a high-level tool that handles package management, building, optimization, etc. basically it's the glue code that makes everything work. wasm-bindgen is a low-level tool that just compiles Rust Wasm, it doesn't do anything fancy. The Rollup plugin is a high-level tool that handles package management, building, optimization, etc. basically it's the glue code that makes everything work.
When you just need to Get Things Done, that's fine, but reporting issues helps everybody, not just you. If the issue can be fixed at the source, then it can benefit millions of people. Economies of scale are important. That's how humanity progresses forward, not by each person reinventing the wheel, but by people working together to make something better. That's why we don't have a dozen different forks of cargo. Having one official way to do things is really helpful (whether it be cargo, rustdoc, rustfmt, etc.). That's the entire philosophy of Rust. |
We currently support
async
functions in a variety of places, including in unit tests and inpub
functions exposed to JS.When writing applications, it's common to have a single "root"
async
function which kickstarts everything. Right now you have to do something like this:But it would be great if we could instead do this:
The semantics is that it would generate a new shim function which calls
spawn_local
and logs theErr
.It should support a return value of either
()
orResult<(), JsValue>
.The text was updated successfully, but these errors were encountered: