# Value System and Type Conversion Guide
This document explains the core value types, traits, and conversion APIs in the Rong JavaScript engine binding layer.
## Table of Contents
- [Overview](#overview)
- [Quick Start](#quick-start)
- [Type Hierarchy](#type-hierarchy)
- [Core Traits](#core-traits)
- [JSValueImpl](#jsvalueimpl)
- [FromJSValue](#fromjsvaluev)
- [IntoJSValue](#intojsvaluev)
- [JSTypeOf](#jstypeof)
- [JSObjectOps](#jsobjectops)
- [Value Types](#value-types)
- [JSValue](#jsvaluev)
- [JSObject](#jsobjectv)
- [JSArray](#jsarrayv)
- [JSFunc](#jsfuncv)
- [JSDate](#jsdatev)
- [Type Conversion](#type-conversion)
- [Rust → JavaScript (IntoJSValue)](#rust--javascript-intojsvalue)
- [JavaScript → Rust (FromJSValue)](#javascript--rust-fromjsvalue)
- [Complete Type Mapping Reference](#complete-type-mapping-reference)
- [API Reference](#api-reference)
- [Common Patterns](#common-patterns)
- [Common Pitfalls](#common-pitfalls)
---
## Overview
The value system provides a type-safe bridge between Rust and JavaScript. It abstracts over different JS engines (QuickJS, ArkTS, etc.) through a trait-based design.
**Key Design Principles:**
1. **Engine Abstraction**: The generic parameter `V` represents engine-specific value types
2. **Type Safety**: `JSValue<V>` bundles raw values with context to prevent misuse
3. **Zero-Cost Wrappers**: Specialized types (`JSObject`, `JSArray`, etc.) are thin wrappers
4. **Bidirectional Conversion**: `FromJSValue` and `IntoJSValue` traits handle all conversions
---
## Quick Start
Most common conversions you'll need:
```rust
// Rust → JavaScript
let js_num = JSValue::from(&ctx, 42);
let js_str = JSValue::from(&ctx, "hello");
let js_bool = true.into_js_value(&ctx);
// JavaScript → Rust
let num: i32 = js_value.try_into()?;
let s: String = js_value.try_into()?;
let b: bool = js_value.try_into()?;
// Working with objects
let obj = JSObject::new(&ctx);
obj.set("name", "Alice")?;
let name: String = obj.get("name")?;
// Working with arrays
let arr = JSArray::new(&ctx)?;
arr.push(1)?;
arr.push(2)?;
let first: i32 = arr.get(0)?.unwrap();
```
For detailed information, see [Type Conversion](#type-conversion) and [API Reference](#api-reference).
---
## Type Hierarchy
```
┌─────────────────┐
│ V (raw value) │ Engine-specific (e.g., QJSValue)
│ JSValueImpl │
└────────┬────────┘
│
│ wrapped by
▼
┌─────────────────┐
│ JSValue<V> │ Safe wrapper with context
└────────┬────────┘
│
┌──────────────┼──────────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ JSObject<V>│ │ JSFunc<V> │ │ JSSymbol<V>│ │ JSDate<V> │
└──────┬─────┘ └────────────┘ └────────────┘ └────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
┌────────────┐ ┌──────────────┐
│ JSArray<V> │ │JSException<V>│
└────────────┘ └──────────────┘
```
### Relationship Summary
| `V` | - | Raw engine value (implements `JSValueImpl`) |
| `JSValue<V>` | `V` | Any JS value with context |
| `JSObject<V>` | `JSValue<V>` | JS object |
| `JSArray<V>` | `JSObject<V>` | JS array |
| `JSFunc<V>` | `JSObject<V>` | JS function |
| `JSDate<V>` | `JSValue<V>` | JS Date object |
| `JSSymbol<V>` | `JSValue<V>` | JS Symbol |
| `JSException<V>` | `JSObject<V>` | Exception-channel object (thrown/rejected payload; often an `Error`) |
---
## Core Traits
### `JSValueImpl`
The foundational trait that all engine-specific value types must implement.
```rust
pub trait JSValueImpl: Clone + PartialEq + Hash {
type RawValue: Copy; // FFI-level value
type Context: JSContextImpl<Value = Self>; // Associated context type
fn from_borrowed_raw(ctx: RawContext, value: RawValue) -> Self;
fn from_owned_raw(ctx: RawContext, value: RawValue) -> Self;
fn into_raw_value(self) -> RawValue;
fn create_null(ctx: &Self::Context) -> Self;
fn create_undefined(ctx: &Self::Context) -> Self;
fn create_symbol(ctx: &Self::Context, description: &str) -> Self;
fn create_date(ctx: &Self::Context, epoch_ms: f64) -> Self;
// ...
}
```
### `FromJSValue<V>`
Converts JavaScript values to Rust types.
```rust
pub trait FromJSValue<V>: Sized
where
V: JSValueImpl,
{
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self>;
}
```
### `IntoJSValue<V>`
Converts Rust types to JavaScript values.
```rust
pub trait IntoJSValue<V>
where
V: JSValueImpl,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V>;
}
```
### `JSTypeOf`
Provides runtime type checking for JS values.
```rust
pub trait JSTypeOf {
fn type_of(&self) -> JSValueType;
fn is_object(&self) -> bool;
fn is_array(&self) -> bool;
fn is_function(&self) -> bool;
fn is_string(&self) -> bool;
fn is_number(&self) -> bool;
fn is_boolean(&self) -> bool;
fn is_null(&self) -> bool;
fn is_undefined(&self) -> bool;
fn is_exception(&self) -> bool;
fn is_promise(&self) -> bool;
fn is_date(&self) -> bool;
fn is_symbol(&self) -> bool;
// ...
}
```
### `JSObjectOps`
Operations available on JavaScript objects.
```rust
pub trait JSObjectOps: JSValueConversion + JSTypeOf {
fn new_object(ctx: &Self::Context) -> Self;
fn get_property(&self, key: Self) -> Option<Self>;
fn set_property(&self, key: Self, value: Self) -> bool;
fn del_property(&self, key: Self) -> bool;
fn has_property(&self, key: Self) -> bool;
fn get_own_property_names(&self) -> Option<Vec<Self>>;
// ...
}
```
### `JSValueConversion`
A helper trait that bundles all primitive type conversion bounds.
```rust
pub trait JSValueConversion:
JSValueImpl
+ for<'a> From<(&'a Self::Context, bool)>
+ for<'a> From<(&'a Self::Context, i32)>
+ for<'a> From<(&'a Self::Context, f64)>
+ for<'a> From<(&'a Self::Context, &'a str)>
+ TryInto<bool, Error = RongJSError>
+ TryInto<i32, Error = RongJSError>
+ TryInto<f64, Error = RongJSError>
+ TryInto<String, Error = RongJSError>
// ... more types
{
}
```
### `JSCompatible`
Marker trait for Rust primitive types that have direct JS equivalents.
```rust
pub trait JSCompatible: Sized {}
// Implemented for: i32, u32, i64, u64, f64, bool
```
---
## Value Types
### `JSValue<V>`
The primary safe wrapper for JavaScript values.
```rust
pub struct JSValue<V: JSValueImpl> {
inner: V,
}
```
**Key Methods:**
| `try_into::<T>(self)` | Convert to Rust type `T` (uses `FromJSValue`) |
| `from(&ctx, val)` | Create from Rust value (uses `IntoJSValue`) |
| `from_raw(ctx, value)` | Create from raw engine value |
| `into_value(self)` | Extract raw engine value |
| `as_value(&self)` | Borrow raw engine value |
| `get_ctx(&self)` | Get associated context |
| `undefined(ctx)` | Create JS `undefined` |
| `null(ctx)` | Create JS `null` |
| `into_object(self)` | Convert to `JSObject` if applicable |
### `JSObject<V>`
Represents JavaScript objects. Wraps `JSValue<V>` and implements `Deref<Target = JSValue<V>>`.
```rust
pub struct JSObject<V: JSValueImpl>(JSValue<V>);
```
**Key Methods:**
| `new(ctx)` | Create empty object |
| `from_json_string(ctx, json)` | Parse JSON into object |
| `get<K, T>(key)` | Get property as type `T` |
| `set<K, T>(key, value)` | Set property |
| `del(key)` | Delete property |
| `has(key)` | Check property existence |
| `keys()` | Get all property keys |
| `values()` | Get all property values |
| `entries()` | Get key-value pairs |
| `into_js_value(self)` | Convert to `JSValue<V>` |
| `as_js_value(&self)` | Borrow as `JSValue<V>` |
### `JSArray<V>`
Represents JavaScript arrays. Wraps `JSObject<V>`.
```rust
pub struct JSArray<V: JSValueImpl>(JSObject<V>);
```
**Key Methods:**
| `new(ctx)` | Create empty array |
| `len()` | Get array length |
| `get<T>(index)` | Get element at index |
| `set<T>(index, value)` | Set element at index |
| `push<T>(value)` | Append element |
| `pop<T>()` | Remove and return last element |
| `iter<T>()` | Iterate over elements |
### `JSFunc<V>`
Represents JavaScript functions.
**Key Methods:**
| `call<R, A>(this, args)` | Call function synchronously |
| `call_async<R, A>(this, args)` | Call and await if returns Promise |
### `JSDate<V>`
Represents JavaScript Date objects.
**Key Methods:**
| `new(ctx, epoch_ms)` | Create from epoch milliseconds |
| `now(ctx)` | Create with current time |
| `get_time()` | Get epoch milliseconds |
| `to_system_time()` | Convert to `std::time::SystemTime` |
---
## Type Conversion
Rong provides seamless type conversion between Rust and JavaScript types through the `FromJSValue` and `IntoJSValue` traits. The conversion system is designed to be:
- **Type-safe**: All conversions are checked at compile time or return `JSResult` for runtime validation
- **Extensible**: Custom types can implement the conversion traits
- **Ergonomic**: Common conversions work automatically with minimal boilerplate
### Conversion Architecture
The conversion system has two layers:
1. **Low-level**: `TryInto<T>` / `From<(&Ctx, T)>` — Engine-specific, operates on raw `V`
2. **High-level**: `FromJSValue<V>` / `IntoJSValue<V>` — Application-level, operates on `JSValue<V>`
```
┌─────────────────────────────────────────────────────────────────────┐
│ Rust Types │
├─────────────────────────────────────────────────────────────────────┤
│ Primitives │ Collections │ Wrapper Types │ Special Types │
│ ─────────── │ ─────────── │ ──────────── │ ──────────── │
│ bool │ Vec<T> │ JSValue<V> │ SystemTime │
│ i8..i64 │ │ JSObject<V> │ RongJSError │
│ u8..u64 │ │ JSArray<V> │ JSResult<T> │
│ f64 │ │ JSFunc<V> │ │
│ String, &str │ │ JSDate<V> │ │
│ () │ │ JSSymbol<V> │ │
│ Option<T> │ │ Promise<V> │ │
└────────┬────────┴───────┬───────┴────────┬────────┴────────┬────────┘
│ │ │ │
│ IntoJSValue │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ JSValue<V> │
├─────────────────────────────────────────────────────────────────────┤
│ .into_value() ───────────────────────────▶ V (raw value) │
│ JSValue::from_raw(ctx, v) ◀─────────────── V (raw value) │
└─────────────────────────────────────────────────────────────────────┘
│ │ │ │
│ FromJSValue │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ JavaScript Types │
├─────────────────────────────────────────────────────────────────────┤
│ Boolean │ Number │ String │ undefined │ null │ Array │ Object │... │
└─────────────────────────────────────────────────────────────────────┘
```
### Low-Level Traits: `TryInto` and `From`
At the engine level, raw values (`V`) implement standard Rust traits for primitive type conversions. These are the foundation that `FromJSValue` and `IntoJSValue` build upon.
| `TryInto<T> for V` | JS → Rust | Extract primitive from raw JS value |
| `From<(&Ctx, T)> for V` | Rust → JS | Create raw JS value from primitive |
**Supported primitive types:** `bool`, `i32`, `u32`, `i64`, `u64`, `f64`, `String`
### How High-Level Traits Use Low-Level Traits
The `FromJSValue` and `IntoJSValue` traits delegate to these low-level implementations:
```rust
// FromJSValue uses TryInto internally
impl<V, T> FromJSValue<V> for T
where
V: TryInto<T, Error = RongJSError>, // ← requires low-level trait
T: JSCompatible,
{
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
value.into_value().try_into()
// ^^^^^^^^^^^^ ^^^^^^^^^
// unwrap to V use TryInto<T>
}
}
// IntoJSValue uses From internally
impl<V, T> IntoJSValue<V> for T
where
V: for<'a> From<(&'a V::Context, T)>, // ← requires low-level trait
T: JSCompatible,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
JSValue::from_raw(ctx, V::from((ctx.as_ref(), self)))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// use From<(&Ctx, T)>
}
}
```
### Trait Layers Summary
| Low-level | JS → Rust | `TryInto<T>` | `V` (raw) | `Result<T>` |
| Low-level | Rust → JS | `From<(&Ctx, T)>` | `(&Ctx, T)` | `V` (raw) |
| High-level | JS → Rust | `FromJSValue<V>` | `JSValue<V>` | `JSResult<T>` |
| High-level | Rust → JS | `IntoJSValue<V>` | `T` | `JSValue<V>` |
> **Note**: The low-level `TryInto<T> for V` operates on raw engine value `V`.
> The `JSValue::try_into::<T>()` method is a high-level convenience API that uses `FromJSValue` internally.
### Rust → JavaScript (IntoJSValue)
The `IntoJSValue` trait converts Rust values into JavaScript values.
```rust
pub trait IntoJSValue<V: JSValueImpl> {
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V>;
}
```
#### Primitive Types
| `bool` | Boolean | `JSCompatible` blanket impl |
| `i32` | Number | `JSCompatible` blanket impl |
| `u32` | Number | `JSCompatible` blanket impl |
| `i64` | Number | `JSCompatible` blanket impl |
| `u64` | Number | `JSCompatible` blanket impl |
| `f64` | Number | `JSCompatible` blanket impl |
```rust
// Implementation: blanket impl for JSCompatible types
impl<V, T> IntoJSValue<V> for T
where
V: JSValueImpl,
V: for<'a> From<(&'a V::Context, T)>,
T: JSCompatible, // marker trait for: i32, u32, i64, u64, f64, bool
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
JSValue::from_raw(ctx, V::from((ctx.as_ref(), self)))
}
}
```
#### Extended Integer Types
Smaller integer types are converted via intermediate types:
| `i8` | → `i32` | Number |
| `i16` | → `i32` | Number |
| `u8` | → `u32` | Number |
| `u16` | → `u32` | Number |
| `isize` | → `i64` | Number |
| `usize` | → `u64` | Number |
```rust
// Generated by impl_js_converter_for_int! macro
impl<V> IntoJSValue<V> for i8 {
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
JSValue::from_raw(ctx, V::from((ctx.as_ref(), self as i32)))
}
}
```
#### String Types
| `&str` | String |
| `String` | String |
```rust
impl<V> IntoJSValue<V> for &str {
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
let raw = V::from((ctx.as_ref(), self));
JSValue::from_raw(ctx, raw)
}
}
impl<V> IntoJSValue<V> for String {
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
let raw = V::from((ctx.as_ref(), self.as_str()));
JSValue::from_raw(ctx, raw)
}
}
```
#### Special Types
| `()` | `undefined` | Unit type becomes undefined |
| `Option<T>` | `T` or `null` | `None` → `null`, `Some(v)` → `v` |
| `Vec<T>` | `Array` | Each element converted |
| `SystemTime` | `Date` | Converted via epoch milliseconds |
| `JSResult<T>` | `T` or Exception | `Ok(v)` → `v`, `Err(e)` → throws |
| `RongJSError` | Exception | Throws JS exception |
```rust
// Option<T>: None becomes null
impl<V, T> IntoJSValue<V> for Option<T>
where
T: IntoJSValue<V>,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
match self {
Some(value) => value.into_js_value(ctx),
None => JSValue::from_raw(ctx, V::create_null(ctx.as_ref())),
}
}
}
// Vec<T>: creates JS Array with converted elements
impl<V, T> IntoJSValue<V> for Vec<T>
where
V: JSObjectOps + JSArrayOps,
T: IntoJSValue<V>,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
let array = JSArray::new(ctx).unwrap();
for item in self {
array.push(item).expect("Failed to set value in array");
}
array.into_js_value(ctx)
}
}
// SystemTime: converts to JS Date via epoch milliseconds
impl<V: JSValueImpl> IntoJSValue<V> for SystemTime {
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
let epoch_ms = self
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as f64;
JSValue::from_raw(ctx, V::create_date(ctx.as_ref(), epoch_ms))
}
}
// JSResult<T>: Ok converts value, Err throws exception
impl<V, T> IntoJSValue<V> for JSResult<T>
where
T: IntoJSValue<V>,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
match self {
Ok(value) => value.into_js_value(ctx),
Err(err) => err.into_js_value(ctx), // throws exception
}
}
}
```
#### Wrapper Types (Passthrough)
These types already wrap JS values and simply unwrap:
| `JSValue<V>` | (any) |
| `JSObject<V>` | Object |
| `JSArray<V>` | Array |
| `JSFunc<V>` | Function |
| `JSDate<V>` | Date |
| `JSSymbol<V>` | Symbol |
| `Promise<V>` | Promise |
```rust
// All wrapper types follow this pattern
impl<V: JSValueImpl> IntoJSValue<V> for JSObject<V> {
fn into_js_value(self, _ctx: &JSContext<V::Context>) -> JSValue<V> {
self.0 // unwrap inner JSValue
}
}
```
### JavaScript → Rust (FromJSValue)
The `FromJSValue` trait converts JavaScript values into Rust types. All conversions are fallible and return `JSResult<T>`.
```rust
pub trait FromJSValue<V: JSValueImpl>: Sized {
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self>;
}
```
#### Primitive Types
| Boolean | `bool` | TypeError |
| Number | `i32`, `u32`, `i64`, `u64`, `f64` | TypeError |
| String | `String` | TypeError |
| any | `()` | Never fails |
```rust
// Blanket impl for JSCompatible types using TryInto
impl<V, T> FromJSValue<V> for T
where
V: TryInto<T, Error = RongJSError>,
T: JSCompatible,
{
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
value.into_value().try_into() // uses engine's TryInto impl
}
}
// String has explicit implementation
impl<V> FromJSValue<V> for String
where
V: TryInto<String, Error = RongJSError>,
{
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
value.into_value().try_into()
}
}
// Unit type always succeeds
impl<V: JSValueImpl> FromJSValue<V> for () {
fn from_js_value(_ctx: &JSContext<V::Context>, _value: JSValue<V>) -> JSResult<Self> {
Ok(())
}
}
```
#### Extended Integer Types
| Number | `i8` | via `i32` then cast |
| Number | `i16` | via `i32` then cast |
| Number | `u8` | via `u32` then cast |
| Number | `u16` | via `u32` then cast |
| Number | `isize` | via `i64` then cast |
| Number | `usize` | via `u64` then cast |
```rust
// Generated by impl_js_converter_for_int! macro
impl<V> FromJSValue<V> for i8 {
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
let intermediate: i32 = value.into_value().try_into()?;
Ok(intermediate as i8)
}
}
```
#### Collection Types
| Array | `Vec<T>` | `T: FromJSValue<V>` |
```rust
impl<V, T> FromJSValue<V> for Vec<T>
where
V: JSTypeOf + JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
if value.is_array() {
let array = JSArray::from_js_value(ctx, value)?;
array.iter::<T>().collect::<JSResult<Vec<_>>>()
} else {
Err(RongJSError::NotJSArray())
}
}
}
```
#### Wrapper Types
Each wrapper type validates the JS type before wrapping:
| Object | `JSObject<V>` | `is_object()` |
| Array | `JSArray<V>` | `is_array()` |
| Function | `JSFunc<V>` | `is_function()` |
| Date | `JSDate<V>` | `is_date()` |
| Symbol | `JSSymbol<V>` | `is_symbol()` |
| Promise | `Promise<V>` | (via JSObject) |
| any | `JSValue<V>` | Never fails |
```rust
// JSObject: validates is_object()
impl<V: JSTypeOf> FromJSValue<V> for JSObject<V> {
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
if value.is_object() {
Ok(value.into())
} else {
Err(RongJSError::NotObject())
}
}
}
// JSArray: validates is_array()
impl<V: JSTypeOf> FromJSValue<V> for JSArray<V> {
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
if value.is_array() {
JSObject::from_js_value(ctx, value).map(Self)
} else {
Err(RongJSError::NotJSArray())
}
}
}
// JSDate: validates is_date()
impl<V: JSTypeOf> FromJSValue<V> for JSDate<V> {
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
if !value.is_date() {
return Err(HostError::new(E_TYPE, "Value is not a Date")
.with_name("TypeError")
.into());
}
Ok(JSDate { inner: value })
}
}
// JSValue: passthrough, never fails
impl<V: JSValueImpl> FromJSValue<V> for JSValue<V> {
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
Ok(value)
}
}
```
#### Special Types
| Date | `SystemTime` | Calls `getTime()` method |
| Error/thrown value | `RongJSError` | Captures exception info |
```rust
// SystemTime: extracts epoch ms from Date
impl<V> FromJSValue<V> for SystemTime
where
V: JSTypeOf + JSValueConversion + JSObjectOps,
{
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
let js_date = JSDate::from_js_value(ctx, value)?;
js_date.to_system_time() // calls getTime() internally
}
}
// RongJSError: captures thrown value
impl<V: JSObjectOps> FromJSValue<V> for RongJSError {
fn from_js_value(_ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
Ok(RongJSError::from_thrown_value(value))
}
}
```
### Complete Type Mapping Reference
#### Rust → JavaScript
| `bool` | Boolean | `JSCompatible` |
| `i8`, `i16` | Number | via `i32` |
| `i32` | Number | `JSCompatible` |
| `i64`, `isize` | Number | `JSCompatible` / via `i64` |
| `u8`, `u16` | Number | via `u32` |
| `u32` | Number | `JSCompatible` |
| `u64`, `usize` | Number | `JSCompatible` / via `u64` |
| `f64` | Number | `JSCompatible` |
| `&str` | String | Direct |
| `String` | String | Direct |
| `()` | undefined | `V::create_undefined()` |
| `Option<T>` | T / null | Recursive |
| `Vec<T>` | Array | Create + push each |
| `SystemTime` | Date | `V::create_date(epoch_ms)` |
| `JSResult<T>` | T / Exception | Ok/Err handling |
| `RongJSError` | Exception | `throw_js_exception()` |
| `JSValue<V>` | (passthrough) | Identity |
| `JSObject<V>` | Object | Unwrap |
| `JSArray<V>` | Array | Unwrap |
| `JSFunc<V>` | Function | Unwrap |
| `JSDate<V>` | Date | Unwrap |
| `JSSymbol<V>` | Symbol | Unwrap |
| `Promise<V>` | Promise | Unwrap |
#### JavaScript → Rust
| Boolean | `bool` | Type check |
| Number | `i32`, `u32`, `i64`, `u64`, `f64` | Type check |
| Number | `i8`, `i16`, `u8`, `u16`, `isize`, `usize` | Type check + cast |
| String | `String` | Type check |
| any | `()` | Always succeeds |
| Array | `Vec<T>` | `is_array()` + element convert |
| Array | `JSArray<V>` | `is_array()` |
| Object | `JSObject<V>` | `is_object()` |
| Function | `JSFunc<V>` | `is_function()` |
| Date | `JSDate<V>` | `is_date()` |
| Date | `SystemTime` | `is_date()` + `getTime()` |
| Symbol | `JSSymbol<V>` | `is_symbol()` |
| Promise | `Promise<V>` | via `JSObject` |
| any | `JSValue<V>` | Always succeeds |
| Error/thrown | `RongJSError` | Captures value |
---
## API Reference
### Creating Values
```rust
// Primitives
let num = 42_i32.into_js_value(&ctx);
let str = "hello".into_js_value(&ctx);
let flag = true.into_js_value(&ctx);
// Special values
let undef = JSValue::undefined(&ctx);
let null = JSValue::null(&ctx);
// Objects
let obj = JSObject::new(&ctx);
let obj = JSObject::from_json_string(&ctx, r#"{"key": "value"}"#)?;
// Arrays
let arr = JSArray::new(&ctx)?;
```
### Converting Values
```rust
// JS → Rust: using try_into (most common pattern)
let value: JSValue<V> = /* ... */;
// Note: try_into() consumes the JSValue, so clone() if you need to keep the original
// With type annotation
let s: String = value.clone().try_into()?;
// With turbofish syntax
let s = value.clone().try_into::<String>()?;
let n = value.clone().try_into::<i32>()?;
let b = value.clone().try_into::<bool>()?;
// Pattern matching (common in conditional logic)
if let Ok(s) = value.clone().try_into::<String>() {
println!("Got string: {}", s);
}
// If you don't need the value afterward, no clone needed
let msg: String = value.try_into().unwrap_or_default();
// Using FromJSValue directly (less common)
let num = i32::from_js_value(&ctx, value)?;
// Rust → JS
let js_value = JSValue::from(&ctx, 42);
let js_value = JSValue::from(&ctx, "hello");
let js_value = true.into_js_value(&ctx);
```
### Working with Objects
```rust
let obj = JSObject::new(&ctx);
// Set properties
obj.set("name", "Alice")?;
obj.set("age", 30)?;
// Get properties
let name: String = obj.get("name")?;
let age: i32 = obj.get("age")?;
// Check and delete
if obj.has("temp") {
obj.del("temp");
}
// Iterate
for key in obj.keys_as::<String>()? {
println!("{}", key);
}
```
### Working with Arrays
```rust
let arr = JSArray::new(&ctx)?;
// Modify
arr.push(1)?;
arr.push(2)?;
arr.push(3)?;
arr.set(0, 100)?;
// Access
let first: i32 = arr.get(0)?.unwrap();
let len = arr.len();
// Iterate
for item in arr.iter::<i32>() {
println!("{}", item?);
}
// Convert to Vec
let vec: Vec<i32> = Vec::from_js_value(&ctx, arr.into_js_value(&ctx))?;
```
### Type Checking
```rust
let value: JSValue<V> = /* ... */;
if value.is_string() {
let s: String = value.try_into()?;
}
match value.type_of() {
JSValueType::Number => { /* ... */ }
JSValueType::String => { /* ... */ }
JSValueType::Object => { /* ... */ }
JSValueType::Array => { /* ... */ }
_ => { /* ... */ }
}
```
---
## Common Patterns
### Pattern 1: Wrapping Raw Values
When receiving raw values from engine APIs, wrap them with `JSValue::from_raw`:
```rust
let raw: V = engine_api_call();
let value = JSValue::from_raw(&ctx, raw);
```
### Pattern 2: Unwrapping for Engine APIs
When passing values to engine APIs that expect raw types:
```rust
let value: JSValue<V> = /* ... */;
let raw: V = value.into_value();
engine_api_call(raw);
```
### Pattern 3: Generic Function with Conversion
```rust
fn process_value<V, T>(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<T>
where
V: JSValueImpl,
T: FromJSValue<V>,
{
T::from_js_value(ctx, value)
}
```
### Pattern 4: Returning Values to JavaScript
```rust
fn rust_function<V>(ctx: &JSContext<V::Context>) -> JSValue<V>
where
V: JSValueImpl + for<'a> From<(&'a V::Context, i32)>,
{
42.into_js_value(ctx)
}
```
### Pattern 5: Optional Properties
```rust
// Getting optional property
let maybe_value: Option<String> = obj.get("optional_key").ok();
// Setting optional value (None becomes null)
let opt: Option<i32> = Some(42);
obj.set("key", opt)?;
```
---
## Common Pitfalls
### 1. `try_into()` Consumes the Value
The `try_into()` method takes ownership of `JSValue`. If you need the value multiple times, clone it first:
```rust
// Wrong: value is consumed after first try_into()
let s: String = value.try_into()?;
let n: i32 = value.try_into()?; // Error: value already moved
// Correct: clone before consuming
let s: String = value.clone().try_into()?;
let n: i32 = value.try_into()?; // OK, uses original
```
### 2. Integer Overflow is Silent
Smaller integer types (`i8`, `u8`, etc.) are converted via intermediate types and then cast. Overflow is not checked:
```rust
// JS number 300 → i8 will truncate
let big_number = JSValue::from(&ctx, 300_i32);
let small: i8 = big_number.try_into()?; // Result: 44, not 300!
```
If you need overflow checking, convert to the intermediate type first and validate.
### 3. `null` vs `undefined`
These are distinct in JavaScript and map differently:
| `()` | `undefined` |
| `Option::None` | `null` |
| `Option::Some(val)` | `val` |
```rust
// To return undefined
fn returns_undefined<V>() -> JSValue<V> {
().into_js_value(&ctx)
}
// To return null
fn returns_null<V>() -> JSValue<V> {
None::<i32>.into_js_value(&ctx)
}
```
### 4. Type Mismatch Errors
Conversion fails at runtime if the JavaScript type doesn't match:
```rust
let js_string = JSValue::from(&ctx, "hello");
let num: Result<i32, _> = js_string.try_into(); // Err(TypeError)
```
Use type checking before conversion if the type is uncertain:
```rust
if value.is_number() {
let n: i32 = value.try_into()?;
}
```
---
## See Also
- [Error Handling](./error_handling.md) - Error types and exception handling