如何在Rust中创建一个Deno插件

1,044 阅读5分钟

Deno是一个用Rust和V8构建的新的JavaScript运行时,使你能够在浏览器之外运行JavaScript。Deno比Node.js更安全,因为它默认限制网络和文件系统访问。

Deno的一个很酷的地方是,你可以用Rust编写插件,并在Deno代码中使用它们。在本教程中,我们将向你展示如何在Rust中创建Deno插件。

我们将涵盖以下内容。

为什么要用Rust编写Deno插件?

Deno中的插件通常会提高性能,并提供对更多工具的访问。

由于它们的性能,插件经常被用于计算重型任务,如图像处理。插件还可以让你访问用其他语言编写的各种库,包括高质量的Rust crates。

Deno插件项目结构

插件项目结构与任何Deno模块相同。为了本教程的目的,你可以使用这个锅炉板

git clone https://github.com/anshulrgoyal/deno-rust-starter.git my_module

首先,为该插件建立Rust模板。

cd my_module/native
cargo build

接下来,运行一个测试来验证Deno是否接收到了正确的库。

cd my_module/native
deno test --unstable --allow-plugin

该模板包括一个位于native 目录下的Rust项目和一个位于根目录下的Deno模块。

构建一个Rust项目

Rust项目编译了一个动态库,由Deno运行时加载。该库的文件类型和名称取决于操作系统。Rust项目可能会编译成一个so 文件--dylibdll --而且编译后的文件名称也可能不同。该模板可以处理三个主要平台。Linux、macOS和Windows。

[package]
name = "native"
version = "0.1.0"
authors = ["anshul <anshulgoel151999@gmail.com>"]
edition = "2018"

[lib]
name = "native"
crate-type = ["cdylib"]

[dependencies]
deno_core = "0.75.0"


├── README.md
├── deps.ts
├── mod.ts
├── mod_test.ts
├── native
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── src
│       └── lib.rs
├── test.ts
├── test_deps.ts
└── tsconfig.json

mod.ts 文件是由另一个使用你的模块的应用程序导入的主文件。

添加Rust代码

在本教程中,我们将向你展示如何使用oxipng crate构建一个PNG优化器。每个Deno插件都必须导出deno_plugin_init 函数,并注册该插件导出的所有方法。

#[no_mangle] 属性告诉编译器不要改变函数的名称。

#[no_mangle]
pub fn deno_plugin_init(interface: &mut dyn Interface) {
 // register the function. Pass name and function to register method
    interface.register_op("hello_world",hello_world);
}

创建优化器函数

每个导出的函数都有相同的签名。Deno插件只能导出函数。这些函数可以是同步的或异步的,取决于返回类型。

fn optimise(_interface: &mut dyn Interface,
    zero_copy: &mut [ZeroCopyBuf],
) -> Op {
    // get first argument
    let first=zero_copy.first().unwrap();
    let opts: oxipng::Options = Default::default();
    // convert vector
    let result = oxipng::optimize_from_memory(&first.to_vec(), &opts).unwrap();
    // move to heap so that deno can use it
    Op::Sync(Box::from(result))
}

函数的第二个参数包含一个缓冲区的数组。数组中的每个缓冲区代表调用时传递给导出函数的参数。这些缓冲区根据要求被序列化为字符串或其他数据类型。

上面的代码获取了zero_copy 的第一个元素,并将其传递给optimize_from_memory 。数组的第一个元素是在Deno代码中调用时传递给optimize 函数的文件。该文件是以字节形式传递的。该函数处理该文件并将结果作为Box 。返回类型是Op enum,有两个变体syncasync

使用cargo build 命令构建代码。现在这个插件可以在Deno中使用了。

在Deno中加载一个Rust插件

现在这个插件已经编译完成,让我们用Deno来加载它。

这个插件还在开发中,是不稳定的API的一部分,所以需要--unstable 标志,同样也需要--allow-plugin

let path = ""
// check the type of OS to load correct file
if (Deno.build.os === "linux") {
// linux file emited by rust compiler
  path = "./native/target/debug/libnative.so"
} else if (Deno.build.os === "windows") {
// windows file emited by rust compiler
  path = "./native/target/debug/native.dll"
} else if (Deno.build.os === "darwin") {
// macos file emited by rust comipler
  path = "./native/target/debug/libnative.dylib"
}
// load plugin from file system
const rid = Deno.openPlugin(path);
// Get available methods on plugin
//@ts-Expect-Error
const { optimise:optimise_native } = (Deno as any).core.ops();

export async function optimise(fileName: string): Promise<Uint8Array> {
// reading a file
  const file = await Deno.open(fileName);
// getting content
  const value = await Deno.readAll(file)
// closing file
  await Deno.close(file.rid)
// running the native plugin method using Deno dispatch method
  return (Deno as any).core.dispatch(optimise_native, value)
}

每个插件都是使用openPlugin 方法加载的。然后,使用ops 方法来获得方法标识符,执行插件导出的代码。

dispatch 是用来运行本地插件导出的代码的。第一个参数是方法标识符;其余参数是为本地函数传递的。在这种情况下,文件被传递。

编写异步插件

由于Deno是单线程的,阻断主线程是不明智的。Deno允许你从本地函数中返回一个未来,你可以用OS线程来编写一个不阻塞主线程的函数。

fn optimise_async(_interface: &mut dyn Interface,
    zero_copy: &mut [ZeroCopyBuf],
) -> Op {
// get first argument
    let first=zero_copy.first().unwrap();
    let opts: oxipng::Options = Default::default();
    let arg=first.to_vec();
// create a new future
    let fut = async move {
// create a channel to send result once done to main thread
        let (tx, rx) = futures::channel::oneshot::channel::<oxipng::PngResult<Vec<u8>>>();
// create a new thread
        std::thread::spawn(move || {
// perform work
          let result = oxipng::optimize_from_memory(&arg, &opts);
// send result to main thread
          tx.send(result).unwrap();
        });
// receive the result
        let result=rx.await;
// create a boxed slice
        let result_box = result.unwrap().unwrap().into_boxed_slice();
// return boxed slice from the future
        result_box
      };
// return the future
    Op::Async(fut.boxed())
}

一个未来是使用async 块创建的,并作为一个盒式未来返回。Deno处理未来的完成,并通知插件的Deno方。一个通道被用来在新线程和主线程之间通信。

Deno的代码不需要太多更新--只需要一个新的asyncHandler 来处理任务的完成。

let path = ""
if (Deno.build.os === "linux") {
  path = "./native/target/debug/libnative.so"
} else if (Deno.build.os === "windows") {
  path = "./native/target/debug/native.dll"
} else if (Deno.build.os === "darwin") {
  path = "./native/target/debug/libnative.dylib"
}
const rid = Deno.openPlugin(path);

const { optimise_async } = (Deno as any).core.ops();

export async function optimise(fileName: string){
  const file = await Deno.open(fileName);
  const value = await Deno.readAll(file);
  await Deno.close(file.rid);
// new handler
  (Deno as any).core.setAsyncHandler(optimise_async, (response:any) => {
    Deno.writeFile("l.png",response)
  });
// executing the native code.
  (Deno as any).core.dispatch(optimise_async,value);
}
await optimise("t.png")

await Deno.close(rid);

结论

在本教程中,我们介绍了如何使用Rust构建一个简单的Deno插件,以及如何使用Rust futures和deno_core crate创建一个异步插件。

Rust有一个庞大的生态系统,拥有高质量的板条箱。你可以通过创建插件在Deno中使用所有这些板块。无论是图像处理插件、数据库连接器等,对Rust插件的访问有助于扩展Deno生态系统。

The postHow to create a Deno plugin in Rustappeared first onLogRocket Blog.