-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Throw a JavaScript Error instead of throwing a literal #1735
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
Where are you seeing this behavior? As far as I can see, wasm-bindgen does use |
I'm using wrangler (the Cloudflare tool) based on their simple rust template.
Shows the result:
None of the results from wasm_bindgen are wrapped in Error. |
Okay, I took a look at your code: #[wasm_bindgen]
pub fn return_string_error() -> Result<String, JsValue> {
Err(JsValue::from_str("Hello, wasm-worker!"))
} Naturally if you return a use js_sys::Error;
#[wasm_bindgen]
pub fn return_string_error() -> Result<String, JsValue> {
Err(Error::new("Hello, wasm-worker!").into())
} As for Instead you should do something else, such as using |
In JavaScript, the only thing that should be thrown is an I agree that what's returned by On the other hand, I can see that the behaviour matches JavaScript in allowing value types to be thrown. What I'm asking for in this feature request, is for wasm-bindgen to make it difficult to throw value types, against good JavaScript practice, by instead wrapping them in As for
Typical behaviour in JavaScript is to return The theme of both of these issues is that they add extra impedance to the development process by forcing the developer to manually add boilerplate scaffolding. |
P.S. In my real code I'm returning Vec so can't return it through a JsValue. |
I don't think that's a good idea:
You can always return Naturally if you use a type like
What is the issue with that? You can return an fn foo() -> Result<Option<String>, JsValue> {
// ...
} ...or you can return a more meaningful error and then convert it at the boundaries if you need custom behavior: enum MyError {
SomeError,
SomeOtherError(Error),
}
fn foo_() -> Result<String, MyError> {
// ...
}
#[wasm_bindgen]
pub fn foo() -> Result<JsValue, JsValue> {
match foo_() {
Ok(s) => Ok(JsValue::from(s)),
Err(MyError::SomeError) => Ok(JsValue::undefined()),
Err(MyError::SomeOtherError(e)) => Err(e),
}
} ...or you can return a fn foo() -> Result<JsValue, JsValue> {
if some_condition() {
Ok(JsValue::from_str("foo"))
} else {
Ok(JsValue::undefined())
}
} Using |
If it's not possible to convert something to For
|
I think throwing value types instead of wrapping them in
That's a showstopper if it prevents the stack frame from being useful... but does an Thanks for your suggestion about returning both success and failure with
My earlier code, which throws
I'd really prefer to return a This might be a general rust question, but when returning the binary data, is there any way to tell which operations result in a clone via memcpy and which simply reference the original memory block? If not, at what point does wasm-bindgen copy the data? Similarly, if I return using a view:
does the original memory address get returned out of the wasm-bindgen generate JavaScript wrapper? (If that's the case I understand it would be more efficient, but would blow up when the wasm memory block is reallocated.) |
JavaScript does not do auto-wrapping, if you do Why do you find it surprising that wasm-bindgen doesn't auto-wrap value types, but you don't find the same behavior in JavaScript surprising?
There is work being done on implementing source maps and DWARF (or similar) for wasm, which will give full stack traces, breakpoints, etc. There is no need for a specific calling convention or anything like that. It works on the same principles as JavaScript source maps, so it can work for any language compiled to wasm. Right now you can use source maps to compile any language (e.g. Clojure) to JavaScript, and you will get correct stack traces, breakpoints, you can debug the original Clojure source code in the browser, etc. The same will be true for wasm.
Stylistically, I would recommend doing this instead: #[wasm_bindgen]
pub fn lookup(name: &str) -> Result<Option<Uint8Array>, JsValue> {
match binary_data_collection.get(name) {
Ok(bytes) => Ok(Some(Uint8Array::from(bytes.as_ref()))),
Err(_) => Ok(None),
}
} And since you're not actually throwing any errors, you can simplify it even further: #[wasm_bindgen]
pub fn lookup(name: &str) -> Option<Uint8Array> {
let bytes = binary_data_collection.get(name).ok()?;
Some(Uint8Array::from(bytes.as_ref()))
}
Why is that? Using
There are two types of data: Rust data types and JS data types. JS data types never make a copy, they are sent as a The data types created by wasm-bindgen are JS data types, including On the other hand, Rust data types are different, those require a full copy when sending to/from JS. So when you send a So when you convert a After you've created the The exception is things like
|
Surprising is too nice a word for the behaviour in JavaScript. :-D When I'm implementing a wrapper I aim for it to feel native and encourage the user to fall forward to good practice (such as throwing an exception wrapped in Error). So, it's more about expecting modern wrappers to make it easiest to follow current JavaScript good practice instead of making it easiest to follow what is now discouraged behaviour.
That is nice. Thanks!
LOL because I've used binary wrappers in the past that could return something like Thanks for pointing it out. So, for performance (in cases like mine where the Vec won't change), I'd be better flattening T and using a few functions, each returning a supported array type, rather than a single function returning a
Thanks. That's very useful information.
Point 2 surprises me. I thought the function returns an ofset into the the wasm memory containing the ofset of the return value which contains the start and length of the memory block. From what you've said I assume that it's on the stack. Wouldn't the JavaScript side copy the start/length and convert it to a slice into the wasm memory? That wouldn't be affected by the stack frame going away. |
wasm-bindgen isn't a high level wrapper, it's designed to be very lightweight and low-level. The idea is that high level wrappers are provided by other crates, like gloo. As for encouraging good behavior, I don't think auto-wrapping is the right way to address that. Instead, I have a better idea: Right now wasm-bindgen APIs return It would also be possible to change the This is using the static type system to force good behavior. This follows Rust's philosophy much better than allowing bad behavior and trying to fix it after the fact (which is your idea).
Wasm has a single large When you use a view function like new Uint8Array(wasm.module.buffer, ptr, len) The This is what makes the But the So if the To prevent that, in JavaScript you must copy the bytes before the |
Sounds like a good solution. Would you prefer to create a new issue for it, or take this one over?
That would apply if the |
Ok reading this over, sounds like this has largely been handled! @aqueenan mind opening follow-up issues for any other action items? |
New thread created: #1742
I don't think that's quite true: {
let x = Box::new(vec![0, 1, 2]);
unsafe { Uint8Array::view(&*x) }
} In this case the Also, the So I think it's best to think of it in terms of lifetimes, since that's how the Rust compiler/optimizer/type system itself does things. |
Motivation
It is considered good practice to only throw the Error object itself or an object using the Error object as base objects for user-defined exceptions as described at https://eslint.org/docs/rules/no-throw-literal.
Proposed Solution
Instead of
throw x;
usethrow new Error(x);
(except forundefined
,null
andSymbol
as raised in #1734).The text was updated successfully, but these errors were encountered: