做者:Michael Yuan
原文連接: https://www.infoq.com/article...
如何在 Deno TypeScript 應用程序訪問 Rust 函數?node
備受期待的 Deno 項目不久前發佈了 1.0 版本。 Deno 由 Node.js 的建立者之一 Ryan Dahl 發起,解決 Ryan 所認爲的「我爲 Node.js 感到遺憾的十件事」。git
Deno 沒有采用 NPM 和臭名昭著的 node_modules
。 Deno 是一個單一的二進制可執行文件,運行用 TypeScript 和 JavaScript 編寫的應用程序。github
可是,儘管 TypeScript 和 JavaScript 適用於大多數的 Web 應用程序,但它們不能知足計算密集型任務,例如神經網絡訓練和推理、機器學習和密碼學。 實際上,Node.js 常常須要使用本地庫來執行這些任務(例如,使用 openssl 進行加密)。web
若是沒有相似 NPM 的系統來合併本機模塊,咱們如何在 Deno 上編寫須要本機性能的服務端應用程序呢? WebAssembly 將提供幫助! 在本文中,咱們用 Rust 編寫高性能函數,將 Rust 函數編譯爲 WebAssembly,而後在 Deno 應用程序中運行它們。算法
在 GitHub 上 clone 或者 fork Deno starter 模板。按照下面的說明進行操做,只需5分鐘,就能夠在 Deno 中運行 WebAssembly 函數(由 Rust 編寫)。sql
Node.js 之因此很是成功,是由於它爲開發人員帶來了一箭雙鵰的優點:JavaScript 的易用性(尤爲是基於事件的異步應用程序)以及 C/C ++ 的高性能。 Node.js 應用程序是用 JavaScript 編寫的,可是在基於 C / C ++ 的本機運行時中執行,例如,Google V8 JavaScript 引擎和許多本機庫模塊。 Deno 但願複製此公式以取得成功,但不一樣的是,Deno 用 TypeScript 和 Rust 支持現代技術堆棧。json
Deno 是用 Rust 編寫的,基於 V8 的 JavaScript 和 TypeScript 的簡單、現代且安全的運行時。 -deno.land網站。
在《我對 Node.js 感到遺憾的十件事》這個著名演講中,Node.js 的建立者 Ryan Dahl 解釋了爲何要開始 Deno 並將 Deno 看作 Node.js 的競爭對手甚至替代者。Dahl 的遺憾集中在 Node.js 如何管理第三方代碼和模塊上。數組
package.json
,node_modules
,index.js
以及其餘 NPM 工件很是複雜,可是這並非必須的。所以,Deno 在管理依賴項時作出了一些很是有意識和自覺的選擇。安全
import
語句,並帶有完整 URL 鏈接到依賴關係的源代碼。這很好。可是,須要更高性能的應用程序應該怎麼作呢?特別是須要在幾秒鐘以內執行復雜的神經網絡模型的AI即服務應用程序,該如何處理呢? 在 Deno 和 Node.js 中,許多函數都是經過 TypeScript 或 JavaScript API 調用的,可是這些函數都是在用 Rust 或 C 語言編寫的本機代碼執行。在 Node.js 中,始終能夠選擇從 JavaScript API 調用第三方的本地庫。 可是咱們目前沒法在 Deno 中執行此操做嗎?網絡
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
,則沒法使用字符串或數組。
首先,讓咱們看一下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 服務。此類服務的示例包括機器學習和圖像識別。