如何在 Deno 應用程序中調用 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/[email protected]/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 (" Worldn");
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) -> Vec {
return Sha3_256::digest(&v).as_slice().to_vec();
}

[wasm_bindgen]

pub fn keccak_digest(s: &[u8]) -> Vec {
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,
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 或 &[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 服務。此類服務的示例包括機器學習和圖像識別。

【雲棲號在線課堂】每天都有產品技術專家分享!
課程地址:https://yqh.aliyun.com/live

立即加入社羣,與專家面對面,及時瞭解課程最新動態!
【雲棲號在線課堂 社羣】https://c.tb.cn/F3.Z8gvnK

原文發佈時間:2020-07-21
本文作者:Michael_Yuan
本文來自:“掘金”,瞭解相關信息可以關注“掘金”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章