Skip to content

Latest commit

 

History

History
288 lines (209 loc) · 9.08 KB

MIGRATION_GUIDE_0.10.md

File metadata and controls

288 lines (209 loc) · 9.08 KB

Neon 0.10 Migration Guide

With the API improvements of Neon 0.10, a few backwards-incompatible changes have been introduced. This migration guide provides all the information you should need to make the (small!) code changes required for upgrading to Neon 0.10.

We have made an effort to minimize these changes, and believe they should typically lead to simpler, clearer code. If you run into any trouble migrating your code to 0.10, please file an issue or reach out for help on the community Slack!

Major changes

Object property access is generic

To improve the ergonomics of the common case of downcasting the result of property access to a specific type, the signature of Object::get() has been changed to have a generic return type. This means that code that follows Object::get() with .downcast_or_throw() or .downcast::<V>().or_throw() no longer needs to do so.

Before:

obj.get(&mut cx, "name")?.downcast::<V, _>(&mut cx).or_throw(&mut cx)?

After (option 1):

obj.get::<V, _, _>(&mut cx, "name")?

After (option 2):

let v: Handle<V> = obj.get(&mut cx, "name")?

Since Object::get() throws an exception when types don't match, use the new Object::get_value() or Object::get_opt() methods for cases that accept a wider range of allowable types.

Before:

let field: Option<Handle<JsBoolean>> = obj
    .get(&mut cx, "optionalField")?
    .downcast(&mut cx)
    .ok();

After:

let field: Option<Handle<JsBoolean>> = obj.get_opt(&mut cx, "optionalField")?;

Before:

let length = obj.get(&mut cx, "length")?;
let length: Option<Handle<JsNumber>> = if length.is_a::<JsNull, _>(&mut cx) {
    None
} else {
    Some(length.downcast_or_throw(&mut cx)?)
};

After:

let length = obj.get_value(&mut cx, "length")?;
let length: Option<Handle<JsNumber>> = if length.is_a::<JsNull, _>(&mut cx) {
    None
} else {
    Some(length.downcast_or_throw(&mut cx)?)
};

Layered APIs for function calls

The API for calling (or constructing, i.e. the Neon equivalent of the JavaScript new operator) a JS function has been split into two layered alternatives. The existing .call() and .construct() functions are now a lower-level primitive, which no longer offers automatic downcasting of arguments or result. But Neon 0.10 now offers a more convenient API for calling functions with an options object and method chaining, with the introduction of the .call_with() and .construct_with() methods.

Example: Calling a function

Before:

let s: Handle<JsString> = ...;
let n: Handle<JsNumber> = ...;
let args: Vec<Handle<JsValue>> = vec![s.upcast(), n.upcast()];
let this = cx.global();
f.call(&mut cx, this, args)

After (low-level API):

let s: Handle<JsString> = cx.string("hello");
let n: Handle<JsNumber> = cx.number(42);
let this = cx.global();
f.call(&mut cx, this, [s.upcast(), n.upcast()])

After (high-level API):

f.call_with(&cx)
 .args((cx.string("hello"), cx.number(42)))
 .apply(&mut cx)

Example: Constructing with a function

Before:

let s: Handle<JsString> = ...;
let n: Handle<JsNumber> = ...;
let args: Vec<Handle<JsValue>> = vec![s.upcast(), n.upcast()];
f.construct(&mut cx, args)

After (low-level API):

let s: Handle<JsString> = ...;
let n: Handle<JsNumber> = ...;
f.construct(&mut cx, [s.upcast(), n.upcast()])

After (high-level API):

let s: Handle<JsString> = ...;
let n: Handle<JsNumber> = ...;
f.construct_with(&cx)
 .args((s, n))
 .apply(&mut cx)

Idiomatic typed arrays

Neon 0.10 replaces the previous typed array API with a more idiomatic API. JavaScript typed arrays (e.g. Uint32Array) can be represented with corresponding Rust types (e.g. JsTypedArray<u32>), allowing easy access to their internals as Rust slices.

Example: Reading a buffer

Before:

let b: Handle<JsArrayBuffer> = ...;
{
    let guard = cx.lock();
    let data = b.borrow(&guard);
    let slice = data.as_slice::<u32>();
    ...
}

After:

let b: Handle<JsTypedArray<u32>> = ...;
let slice = b.as_slice(&cx);

Example: Reading and writing buffers

Before:

let b1: Handle<JsArrayBuffer> = ...;
let b2: Handle<JsArrayBuffer> = ...;
{
    let guard = cx.lock();
    let data1 = b1.borrow(&guard);
    let data2 = b2.borrow(&guard);
    let slice1 = data1.as_slice::<u32>();
    let slice2 = data2.as_slice::<u32>();
    ...
}

After:

let src_buf: Handle<JsTypedArray<u32>> = ...;
let dst_buf: Handle<JsTypedArray<u32>> = ...;
{
    let lock = cx.lock();
    let src = src_buf.as_slice(&lock).unwrap();
    let dst = dst_buf.as_mut_slice(&lock).unwrap();
    ...
}

Example: Casting buffer types

Previous versions of Neon came with a special datatype for casting the data of an ArrayBuffer, but this had incomplete handling of unaligned data and is deprecated in Neon 0.10. Crates like bytemuck can be used for casting buffer slices.

Before:

let b: Handle<JsArrayBuffer> = ...;
{
    let guard = cx.lock();
    let data = b.borrow(&guard);
    let u8_slice = data.as_slice::<u8>();
    let f32_slice = data.as_slice::<f32>();
    ...
}

After:

use bytemuck::cast_slice;

let b: Handle<JsArrayBuffer> = ...;
let u8_slice = b.as_slice(&cx);
let f32_slice: &[f32] = cast_slice(u8_slice);

Minor changes

Uncaught errors in tasks

Starting with 0.10, uncaught errors (whether Rust panics or JavaScript exceptions) in a task are now captured and reported to Node as an unhandledRejection event. Previously, an uncaught JavaScript exception would be ignored. To handle uncaught exceptions, either wrap the body of a task with try_catch or, alternatively, capture all uncaught rejections in Node with process.on("unhandledRejection, (err) => {}).

Before:

cx.task(|| "hello".to_string())
  .and_then(|mut cx, _| {
      cx.throw_error("ignore me")
  })

After:

cx.task(|| "hello".to_string())
  .and_then(|mut cx, _| {
      let _ = cx.try_catch(() {
          cx.throw_error("ignore me")
      });
      Ok(())
  })

Channel::send returns JoinHandle

The Channel::send() method now returns a JoinHandle type instead of (), allowing code to optionally and conveniently block on the result with JoinHandle::join(). Non-blocking code should usually work with little to no change; sometimes a semicolon may be needed to explicitly ignore the JoinHandle.

Before:

fn helper<'a, C: Context<'a>>(cx: &mut C, ch: Channel) {
    ch.send(...)
}

After:

fn helper<'a, C: Context<'a>>(cx: &mut C, ch: Channel) {
    ch.send(...);
}

Value types aren't cloneable

Previous versions of Neon had a safety bug in allowing types that implement the Value trait to be cloned. This has been fixed in Neon 0.10. It should be rare for code to ever need to clone a value. In most cases where this may be occurring, a Handle or reference (&) should work. For longer-lived use cases such as storing a value in a Rust static variable, use a Root.

Before:

fn helper<'a, C: Context<'a>>(cx: &mut C, s: JsString) -> ... {
    ...
}

After:

fn helper<'a, C: Context<'a>>(cx: &mut C, s: Handle<JsString>) -> ... {
    ...
}

Context methods now all take &mut self

Context methods such as execute_scoped, compute_scoped, and lock all take &mut self instead of the previous &self. This was necessary for safety and is more consistent with other Context methods. In normal usage, this should not require code changes.

Throw is unconstructable

The Throw type can no longer be explicitly constructed, and cannot be shared across threads. This makes it harder to accidentally mis-report a NeonResult value by reusing a stale Throw value. Existing code that uses Throw for application-specific use cases should use a custom struct or enum instead.