作者:Michael Yuan
原文链接:https://www.infoq.com/article…
如何在 Deno TypeScript 应用程序拜访 Rust 函数?
要点:
- Deno 和 Node.js 都在基于 C/C ++ 的运行时上执行 JavaScript 以实现高性能。
- Deno 是单个二进制应用程序,与 NPM 模块不兼容,并且没有简略的办法能将本机模块合并到应用程序中。
- WebAssembly 提供了一种在 Deno 应用程序中运行高性能代码的办法。
- WebAssembly 用于服务端应用程序,是平安、轻便且轻量级的容器。
- Rust 编译器工具链为 WebAssembly 提供了弱小的反对。
备受期待的 Deno 我的项目不久前公布了 1.0 版本。Deno 由 Node.js 的创建者之一 Ryan Dahl 发动,解决 Ryan 所认为的“我为 Node.js 感到遗憾的十件事”。
Deno 没有采纳 NPM 和臭名远扬的 node_modules
。Deno 是一个繁多的二进制可执行文件,运行用 TypeScript 和 JavaScript 编写的应用程序。
然而,只管 TypeScript 和 JavaScript 实用于大多数的 Web 应用程序,但它们不能满足计算密集型工作,例如神经网络训练和推理、机器学习和密码学。实际上,Node.js 常常须要应用本地库来执行这些工作(例如,应用 openssl 进行加密)。
如果没有相似 NPM 的零碎来合并本机模块,咱们如何在 Deno 上编写须要本机性能的服务端应用程序呢?WebAssembly 将提供帮忙!在本文中,咱们用 Rust 编写高性能函数,将 Rust 函数编译为 WebAssembly,而后在 Deno 应用程序中运行它们。
TL;DR
在 GitHub 上 clone 或者 fork Deno starter 模板。依照上面的阐明进行操作,只需 5 分钟,就能够在 Deno 中运行 WebAssembly 函数(由 Rust 编写)。
背景常识
Node.js 之所以十分胜利,是因为它为开发人员带来了两败俱伤的劣势:JavaScript 的易用性(尤其是基于事件的异步应用程序)以及 C/C ++ 的高性能。Node.js 应用程序是用 JavaScript 编写的,然而在基于 C / C ++ 的本机运行时中执行,例如,Google V8 JavaScript 引擎和许多本机库模块。Deno 心愿复制此公式以取得成功,但不同的是,Deno 用 TypeScript 和 Rust 反对古代技术堆栈。
Deno 是用 Rust 编写的,基于 V8 的 JavaScript 和 TypeScript 的简略、古代且平安的运行时。-deno.land 网站。
在 《我对 Node.js 感到遗憾的十件事 》这个驰名演讲中,Node.js 的创建者 Ryan Dahl 解释了为什么要开始 Deno 并将 Deno 看做 Node.js 的竞争对手甚至替代者。Dahl 的遗憾集中在 Node.js 如何治理第三方代码和模块上。
- 用于将 C 模块连贯到 Node.js 的简单构建零碎。
-
package.json
,node_modules
,index.js
以及其余 NPM 工件非常复杂,然而这并不是必须的。
因而,Deno 在治理依赖项时做出了一些十分无意识和盲目的抉择。
- Deno 是单个二进制可执行文件。
- 应用程序应用 TypeScript 或 JavaScript 编写,并且在代码中将依赖关系明确申明为
import
语句,并带有残缺 URL 连贯到依赖关系的源代码。 - Deno 与 Node.js 模块不兼容。
这很好。然而,须要更高性能的应用程序应该怎么做呢?特地是须要在几秒钟之内执行简单的神经网络模型的 AI 即服务应用程序,该如何解决呢?在 Deno 和 Node.js 中,许多函数都是通过 TypeScript 或 JavaScript API 调用的,然而这些函数都是在用 Rust 或 C 语言编写的本机代码执行。在 Node.js 中,始终能够抉择从 JavaScript API 调用第三方的本地库。然而咱们目前无奈在 Deno 中执行此操作吗?
Deno 中的 WebAssembly 反对
WebAssembly 是一种轻量级虚拟机,旨在以靠近本机的速度执行可移植的字节码。你能够将 Rust 或 C C ++ 函数编译为 WebAssembly 字节码,而后从 TypeScript 拜访这些函数。对于某些工作,这种形式比执行 TypeScript 编写的等效函数要快得多。例如,IBM 公布的钻研发现在某些数据处理算法中应用 Rust 和 WebAssembly,能够将 Node.js 的执行速度进步 1200% 至 1500%。
Deno 外部应用 Google V8 引擎。V8 不仅是 JavaScript 运行时,还是 WebAssembly 虚拟机。Deno 开箱即用地反对 WebAssembly。Deno 为 TypeScript 应用程序提供了一个 API,以调用 WebAssembly 中的函数。
实际上,WebAssembly 中曾经实现了一些风行的 Deno 组件。例如,应用 Emscripten 将 sqlite 的 C 源代码编译到 WebAssembly 中来创立 Deno 中的 sqlite 模块。Deno WASI 组件使 WebAssembly 应用程序能够拜访底层操作系统资源,例如文件系统。本文将展现如何在 Rust 和 WebAssembly 中编写高性能 Deno 应用程序。
设置
第一步当然是装置 Deno,在大多数操作系统中,只须要一行命令
$ curl -fsSL https://deno.land/x/install/install.sh | sh
既然咱们要用 Rust 写函数,也须要装置 Rust 语言的编译器与工具.
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
最初,ssvmup 工具主动执行构建过程并生成所有工件,使 Deno 应用程序能够轻松调用 Rust 函数。同样,须要装置 ssvmup 依赖项。
$ curl https://raw.githubusercontent.com/second-state/ssvmup/master/installer/init.sh -sSf | sh
留神:ssvmup 应用 wasm-bindgen
在 JavaScript 和 Rust 源代码之间主动生成“胶水”代码,以便 JavaScript 和 Rust 能够应用各自的本机数据类型进行通信。没有 ssvmup,函数参数和返回值将限于 WebAssembly 本地反对的简略类型(即 32 位整数)。例如,如果没有 ssvmup 和 wasm-bindgen
,则无奈应用字符串或数组。
Hello world
首先,让咱们看一下 Deno 的 hello world 示例,从 GitHub 获取 hello world 源代码和应用程序模板。
Rust 函数位于 src/lib.rs 文件中,只需在输出字符串前加上“hello”即可。留神,say()
函数应用#[wasm_bindgen]
进行了正文,从而使 ssvmup 能够生成必要的“管道”。基于此,咱们能够从 TypeScript 调用 Rust 函数。
#[wasm_bindgen]
pub fn say(s: &str) -> String {let r = String::from("hello");
return r + s;
}
Deno 应用程序位于 deno / server.ts 文件中。该应用程序从 pkg / functions_lib.js 文件导入 Rust 的 say()
函数,该文件由 ssvmup 工具生成。functions_lib.js
文件名取决于 Cargo.toml 文件中定义的 Rust 项目名称。
import {serve} from "https://deno.land/std@0.54.0/http/server.ts";
import {say} from '../pkg/functions_lib.js';
type Resp = {body: string;}
const s = serve({port: 8000});
console.log("http://localhost:8000/");
for await (const req of s) {let r = {} as Resp;
r.body = say ("World\n");
req.respond(r);
}
当初,运行 ssvmup 将 Rust 函数构建为 Deno WebAssembly 函数。
$ ssvmup build --target deno
ssvmup 胜利实现后,您能够查看 pkg / functions_lib.js
文件,理解如何应用 Deno WebAssembly API 执行已编译的 WebAssembly 文件 pkg / functions_lib.wasm
。
接下来,运行 Deno 应用程序。Deno 须要读取文件系统的权限,因为它须要加载 WebAssembly 文件。同时,Deno 也须要拜访网络,因为它须要接管和响应 HTTP 申请。
$ deno run --allow-read --allow-net deno/server.ts
当初,在另一个终端窗口中,能够拜访 Deno Web 应用程序,通过 HTTP 连贯 say hello!
$ curl http://localhost:8000/
hello World
一个简单的例子
这个入门模板我的项目包含许多具体的示例,展现了如何在 Deno TypeScript 和 Rust 函数之间传递简单的数据。这是 src/lib.rs 中其余的 Rust 函数。请留神,它们都用 #[wasm_bindgen]
正文。
#[wasm_bindgen]
pub fn obfusticate(s: String) -> String {(&s).chars().map(|c| {
match c {'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
_ => c
}
}).collect()}
#[wasm_bindgen]
pub fn lowest_common_denominator(a: i32, b: i32) -> i32 {let r = lcm(a, b);
return r;
}
#[wasm_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {return Sha3_256::digest(&v).as_slice().to_vec();
}
#[wasm_bindgen]
pub fn keccak_digest(s: &[u8]) -> Vec<u8> {return Keccak256::digest(s).as_slice().to_vec();
}
兴许最乏味的是 create_line()
函数。这个函数须要两个 JSON 字符串。每个字符串代表一个 Point
构造,并返回一个代表 Line
构造的 JSON 字符串。留神,Point
和 Line
构造都应用 Serialize
和 Deserialize
进行了正文,以便 Rust
编译器主动生成必要的代码来反对它们与 JSON
字符串之间的转换。
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: f32,
y: f32
}
#[derive(Serialize, Deserialize, Debug)]
struct Line {
points: Vec<Point>,
valid: bool,
length: f32,
desc: String
}
#[wasm_bindgen]
pub fn create_line (p1: &str, p2: &str, desc: &str) -> String {let point1: Point = serde_json::from_str(p1).unwrap();
let point2: Point = serde_json::from_str(p2).unwrap();
let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
let valid = if length == 0.0 {false} else {true};
let line = Line {points: vec![point1, point2], valid: valid, length: length, desc: desc.to_string()};
return serde_json::to_string(&line).unwrap();}
#[wasm_bindgen]
pub fn say(s: &str) -> String {let r = String::from("hello");
return r + s;
}
接下来,让咱们检查一下 JavaScript 程序 deno/test.ts,这显示了如何调用 Rust 函数。String
和 &str
是 JavaScript 中的简略字符串,i32
是数字,而 Vec <u8>
或 &[8]
是 JavaScript Uint8Array
。JavaScript 对象须要先通过 JSON.stringify()
或 JSON.parse()
能力传入 Rust 函数或从 Rust 函数返回。
import {say, obfusticate, lowest_common_denominator, sha3_digest, keccak_digest, create_line} from '../pkg/functions_lib.js';
const encoder = new TextEncoder();
console.log(say("SSVM") );
console.log(obfusticate("A quick brown fox jumps over the lazy dog") );
console.log(lowest_common_denominator(123, 2) );
console.log(sha3_digest(encoder.encode("This is an important message")) );
console.log(keccak_digest(encoder.encode("This is an important message")) );
var p1 = {x:1.5, y:3.8};
var p2 = {x:2.5, y:5.8};
var line = JSON.parse(create_line(JSON.stringify(p1), JSON.stringify(p2), "A thin red line"));
console.log(line);
运行 ssvmup 构建 Rust 库之后,在 Deno 运行时中运行 deno/test.ts 会产生以下输入:
$ ssvmup build --target deno
... Building the wasm file and JS shim file in pkg/ ...
$ deno run --allow-read deno/test.ts
hello SSVM
N dhvpx oebja sbk whzcf bire gur ynml qbt
246
Uint8Array(32) [
87, 27, 231, 209, 189, 105, 251, 49,
... ...
]
Uint8Array(32) [
126, 194, 241, 200, 151, 116, 227,
... ...
]
{points: [ { x: 1.5, y: 3.8}, {x: 2.5, y: 5.8} ],
valid: true,
length: 2.2360682,
desc: "A thin red line"
}
接下来是什么?
当初,咱们能够创立 Rust 函数,并从 Deno TypeScript 应用程序拜访 Rust 函数。接下来,咱们能够用 Rust 函数编写计算密集型工作,并通过 Deno 提供高性能和平安的 Web 服务。此类服务的示例包含机器学习和图像识别。