如何在 Deno 應用程序中調用 Rust 函數

做者:Michael Yuan
原文連接: https://www.infoq.com/article...

如何在 Deno TypeScript 應用程序訪問 Rust 函數?node

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 感到遺憾的十件事」。git

Deno 沒有采用 NPM 和臭名昭著的 node_modules。 Deno 是一個單一的二進制可執行文件,運行用 TypeScript 和 JavaScript 編寫的應用程序。github

可是,儘管 TypeScript 和 JavaScript 適用於大多數的 Web 應用程序,但它們不能知足計算密集型任務,例如神經網絡訓練和推理、機器學習和密碼學。 實際上,Node.js 常常須要使用本地庫來執行這些任務(例如,使用 openssl 進行加密)。web

若是沒有相似 NPM 的系統來合併本機模塊,咱們如何在 Deno 上編寫須要本機性能的服務端應用程序呢? WebAssembly 將提供幫助! 在本文中,咱們用 Rust 編寫高性能函數,將 Rust 函數編譯爲 WebAssembly,而後在 Deno 應用程序中運行它們。算法

TL;DR

在 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 如何管理第三方代碼和模塊上。數組

  • 用於將 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 服務。此類服務的示例包括機器學習圖像識別

相關文章
相關標籤/搜索