rquickjs Documentation

repository·master·Indexed 21 days ago

https://github.com/delskayn/rquickjs

High-level, safe Rust bindings for the QuickJS-NG JavaScript engine (a fork of QuickJS). rquickjs provides ES2020 support, seamless async integration between Rust futures and ES6 Promises, and flexible data conversion. It allows Rust data types to be represented as JS classes via the JsClass trait, supporting static/instance members, getters, setters, and exotic property behavior. The library supports custom allocators, user-defined module loaders, and bytecode bundling via the embed macro.

Tokens
17.9K
Snippets
48
Records
78
Agent score
73%

What's inside rquickjs

  1. What is rquickjs

    master
    rquickjs is a high-level, safe Rust binding for the QuickJS-NG JavaScript engine (a fork of QuickJS). It is designed to be an easy-to-use wrapper, similar in philosophy to the rlua library. It provides a way to embed a small, fast, and ES2020-compliant JavaScript engine into Rust applications.
  2. Overview of rquickjs-core

    master

    rquickjs-core provides high-level, safe Rust bindings for the QuickJS-NG JavaScript engine (a fork of QuickJS). It is designed to be an easy-to-use wrapper, similar in philosophy to the rlua library.

    Key capabilities include:

    • ES2020 Support: Includes modules, asynchronous generators, proxies, and BigInt.
    • Async Integration: Full integration with async Rust; ES6 Promises can be handled as Rust futures and vice versa.
    • Data Conversion: Flexible conversion between many widely used Rust types and JavaScript types.
    • Memory Management: Supports user-defined allocators and integrates with Rust's global allocator. It uses reference counting with cycle removal for garbage collection.
    • Class Support: Full support for ES6 classes, allowing Rust data types to be represented as JS classes with support for static/instance members, getters, setters, and constant properties.
  3. Use rquickjs-sys for low-level QuickJS bindings

    master

    The rquickjs-sys crate provides low-level, unsafe raw bindings to the QuickJS JavaScript engine.

    Warning: This crate is intended for low-level access and is marked as unsafe. For most use cases, you should use the high-level, safe bindings provided by the rquickjs crate instead.

  4. Use ES6 classes with Rust data types

    master

    You can represent Rust data types as JavaScript classes. This allows JS to interact with Rust data through object properties, including support for both static and instance members.

    To ensure proper garbage collection when holding references to JS objects within Rust data types, the type must implement the Trace trait.

  5. Key features of the rquickjs crate

    master

    The rquickjs crate extends the core QuickJS engine with several Rust-centric features:

    • Async Integration: Full integration with async Rust. ES6 Promises can be handled as Rust futures and vice versa, making it compatible with most async runtimes.
    • Data Conversion: Flexible conversion between many widely used Rust types and JavaScript values.
    • Custom Allocators: Support for user-defined allocators when creating a Runtime, including full support for Rust's global allocator.
    • Module Loading: Support for custom module resolvers and loaders.
    • Bytecode Bundling: Use the embed macro to bundle JavaScript modules as bytecode.
    • ES6 Class Support:
      • Represent Rust data types as JS classes.
      • Access Rust data fields via JS object properties.
      • Support for both static and instance members, including getters and setters.
      • Support for constant static properties.
      • Ability to extend defined classes using JavaScript.
      • Note: If a Rust type holds references to JS objects, it must implement the Trace trait to ensure the garbage collector works correctly.
  6. Integrate QuickJS with async Rust

    master

    The crate provides full integration with async Rust, allowing for seamless communication between the JavaScript engine and Rust's asynchronous ecosystem.

    • Promises to Futures: ES6 Promises can be treated as Rust futures.
    • Futures to Promises: Rust futures can be handled as ES6 Promises.
    • Runtime Compatibility: Designed for easy integration with almost any async runtime or executor.
  7. Configure custom allocators and module loading

    master

    The library provides hooks for advanced memory and module management:

    • Custom Allocators: You can create a Runtime using a custom allocator or use Rust's global allocator.
    • Module Management: Supports user-defined module resolvers and loaders, which can be combined for flexible module resolution strategies.
  8. Handle JavaScript exceptions in Rust with CaughtResult

    master

    When executing JavaScript code via rquickjs, errors can be either native Rust errors or exceptions thrown from within the QuickJS engine. To handle both, use CaughtResult<'js, T> (an alias for StdResult<T, CaughtError<'js>>).

    CaughtError can be one of three variants:

    • Error(Error): A native Rust error.
    • Exception(Exception<'js>): A JavaScript exception that is an instance of the Error object.
    • Value(Value<'js>): A JavaScript exception that is a primitive value (e.g., throw 3).

    You can use the catch extension trait to convert a standard Result<T> into a CaughtResult<'js, T>, which automatically retrieves the underlying JavaScript exception value from the context if an Error::Exception is encountered.

    # use rquickjs::{Error, Context, Runtime, CaughtError};
    # let rt = Runtime::new().unwrap();
    # let ctx = Context::full(&rt).unwrap();
    # ctx.with(|ctx|{
    # use rquickjs::CatchResultExt;
    
    if let Err(CaughtError::Value(err)) = ctx.eval::<(),_>("throw 3").catch(&ctx){
        assert_eq!(err.as_int(), Some(3));
    } else {
        panic!("Expected a Value exception")
    }
  9. Use WeakRuntime for safe reference management

    master

    If you need to hold a reference to a Runtime without preventing it from being dropped, use WeakRuntime. This is useful for long-lived handles that should not extend the lifetime of the engine.

    • Runtime::weak() -> WeakRuntime: Creates a weak handle from an existing Runtime.
    • WeakRuntime::try_ref() -> Option<Runtime>: Attempts to upgrade the weak handle back into a strong Runtime handle. Returns None if the original Runtime has been dropped.
    let rt = Runtime::new().unwrap();
    let weak_rt = rt.weak();
    
    // Later...
    if let Some(strong_rt) = weak_rt.try_ref() {
        // Use strong_rt
    }
  10. How `#[qjs]` handles function parameters

    master

    The #[qjs] macro automatically maps JavaScript arguments to Rust function parameters based on their type and mutability:

    1. Owned Values: Passing a type like String or i32 will attempt to extract an owned value from the JavaScript argument.
    2. Borrows: Using a reference (e.g., &T) will extract an OwnedBorrow<'js, T>.
    3. Mutable Borrows: Using a mutable reference (e.g., &mut T) will extract an OwnedBorrowMut<'js, T>.
    4. self (Methods): If the function is a method (using self), the macro handles the this context, allowing you to access the object instance as the first argument.
  11. Configure property attributes using `Property` and `Accessor`

    master

    When defining properties via Object::prop, you can use two main types to control how the property behaves in the JavaScript environment:

    Property<T>

    Used for data descriptors. It stores a value and its associated flags.

    • Methods: writable(), configurable(), enumerable().
    • Creation: Property::from(value).

    Accessor<G, S>

    Used for accessor descriptors. It defines logic for getting and setting a property.

    • Methods: get(closure), set(closure).
    • Creation: Accessor::from(get_closure) or Accessor::new_get(get_closure) or Accessor::new_set(set_closure) or Accessor::new(get_closure, set_closure).

    Note: If you pass a raw value that implements IntoJs directly to prop, it is treated as a Property that is read-only by default.

    // Property example
    let p = Property::from("value").writable().enumerable();
    
    // Accessor example
    let a = Accessor::from(|| "get_val").set(|v| { /* set_val */ });
  12. Automatic conversion of Structs to/from JS

    master

    The FromJs and IntoJs derives allow for seamless data transfer between Rust structs and JavaScript objects/arrays.

    Mapping Rules

    • Named Structs: Mapped to JavaScript Objects. Field names are converted based on #[qjs(rename_all = ...)] or individual field attributes.
    • Unnamed Structs (Tuple Structs): Mapped to JavaScript Arrays. Fields are accessed by their index.
    • Unit Structs: Mapped to JavaScript undefined when converting to JS.

    Supported Types

    • Structs: Supported (Named and Unnamed).
    • Enums: Currently not supported for FromJs or IntoJs derivation.
    • Unions: Currently not supported.
    // Unnamed struct (Tuple struct) mapping to a JS Array
    #[derive(FromJs, IntoJs)]
    struct Point(u32, u32); 
    // JS: [1, 2]
    
    // Named struct mapping to a JS Object
    #[derive(FromJs, IntoJs)]
    struct User { id: u32, name: String }
    // JS: { id: 1, name: "Alice" }