Objects
Most types of data in JavaScript are considered objects. (In fact, everything that isn't a primitive type is an object type.) In Neon, all object types implement the Object
trait, which allows you interact with an object's properties.
The JavaScript Object
class, sometimes referred to as "vanilla objects" or "simple objects," is one of the most common ways to construct objects in JavaScript, and is available to Neon through the JsObject
type.
#
Creating ObjectsThe Context::empty_object()
method creates a new JsObject
:
let obj: Handle<JsObject> = cx.empty_object();
#
Getting PropertiesThe Object::get()
method accesses a property of an object at runtime:
// Create an empty object:let obj: Handle<JsObject> = cx.empty_object();
// Get the `toString` property of the object:let prop: Handle<JsValue> = obj.get(&mut cx, "toString")?;
Notice that this example extracts the toString
property from an empty object, which will typically be inherited from the object's prototype chain.
#
Setting PropertiesThe Object::set()
method sets a property of an object at runtime:
let obj = cx.empty_object();let age = cx.number(35);
obj.set(&mut cx, "age", age)?;
#
Converting Rust data to JavaScriptHere is a simple example of converting a Rust struct
to a JavaScript object. First, let's define a Rust type describing, say, about a book in a library catalog:
struct Book { pub title: String, pub author: String, pub year: u32,}
To copy a Book
into a JavaScript object, we'll define a conversion function. To make it idiomatically Rusty, let's define it as a method of the Book
type, so that callers of our API can use a pleasant method call syntax:
let obj = book.to_object(&mut cx)?;
First let's look at the signature of Book::to_object()
, which we define as a method using Rust's impl Book
syntax and a &self
parameter:
impl Book { fn to_object<'a>(&self, cx: &mut FunctionContext<'a>) -> JsResult<'a, JsObject> { // ... }}
This is our first example using a lifetime annotation 'a
. This allows the Rust compiler to ensure that our code never accidentally makes an unsafe reference to JavaScript values managed by the Node runtime. Specifically, this signature tells Neon that the result object returned by this function (which has lifetime 'a
) is managed by the runtime context that was passed in as an argument (which also has that same lifetime 'a
).
If you've never seen lifetimes before or are not yet confident using them, don't worry! For now, you can use this code as a template, and know that the Rust compiler will keep you safe.
Now here is the full implementation:
fn to_object<'a>(&self, cx: &mut FunctionContext<'a>) -> JsResult<'a, JsObject> { let obj = cx.empty_object();
let title = cx.string(&self.title); obj.set(cx, "title", title)?;
let author = cx.string(&self.author); obj.set(cx, "author", author)?;
let year = cx.number(self.year); obj.set(cx, "year", year)?;
Ok(obj) }}
Let's walk through the implementation. First, it constructs a new empty JavaScript object, which will serve as the result, converts each of the fields to a primitive type and sets the relevant property on the object to its value. Finally, the method returns the new object, wrapped in an Ok
value to signal success.
One thing worth noticing about this function is that it doesn't use anything specific about the FunctionContext
type other than the generic methods of the Context
trait. To make our function even more powerful, we can make it generic and accept any implementation of Context
:
impl Book { fn to_object<'a>(&self, cx: &mut impl Context<'a>) -> JsResult<'a, JsObject> { // same as before... }}
This allows us to use our method in more places, such as with a ModuleContext
:
#[neon::main]pub fn main(mut cx: ModuleContext) -> NeonResult<()> { let book = Book { title: "Chadwick the Crab".to_string(), author: "Priscilla Cummings".to_string(), year: 2009, };
let obj = book.to_object(&mut cx)?; cx.export_value("chadwick", obj)?; Ok(())}