如何在 Deno 应用程序中调用 Rust 函数?

云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


要点:

  • Deno 和 Node.js 都在基于 C/C ++ 的运行时上执行 JavaScript 以实现高性能
  • Deno 是单个二进制应用程序,与 NPM 模H 0 _ ^ { ; n / @块不兼容,并且没有简单的方法能将本机; v 2 Q j 4模块合并到应用程序中。
  • WebAssembly 提供了一种在 Deno 应用程序中运行高性能代码的方法。
  • WebAssemblL ) u 4 8y 用于服务端应用程序,l _ 7 : ( A是安全、轻便且轻量级的容器。
  • Rust 编译器] K T G Q工具链为 WebAssembly 提供了强大的支持。

备受期待的 Deno 项目不久前发布了 1.~ l p % 40 版本。U y 8 C U L Deno 由 Node.js 的创建者之一 Ryan Dahl 发起,解决 Ryan 所认为的“我为 Node.js 感到遗憾的十件事”。

Deno 没有采% Y ] U用 NPM 和臭- + H名昭著的 node_module8 e / l L R [s。 Deno 是一个单一的二进制可执行文件,运行用 TypeScript 和 JavaScript 编写的应用程序。

但是,尽管 TypeScrh 1 ) | A ] 3 |ipt 和 JavaScript 适用于大多数的 Web 应用程序,但它们不能满足计算密集型任务,例如神经网络训练和推理、机器学习和密码学。 实际上,Node.js 经常需要使用本地库来执行这些任务(例如,使用 openssl 进行加密)。

如果没有x z s f 7 I [ 6 }类似 NPM 的系统来合并本机模块,我们如何在 Deno 上编写需要本机性能的服务端应用程序呢? WebAssembD 7 : D n s G y ply 将提供帮助! 在本文中,我们用 Ru{ M e Zst 编写高r O ) . T ^ ] 8性能函数,将 Rust 函数编译为 WebAssembly,然后在 Deno 应用程序中运行它们。

TL;DR

在 GitHub 上 clone 或_ r D = % 0 m P @者 fork Deno starter 模板。按照下面的说明# u E } d n进行操作,只需5分钟,就可以在 Deno 中运行 WebAsse: x B 7mbly 函数(由 Rust 编} a 7 S g = I写)。

背景知识

Node.js 之所以非常成功,是因为它为开发人员带来了两全其美的优势:JavaScri= z D V f X { pt 的易用性(尤其是基于事件的异步应用程序)以及 C/} R I . G - 8 ( aC ++ 的高性能。 Node.js 应+ p Y b $ W * j用程序是用 JavaScript 编写的,但是在基于 C / C ++ 的本机运行时中执行,例如,Google V8 JavaScript 引擎和许多本机库模块。 Deno 希望复制此公式以取得成功,但不同的是,D{ 5 :eno 用 TypeScript 和= 6 [ w t H t N ( Rust 支持现代技术堆栈。

Deno 是用 Rust 编写的,基于 V8 的 JavaScript 和 TypeScript 的简单、现代且安全的运行时。 -deno.land网站。

《我对 Node.js 感到遗憾的十件事n a Q C / H r #这个著名演讲中,Node.js 的创建者 Ryan Dahl 解释了为什么要开始 Deno 并将 Deno 看做 Node.js 的k ? @ d # I `竞争A F E , q u Q (对手甚至替代者。Dahl 的遗憾集中在 Node.js 如何管理第三方代码和模块M 6 H 3 E 2上。

  • 用于将 C 模块连接到 Node.js 的复杂构建系统。
  • package.json,node_modules,index.js 以及其他 NPM 工件非常复杂,但是这并不是必! ) g w x # } * r须的。

因此,Deno 在管理依赖项时做出了一些非常有意识和自觉的选择。

  • Deno 是单个二进制可执行文件。
  • 0 e 2 ! X用程序使用 TypeScript 或 JavaScript 编写,并且在代码中将依赖关系明确声明为import 语句,并带有完整 URL 连接到依赖关系F q Y的源代码。
  • Deno 与B Y c Node.j! & x Q & p 3 ls 模块不兼容。

这很好。但是,需要更高性能的应用程序应该怎么做呢?特别是需要在几秒钟之内执行复杂的神$ 7 y Y Z经网络2 3 ] * i Q 2 f模型的u [ M : r D r 7AI即服务应用程序,该如何处理呢? 在 Deno 和 Not W V F { 2de.js 中,许多函数都是通过 TypeScript 或 JavaScript@ K ~ ` & API 调用的,但是这些函数都是在用 Rust 或 C 语言编写的本机代码执行。在 Nody . t pe.js 中,始终可以选择从 JavaScr` 9 7ipt API 调用第三方的本地库。} j k j / J 但是我们目前无法在 Deno 中执行此操作M R 2 3 o o x吗?

Deno 中的 WebAssembly 支持W % 4 o b x 2 k

WebAssembly 是一种轻量级虚拟机,旨在以接近本机的速度执行可移植的字节码。你可以将 Rust 或 C C ++ 函数编译为WebAssembly 字节码,然后从 TypeScript 访问这些函数。对于某些任务,这种方式比执行 TypeScript 编/ ? f : : & c /写的等效函数要快得多。例如,IBM 发布的研d = q究发现在某些数据处理算法中使用 Rust 和 Web~ 3 Y k 0 +Assembly ,可以将 Node.js 的执行速度提高 1200% 至 1500% 。

Deno 内部使用 Google V8 引擎。 V8 不仅是 JavaScript 运行时,还是 WebAssembly 虚拟机。 Deno 开箱即用地支持WebAssembly。 Deno 为 TypeScript 应用程序提供了一个API,以调用 W2 z u T D V s D febAssembly 中的函数。

实际上,WebAssemj 1 W j K Q 7 =bly 中已经实& j | R 4 g % .现了一些流行的 Deno 组件。例如,使用 Emscripten 将 sqlite 的 C 源代码编译到 WebAssembly 中来创建 Deno 中的 s/ 9 a f S = l 9qlite 模块。 Deno WASI 组件使 WebAssembly 应用程序可以访问底层操作系 ~ U 0 ( M 0统资源,例如文件系统。本文将展示如何在 Rust 和 WebAssembly 中编写高性能 Deno 应用程序。

设置

第一步- ) I G h ^ J K =当然是安装 Deno, 在大多数操作系统中,只需要一行命令

$ curl -o e C SfsSL https://deno.land/x/install/install.sh | sh

既然我们要A # 5用 Rust 写函数,也需要安装 Rust 语言的编译器与工具.

$ curl --proto '=htz ) e D H 9 wtps' --tlsv1.2 -sSf https://sh.rustup.rs | sh

最后,ssvmup 工具自动执行构建过程并生成所有工件,使 Deno 应用程序可以轻松调用 Rust 函数。同样,需要安n M - n b , q K j装 ssvmup 依赖项。

$ curl https://raw.githubusercontent.com/second-state/ssvmup/master/installeY P p y Dr/init.sh -sSf | sh

注意:ssvmup$ G 6 P Q 使用 wasm-bindgen 在 JavaScript 和 Rust 源代码之间自动生成“胶水”代码,以便 JavaScript 和 Rust 可以使用各自的本机数据类型进行通信。没有 ssvmup,函数参数和返回值将限于 WebAssembly 本地支持的简单类型(即32位整数)。例如,如果没有 ssvmup 和 wasm-bindgen,则无法使用字符串或数组。

Hello world

首先,让我们看一下Deno 的 hello world 示例,从 Gw 5 XitHub 获取 helK t R J d P ^ &loc & 6 world 源代码和应用程序模板。

Rust 函数位于 src/lik 0 ;b.rs 文件中,只需在输入字符串前加上“ hello” 即可。注意Y D # (,say() 函数使用#[wasm_bindgen]进行了注释,从而使 ssvmuL F = O N +p 可以生成必要的“管道”。基于此,我们可m u [ y n h以从 TypeScript 调用 Rust 函数。

#[wasm_bindgen]
p- & a [ { eub fn say(s: &str) -> String {
let r = String::from("hello_ ( j g _ ");
return r + s;
}

Deno 应用程序位于 deno / server.ts 文件中。该应用程序从 pkg / fun} Y ^ ; ) ` 9 0 =ctions_lib.js 文; v 7 | M |件导入 Rust 的 say() 函数,该文件由 ssvmup 工具生成。 functions_lib.js 文件名取决于 Cargo.toml 文件中定义的 Rust 项目名称。

import { serve } from "https://deno.land/std& $ x Y #@0.g o ! o 3 x Q *54.0/http/server.ts";
import { say } from '../pkg/functions_lib.js';
type Resp =r q i . {
body: string;
}
co1 j /nst s = serve({ port: 8000 });
conso7 1 9 z - n 6 nle.log("http://localhos, . [ { G k 5 M )t:8000/");
for await (const req of s) {
let r = {} as Resp;
r.body = say (" World\n");
req.respond(r);
}

? | % H u s A在,运行 ssvmup 将 Rust 函数构建为 Deno WebAssembly 函数。

$ ssvmup build --target deno

ssvmup 成功完成后,您可以检查 pkg / functions_lib.js文件,了解如H - 3 H * W U何使用 De( = C Jno WebAssemblye 9 Z U x 1 C API 执行已编译的 WebAssembly 文件 pkg / functions_lib.wasm。

接下来v j l Z S a v C,运行 Deno 应用程序。 Deno 需要读取文件系统的权限,因为它需要加载0 ) K ( M C m WebAssembly 文件。同时,Deno 也需要访问网络,因为它需f b K n f J l o要接收和响应 HTTP 请求。

$ deno run --allow-read! , 4 E ` * N ; --allow-net deno/server.ts

现在,Q { { j ! u k在另一个终端窗口中,可以访问 De6 Y s M % $no Web 应用程序,通过 HTTP 连接 sayb B 3 hello!

$ curl http://localhostr t:8000/

hello World

一个复杂的例子

这个入门模板项目包括许多详细的示例,展示了如何在 Deno TypeScriptA D 4 和 Rust 函数之间传递复杂的数据。这是 src/libg V .rs 中其他的 Rust 函数。请注意,它们都用 #[wasm_bindgen]注释。

#[wasm_bindgen]
pub fn obfusticate(s: String) -> String {
(&- E p [ { 6 1 9amp;s).chars().map(|c| {
match c {
'A' ..= 'M' | 'aR _ b . $ y' ..= '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_denomiO j L s knator(a: i32, b:# ^ C E i32) -> i32 {
let r = lc C : u r * Pm6 H - a m + Q I(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_bindgen1 N 7 _ * l W s]
pub fn keccak_digest(s: &[u8]) -> Vec<u8> {
return Keccak256::dige8 * 9 = - _ C p )st(E 2 z zs).as_slice().to_vec();
}

也许最有趣的是 creaty J [ ( qe_line()函数。这个函数需要两个 JSON 字符串。每个字d = f a 4 } ) 5符串代表一个 Point 结构,并返回一个代表 Line 结构的 JSON 字符串。注意,Point和 Lin2 O b ) L T 6 we 结构都使用 Serialize 和 DeserA I T U ( = I u Viali3 : 2 X Wze 进行了注释,以便 RustG c | / ( L { 编译器自动生成必要的代码来支持它们与 JSONg # X Q y I X 6 字符串之间& y ! ; c # b的转换。

use wasm_bo 0 K ^indgen::prelude::*;
use serde::{Serialize, Deserialize3 ] C};
#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: f32,
y: f32
}
#[d@ E : u I n ! {erive(Serialize, Deserialize,7 . : J q N @ Debug)]
struct Line {
points: Vec<Point&g& & 4 ( i N 7 ft;c ; j D  S 7 [,
valid: bool,
length: f32,
desc: Str; : 5 g z S 9 ~ ;ing
}
#[wasm_bindgen]
pub fn create_line (b 8 q | H Z e _p1: &str, p2: &str, desc: &str) -> String {
let p[ } S ! k R M ^ ioint1: Pr l #oint = serde_json::from_str(p1).unwrap()) E / );
let point2: Point = serde_json::from_str(p2).unwrap();
let length = ((point1.x - pd % S , 7 noint2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();
let valid = if length == 0.0 { false } el@ Z  ` Pse { true };
let line = Line { points: vecw - v![point1, point2], valid: valid, lenR = t V , , - tgth: length, desc: desc.to_string() };
return serde_json::toC . T - u_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 Uint8Arr3 p x @ s / V Gay。 JavaScript 对象需要先通过 JSON.stringF ] W | ! 9 0 { mify() 或 JSON.parse() 才能传入 Rust 函数或从 Rust 函数返回。

import { say, obfusticate, lowest_common_dea w  + J N Y enominator, sha3_digest, keccak_digest, create_line } from '.Y [ } _ H 1 V s `./pkg/fy ` k P ^ junctions_lib./ : I V ` Hjs';
const encoder = new TextEncoder();
console.log( say("SSVM") );
console.log( obfusticate("A quick brown fox jumps over the lazyw n * 7 | K d f dog") );
console.log( lowE I 8 = O 9 h Q 4est_common_denominator(123, 2) );
console.log( sha3_di^ 3 Lgest(encoder.encode("This is an important message")) );
coD  + Vnsole.log( keccak_digest(encoder.encode("T4 1 E % 0 g Z 2his is an important message")) );
var p1 = {x:1.5, y:3.8};
vd 5 F oar p2 = {x:2.5, y:5.c ^ x  18};
var line = JSON.parse(create_line(JSON.stringify(p1), JSON.str| # H  s s Ningify(p2), "A thin red line")* @ d d N);
console.log( line );

运行 ssvmup 构建 Rust 库之后,在 Deno 运行时中运行 deno/test.ts 会产生以7 L D下输出:

$ ssvmup build --target deno
... Building the wasm file aV % _ ! C c [ U tnd JS shim file in pkg/ ...
$ deno run --allow-read denoR v 8 ) [ g/test.ts` B  l # & M I
hello SSVM
N dhvpx oebja sbk whzcf bir9 S h m O 7 s 3 e gur ynml qbt
246
Uint8Array(32) [
87, 27, 231, 209, 189, 105, 251,  49,
...s h 8 E O 4 m H Z ...
]
Uint8Array(32) [
126, 194, 241, 200, 151, 116, 227,
... ...
]
{
points: [ { x: 1.a B ? ( ( t5,8 i G E + 1 B w y: 3.8 }, { x: 2.5, y: 5.8 } ],
valid: true,
length: 2.2360682,
desc: "A thiQ 9 S } x 3n red line"
}

接下来是什么?

现在,我们可以创建 Rust 函数,并从 Deno TypeScript 应- U M m用程序访问 Rust 函数。接下来,我们可以用 Rust 函数编写计算密集型任H s w ^ ) ) 6务,并通过 Deno 提供高性能和安全的 Web 服务。此类服务的示例包括机器学习和图像识别。

【云栖号在线课堂】每天都有产品技术专家N Z ^ S r u -分享!
课程地址:https://yqh.aliyun.comr h 4 &/live

立即加入社群,与专家面对面,及时了a D J m解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-07-21
本文作者:Michael_Yuan
5 ; H 2 f . c文来自:“掘金”,了解相关信息& u G可以关注“掘金”