rust_jsc 0.5.0

High-level bindings to JavaScriptCore
Documentation
# JSClass and Custom Objects

In JavaScriptCore, a `JSClass` acts as a blueprint for creating custom JavaScript objects that are backed by native code. Rust-JSC provides a safe and ergonomic `JSClassBuilder` to define these classes and attach Rust data to the resulting objects.

## Defining a JSClass

To create a custom class, use `JSClass::builder()`. You must specify the type of the Rust data that will be attached to instances of this class using the `.build::<T>()` method.

```rust
use rust_jsc::{JSClass, JSContext};

// Define a class that will hold a `String` as its private data
let class = JSClass::builder("Greeter")
    .build::<String>()
    .expect("Failed to build class");
```

### The `type_id` Binding

When you call `.build::<T>()`, Rust-JSC internally records the `TypeId` of `T` inside the `JSClass`. This ensures that any object created from this class is strictly bound to the type `T`. 

This is a crucial safety feature: it prevents you from accidentally attaching an `i32` to an object that was created from a class expecting a `String`.

## Creating Objects

Once you have a `JSClass`, you can instantiate objects from it using the `.object()` method. You can optionally provide the initial Rust data to attach to the object.

```rust
let ctx = JSContext::new();

// Create an object and attach a String to it
let obj = class.object::<String>(&ctx, Some(String::from("Hello, World!")));
```

If you try to pass data of the wrong type, the compiler will reject it.

## Accessing Private Data

You can retrieve the attached Rust data using the `get_private_data` methods on `JSObject`. Thanks to the `PrivateDataWrapper` architecture, this is completely type-safe.

```rust
// Safe immutable access
let greeting = obj.get_private_data::<String>().unwrap();
assert_eq!(greeting, "Hello, World!");

// Wrong type returns None instead of causing UB
assert!(obj.get_private_data::<i32>().is_none());
```

### Mutating Private Data

To mutate the data, you have two options:

1. **Unsafe Mutable Reference**: Use `get_private_data_mut::<T>()`. You must manually guarantee that no other references to this data exist.
2. **Safe Interior Mutability (Recommended)**: Wrap your data in a `RefCell` or `Mutex` when defining the class.

```rust
use std::cell::RefCell;

let class = JSClass::builder("Counter")
    .build::<RefCell<i32>>()
    .unwrap();

let obj = class.object(&ctx, Some(RefCell::new(0)));

// Safe mutation!
*obj.get_private_data::<RefCell<i32>>().unwrap().borrow_mut() += 1;
```

## Callbacks and Methods

You can attach native Rust functions to your class to act as methods, constructors, or property getters/setters.

```rust
use rust_jsc::{JSContext, JSObject, JSValue, JSResult};
use rust_jsc_macros::constructor;

#[constructor]
fn my_constructor(
    ctx: JSContext,
    this: JSObject,
    arguments: &[JSValue],
) -> JSResult<JSValue> {
    // Initialize the object
    Ok(this.into())
}

let class = JSClass::builder("MyClass")
    .call_as_constructor(Some(my_constructor))
    .build::<()>()
    .unwrap();
```

When these callbacks are invoked by JavaScript, you can safely extract the `this` object and access its private Rust data to perform native operations.

## Garbage Collection and Finalization

When the JavaScript garbage collector destroys an object, Rust-JSC automatically intercepts the finalization event. It safely drops the attached `TypedData<T>`, ensuring that your Rust destructors are run and no memory is leaked.

You do not need to manually implement a `finalize` callback just to free your Rust data; Rust-JSC handles this automatically based on the `T` you provided to `.build::<T>()`.