-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Implement extern "C" async functions. #2196
Conversation
Thanks for this! Before diving too much into the code though I think it'd be good to try to sort out the story for a generic return type here. For example it'd be great if |
The automatic conversion of the return type of a promise has two separate issues. The first one is that JS promises are always fallible, there are always the Currently a The second issue is the type of the I like "c". We could implement An extra issue is the conversion of the error type. I think that a |
Ok, I'm correcting myself. Any JS function may fail, just by doing About how to handle the conversion errors, I just tried a few random normal functions returning the wrong value and sometimes they return default (0), sometimes they panic. I think we can do more or less the same. I'll post some code not to be merged, just to get feedback (it misses docs and proper test cases). BTW, do you prefer incremental commits or rebases in PRs? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! I left some comments inline.
crates/backend/src/codegen.rs
Outdated
abi_ret = quote! { js_sys::Promise }; | ||
let future = quote! { wasm_bindgen_futures::JsFuture::from(#ret_ident).await }; | ||
convert_ret = if self.catch { | ||
quote! { Ok(<#ty as wasm_bindgen::FromJsValue>::from_js_value(#future?)) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this perhaps use TryFrom
instead of a new trait? (now that I think about it).
It might be cool if this could leverage existing TryFrom
implementation (and add a few here too), and that way if the conversion from JsValue
fails we've got a way to return the error (it's like as if the future failed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I tried that but it didn't go well. I wanted these blanket impls:
impl<T: JsCast> TryFrom<JsValue> for T { ... }
impl<T: TryFrom<JsValue>> TryFrom<JsValue> for Option<T> { ... }
But these resulted in a lot of conflicting implementations.
Maybe this trait could be leveraged into JsCast
, but a new trait looked cleaner to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I don't think we'll be able to do some blanket impls, but we could add specific impls perhaps?
In general I'm just hoping that we can propagate an error for a failed conversion.
In general though this seems like a really tricky problem. If you'd prefer we can just stick to Result<JsValue, JsValue>
like Promise
and call it a day.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, returning Result<JsValue, JsValue>
was my first option, but it looked poor. The user can just map
to do the casting, but it breaks ergonomics.
And at first I though that I could coerce the error in the conversion to that Err(_)
but it did not work either. On one side, because a cach function is not required to return a JsValue
but any type that implements From<JsValue>
, because of the ?
operator. But a cast failure would be a js_sys::TypeError
, and that conversion may not be possible.
On the other side, wasm_bindgen
does not actually depend on js_sys
. Adding that dependency just for this seemed overkill.
The fancy solution could be making JsFuture
generic and doing ABI magic in the JS side, just like regular functions do. But that is way out of my league.
Anyway, I'm not convinced with the failing conversions argument. If I write a regular JS function and import it with a wrong prototype it will just panic or return a default value, depending on the types, (I think these situations are not documented):
function foo() { return 42; }
#[wasm_bindgen]
extern "C" {
fn foo() -> String; //undefined?
}
Going async would be quite similar:
async function foo() { return 42; }
#[wasm_bindgen]
extern "C" {
async fn foo() -> String; //undefined!
}
8f9c541
to
6fafe5e
Compare
Hi again! I have rewritten this PR and rebased to a single commit (I hope you don't mind).
If the user wants a different type, they have to do the casting themselves and dealing with the errors. This way you can write 4 kinds of extern async functions, based on the return type, that I think they are quite useful.
There are still the test cases missing, and the doc changes need a reword... |
That all sounds great to me! If you want to add some tests and update the docs I think this should be good to go! |
6fafe5e
to
4dc5827
Compare
It converts a JS Promise into a wasm_bindgen_futures::JsFuture that implements Future<Result<JsValue, JsValue>>.
4dc5827
to
98d7919
Compare
Hi! I rebased yet again, this time with more or less proper docs and test cases. While writing the test cases I noticed a bug when the functions is declared to return nothing:
because the JS-glue generator, seeing that it returns nothing, skipped the function. But I need the JS-glue to be able to return the Future, so I added a line in |
All looks good to me, thanks again for this! I think rustfmt needs to run again but otherwise should be good to go? |
Oh, I ran I'm quite happy with this version, but sorry, I don't know the procedure... do you want me to do the |
Oh that may be a bug in rustfmt then, mind filing an issue upstream and adding (CI is failing and we gotta get it green somehow) |
Add #[rustfmt::skip] to the tests/wasm/futures.rs because it removes the async from extern "C" blocks.
Green light (I hope)! Issue created at rustfmt. |
👍 Thanks! |
It converts a JS Promise into a wasm_bindgen_futures::JsFuture that
implements Future<Result<JsValue, JsValue>>.
Fixes issue #2189. Now you can write:
And the generated code will do the conversion automatically.
Currently there is the limitation that the return value must be
Result<JsValue, JsValue>
, because that is theOutput
type of awasm_bindgen_futures::JsFuture
.I've added a custom error message if the user does not specify a return type, and let the compiler complaint if they write a different type.
I've tried doing automatic conversions to be able to return
Result<i32, JsValue>
for example, but I could make it work for every case. I think that these kind of conversions (unboxing?) is never done in the generated code. Maybe ifwasm_bindgen_futures::JsFuture
were generic on the return type...I noticed that one of the test cases used a manually-written promise, so I abused a bit and converted it to the new format. I don't know if it is appropriate or if more tests are needed.
About the documentation, writing English is not my best talent, so feel free to rewrite it at will.