作者: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 服務。此類服務的示例包括機器學習和影像識別。