Rust JavaScript引擎绑定库rquickjs的使用,rquickjs提供高性能QuickJS引擎集成与脚本执行功能

Rust JavaScript引擎绑定库rquickjs的使用

rquickjs 是一个高级的 QuickJS JavaScript 引擎绑定库,目标是成为一个易于使用且安全的包装器,类似于 rlua 库。

QuickJS 的主要特点

  • 小巧且易于嵌入:只需几个 C 文件,无外部依赖,简单的 hello world 程序在 x86 上仅需 210 KiB 代码
  • 快速解释器,启动时间极短:在桌面 PC 单核上运行 75000 个 ECMAScript 测试套件用例仅需约 100 秒
  • 几乎完整的 ES2020 支持,包括模块、异步生成器和完整的 Annex B 支持(传统 Web 兼容性)
  • 数学扩展:BigDecimal、BigFloat、运算符重载、bigint 模式、数学模式
  • 使用引用计数的垃圾回收(减少内存使用并具有确定性行为)和循环移除
  • 命令行解释器,使用 JavaScript 实现上下文着色

该 crate 提供的功能

  • 完整的异步 Rust 集成
    • ES6 Promise 可以作为 Rust future 处理,反之亦然
    • 轻松与几乎任何异步运行时或执行器集成
  • Rust 和 JS 之间的灵活数据转换
    • 许多广泛使用的 Rust 类型可以转换为 JS 类型,反之亦然
  • 支持用户定义的分配器
    • 可以使用自定义分配器创建 Runtime
    • 也完全支持使用 Rust 的全局分配器
  • 支持用户定义的模块解析器和加载器
  • 支持使用 embed 宏将 JS 模块打包为字节码
  • 支持延迟调用 JS 函数
  • 完整的 ES6 类支持
    • Rust 数据类型可以表示为 JS 类
    • 可以通过对象属性访问数据字段
    • 同时支持静态和实例成员

示例代码

use rquickjs::{Context, Runtime};

fn main() {
    // 创建运行时和上下文
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();

    // 在上下文中执行JavaScript代码
    ctx.with(|ctx| {
        let result: i32 = ctx.eval("1 + 2").unwrap();
        println!("1 + 2 = {}", result);  // 输出: 1 + 2 = 3
        
        // 调用JavaScript函数
        ctx.eval::<(), _>("function add(a, b) { return a + b; }").unwrap();
        let add_fn = ctx.globals().get::<_, fn(i32, i32) -> i32>("add").unwrap();
        let sum = add_fn(3, 4);
        println!("3 + 4 = {}", sum);  // 输出: 3 + 4 = 7
        
        // 从Rust调用JavaScript
        let value = ctx.globals().get::<_, String>("undefined").unwrap();
        println!("undefined as string: {}", value);  // 输出: undefined as string: undefined
    });
}

完整示例

下面是一个更完整的示例,展示如何在 Rust 中嵌入 JavaScript 并实现双向交互:

use rquickjs::{Runtime, Context, Function, Object, Value};
use std::error::Error;

// 定义一个Rust函数,可以从JavaScript调用
fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() -> Result<(), Box<dyn Error>> {
    // 创建运行时和上下文
    let rt = Runtime::new()?;
    rt.set_max_stack_size(512 * 1024); // 设置最大栈大小
    
    let ctx = Context::full(&rt)?;
    
    ctx.with(|ctx| {
        // 将Rust函数暴露给JavaScript
        let globals = ctx.globals();
        globals.set("rustAdd", Function::new(ctx, rust_add))?;
        
        // 执行JavaScript代码
        let result: i32 = ctx.eval(r#"
            function jsMultiply(a, b) {
                return a * b;
            }
            
            // 调用Rust函数
            const sum = rustAdd(10, 20);
            
            // 调用JavaScript函数
            const product = jsMultiply(5, 6);
            
            // 返回计算结果
            sum + product
        "#)?;
        
        println!("计算结果: {}", result);  // 输出: 计算结果: 80
        
        // 获取JavaScript中定义的函数并在Rust中调用
        let js_multiply = globals.get::<_, fn(i32, i32) -> i32>("jsMultiply")?;
        let product = js_multiply(7, 8);
        println!("7 * 8 = {}", product);  // 输出: 7 * 8 = 56
        
        Ok(())
    })?;
    
    Ok(())
}

开发状态

该绑定功能完整、基本稳定并已准备好使用。错误处理是未来可能改变的唯一部分。一些实验性功能如 parallel 可能无法按预期工作,使用时需自行承担风险。

许可证

该库采用 MIT 许可证


1 回复

Rust JavaScript引擎绑定库rquickjs使用指南

简介

rquickjs 是一个 Rust 绑定库,用于集成 QuickJS JavaScript 引擎。QuickJS 是一个轻量级、可嵌入的 JavaScript 引擎,支持 ES2020 规范,具有极小的体积和出色的性能。

rquickjs 提供了:

  • 高性能的 JavaScript 执行环境
  • 完整的 ES2020 支持
  • 内存安全保证
  • Rust 和 JavaScript 之间的无缝互操作

基本使用方法

添加依赖

首先在 Cargo.toml 中添加依赖:

[dependencies]
rquickjs = "0.4"

基本示例

use rquickjs::{Context, Runtime};

fn main() {
    // 创建运行时和上下文
    let rt = Runtime::new().unwrap();
    rt.set_max_stack_size(1024 * 1024); // 设置最大栈大小
    
    let ctx = Context::full(&rt).unwrap();
    
    // 在上下文中执行 JavaScript 代码
    ctx.with(|ctx| {
        let result: i32 = ctx.eval("1 + 2 * 3").unwrap();
        println!("Result: {}", result); // 输出: Result: 7
        
        // 执行更复杂的脚本
        let value: String = ctx.eval(r#"
            function greet(name) {
                return `Hello, ${name}!`;
            }
            greet("Rust")
        "#).unwrap();
        println!("{}", value); // 输出: Hello, Rust!
    });
}

高级功能

在 Rust 中调用 JavaScript 函数

use rquickjs::{Context, Runtime, Function};

fn main() {
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();
    
    ctx.with(|ctx| {
        // 定义一个 JavaScript 函数
        ctx.eval::<(), _>(r#"
            function add(a, b) {
                return a + b;
            }
        "#).unwrap();
        
        // 获取并调用该函数
        let add: Function = ctx.globals().get("add").unwrap();
        let result: i32 = add.call((2, 3).unwrap();
        println!("2 + 3 = {}", result); // 输出: 2 + 3 = 5
    });
}

在 JavaScript 中调用 Rust 函数

use rquickjs::{Context, Runtime};

fn main() {
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();
    
    ctx.with(|ctx| {
        // 将 Rust 函数暴露给 JavaScript
        ctx.globals().set("rustAdd", 
            |a: i32, b: i32| a + b
        ).unwrap();
        
        // 在 JavaScript 中调用 Rust 函数
        let result: i32 = ctx.eval("rustAdd(5, 7)").unwrap();
        println!("5 + 7 = {}", result); // 输出: 5 + 7 = 12
    });
}

处理 JavaScript 对象

use rquickjs::{Context, Runtime, Object};

fn main() {
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();
    
    ctx.with(|ctx| {
        // 创建 JavaScript 对象
        let obj = Object::new(ctx.clone()).unwrap();
        obj.set("name", "Alice").unwrap();
        obj.set("age", 30).unwrap();
        
        // 将对象设置为全局变量
        ctx.globals().set("person", obj).unwrap();
        
        // 在 JavaScript 中使用该对象
        let greeting: String = ctx.eval(r#"
            `Hello, my name is ${person.name} and I'm ${person.age} years old.`
        "#).unwrap();
        println!("{}", greeting); // 输出: Hello, my name is Alice and I'm 30 years old.
    });
}

异步执行

use rquickjs::{Context, Runtime, Promise};
use futures::executor::block_on;

fn main() {
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();
    
    ctx.with(|ctx| {
        // 执行异步 JavaScript 代码
        let promise: Promise<String> = ctx.eval(r#"
            new Promise((resolve) => {
                setTimeout(() => resolve("Async operation completed"), 1000);
            })
        "#).unwrap();
        
        // 等待 Promise 完成
        let result = block_on(promise).unwrap();
        println!("{}", result); // 输出: Async operation completed
    });
}

错误处理

use rquickjs::{Context, Runtime, Error};

fn main() {
    let rt = Runtime::new().unwrap();
    let ctx = Context::full(&rt).unwrap();
    
    ctx.with(|ctx| {
        match ctx.eval::<(), _>("invalid js code") {
            Ok(_) => println!("Success"),
            Err(Error::Exception) => {
                // 获取 JavaScript 异常信息
                let exception: String = ctx.eval("String(new Error().stack)").unwrap();
                println!("JS Exception: {}", exception);
            }
            Err(e) => println!("Rust Error: {}", e),
        }
    });
}

性能提示

  1. 重用 Runtime 和 Context 对象,它们的创建成本较高
  2. 对于频繁调用的函数,考虑使用 Function 缓存而不是每次都查找
  3. 设置适当的栈大小以避免栈溢出
  4. 对于高性能场景,考虑将数据序列化为 JSON 而不是使用对象属性访问

rquickjs 是一个强大的工具,可以在 Rust 应用程序中安全高效地执行 JavaScript 代码,特别适合需要脚本扩展性或需要与现有 JavaScript 代码集成的场景。

完整示例DEMO

下面是一个整合了多项功能的完整示例:

use rquickjs::{Context, Runtime, Function, Object, Promise};
use futures::executor::block_on;

fn main() {
    // 创建运行时和上下文
    let rt = Runtime::new().unwrap();
    rt.set_max_stack_size(1024 * 1024);
    
    let ctx = Context::full(&rt).unwrap();
    
    ctx.with(|ctx| {
        // 示例1: 基本JavaScript执行
        let basic_result: i32 = ctx.eval("2 + 2 * 2").unwrap();
        println!("Basic calculation: {}", basic_result);  // 输出: 6
        
        // 示例2: 在Rust中调用JS函数
        ctx.eval::<(), _>(r#"
            function multiply(a, b) {
                return a * b;
            }
        "#).unwrap();
        
        let multiply: Function = ctx.globals().get("multiply").unwrap();
        let multiply_result: i32 = multiply.call((3, 4)).unwrap();
        println!("Multiply result: {}", multiply_result);  // 输出: 12
        
        // 示例3: 在JS中调用Rust函数
        ctx.globals().set("rustSubtract", 
            |a: i32, b: i32| a - b
        ).unwrap();
        
        let subtract_result: i32 = ctx.eval("rustSubtract(10, 3)").unwrap();
        println!("Subtract result: {}", subtract_result);  // 输出: 7
        
        // 示例4: 对象操作
        let user = Object::new(ctx.clone()).unwrap();
        user.set("username", "js_user").unwrap();
        user.set("score", 100).unwrap();
        ctx.globals().set("user", user).unwrap();
        
        let user_info: String = ctx.eval(r#"
            `User ${user.username} has ${user.score} points`
        "#).unwrap();
        println!("{}", user_info);  // 输出: User js_user has 100 points
        
        // 示例5: 异步操作
        let promise: Promise<String> = ctx.eval(r#"
            new Promise((resolve) => {
                setTimeout(() => resolve("Async task finished!"), 500);
            })
        "#).unwrap();
        
        let async_result = block_on(promise).unwrap();
        println!("Async result: {}", async_result);  // 输出: Async task finished!
        
        // 示例6: 错误处理
        match ctx.eval::<(), _>("this.will.throw.error") {
            Ok(_) => println!("No error occurred"),
            Err(e) => println!("Error occurred: {:?}", e),
        }
    });
}
回到顶部