如何在 Deno 應用程式中呼叫 Rust 函式

WASM中文社群發表於2020-07-22
作者:Michael Yuan
原文連結:https://www.infoq.com/article...

如何在 Deno TypeScript 應用程式訪問 Rust 函式?

1image-5-1594801176405.jpg

要點:

  • 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.jsonnode_modulesindex.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 結構都使用 SerializeDeserialize 進行了註釋,以便 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 服務。此類服務的示例包括機器學習影像識別

相關文章