Rust 语言原本是 Mozilla 员工 Graydon Hoare[1] 的私人项目,Mozilla 于 2009 年开始赞助这个项目,在 2010 年官方首次透露并于 2015 年发布 1.0 版本。在过去的十年中,编程语言 Rust 一直是一项突破性的技术,Rust 始终站在独特的学术研究和行业实用性结合的挑战视角。但是如果说 Rust 的影响仅仅是技术性的,那就错过了精髓,正如社区在 2016 年讨论的系列博客一样:"Rust 不仅是编程语言或编译器[2]"和"Rust 让一切触手可及[3]",同年 Rust 宣布了其官方口号:“一种让每个人都能够构建可靠和高效软件的语言”。
2021 年 2 月 8 日,Rust 基金会宣布成立,其基金会董事成员有:AWS、Google、华为、微软、Mozilla 。Rust 基金会诞生自 Rust 核心团队,并且得到了五位全球行业领先公司的财务承诺,这标志着 Rust 向成熟化迈出了坚实的一步。
各种编程语言内存管理的方式不同,但通常有以下两种方式:
Rust 则另辟蹊径采用所有权、借用、生命周期机制在编译期自动插入内存释放逻辑来实现内存管理,由于没有了垃圾回收产生的运行时开销,Rust 整体表现的速度惊人且内存利用率极高。
fn main() {
let a = String::from("hello rust");
let b = a; // 所有权被转移
println!("{}", a); // 编译失败!a 已经被释放,无法再使用
}
在一项比较 REST API 性能的基准测试中(Rust 使用 Rocket[4],Node.js 使用 Restify[5]),Rust 每秒处理 72,000 个请求,而 Node.js 为 8,000 个,空闲时使用的内存大约为 1MB,而 Node.js 为 19MB。在另一个测试中(Rust 使用 Nickel[6],Node.js 使用 Restana[7]),Rust 对请求的平均响应速度比 Node.js 快近 100 倍。
具体数据可以参考 Web Frameworks Benchmark[8]。
Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误,关于“内存安全”和“线程安全”的解释如下:
内存安全: 在具有内存安全性的编程语言中,所有内存访问都是明确定义的,通常内存不安全的情况包含:空指针、野指针、悬空指针、使用未初始化的指针、非法释放、缓冲区溢出、执行非法函数指针、数据竞争等。
据说微软 70% 的漏洞是内存安全问题
线程安全: 线程安全是程序设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。
Github((80.9K star):https://github.com/denoland/deno[9]
Deno 是一个简单、现代且安全的 JavaScript 和 TypeScript 运行时,它使用 V8 并基于 Rust 构建。Deno 是由 Node.JS 之父 Ryan Dahl[10] 创建,在 2018 JS Conf Berlin 上借演讲《Design Mistakes in Node》[11]首次对外公开。
Deno 诞生之初就是为了解决 Node 不安全和糟糕包的管理等老生常谈的问题,其中不安全时常令 Node.JS 开发者感到头疼和愤怒,近期也刚刚发生 node-ipc 事件(node-ipc 在所有用户的桌面上都会创建一个文件来宣传作者的政治观点),影响到了众多开源项目包括 Vue CLI 等。
因此 Deno 很自然地拥有以下特性:
默认安全,除非特别启用它,否则使用 Deno 运行的程序没有文件、网络或环境访问权限
deno run --allow-read mod.ts
开箱即用地支持 TypeScript
仅编译单个可执行文件
拥抱 Web 生态标准,内置了 fetch、localStorage、location 等 API
localStorage.setItem("myDemo", "Deno App"); // 拥有 10M 的持久化存储限制
内置依赖检查器 deno info
和代码格式化工具 deno fmt
有一组经过审查的标准模块,可以与 Deno 一起使用:deno.land/std[12]
目前已经有众多公司正在积极探索 Deno,包括 Amazon、Github、IBM、Vercel、Tencent、Microsoft 等头部技术公司。
Github(21K star):https://github.com/swc-project/swc[13]
SWC 是一个可扩展的基于 Rust 的前端构建工具,目前核心功能相当于 Babel,包含以下这些模块:
模块 | 状态 | 作用 |
---|---|---|
@swc/cli | 稳定 | 该模块提供命令行入口 |
@swc/core | 稳定 | 该模块提供核心 SWC API |
@swc/wasm-web | 稳定 | 该模块允许使用 WebAssembly 在浏览器内同步转换代码 |
@swc/jest | 稳定 | ts-jest 的代替品,可以使单元测试速度有质的提升 |
swc-loader | 稳定 | 该模块允许将 SWC 与 Webpack 一起使用 |
swcpack | 建设中 | 该模块试图提供完整的构建器模块,可以简单理解为 Rust 版的 Rollup 或 Webpack |
官方提供的基准测试数据如下:
name | 1 core, sync | 4 promises | 100 promises |
---|---|---|---|
swc (es3) | 616 ops/sec | 1704 ops/sec | 2199 ops/sec |
swc (es2015) | 677 ops/sec | 1688 ops/sec | 1911 ops/sec |
swc (es2016) | 1963 ops/sec | 3948 op s/sec | 5580 ops/sec |
swc (es2017) | 1971 ops/sec | 3948 ops/sec | 6259 ops/sec |
swc (es2018) | 2555 ops/sec | 4884 ops/sec | 8108 ops/sec |
swc-optimize (es3) | 645 ops/sec | 1716 ops/sec | 1860 ops/sec |
babel (es5) | 34.05 ops/sec | 27.28 ops/sec | 32 ops/sec |
SWC 在单线程上比 Babel 快 20 倍,在四核上快 70 倍。
目前 SWC 已经被 Next.js、Parcel 和 Deno 等工具以及 Vercel、字节跳动、腾讯、Shopify 等公司广泛使用。
Github(40k star):https://github.com/parcel-bundler/parcel[14]
支持以 HTML 作为入口的零配置构建工具,Parcel 支持多种开箱即用的语言和文件类型,从 HTML、CSS 和 JavaScript 等 Web 技术到图像、字体、视频等资产。当您使用默认不包含的文件类型时,Parcel 将自动为您安装所有必要的插件和开发依赖项。Parcel 的 JavaScript 编译器和源映射是建立在 SWC[15] 编译器之上的,在 SWC 之上,Parcel 实现了依赖项收集、捆绑、摇树优化、热重载等。
目前 Parcel 已经被广泛应用在微软、Atlassian、SourceGraph 等公司。
Github(17.2 star):https://github.com/rome/tools[16]
Rome[17] 是 Babel 作者做的基于 Node.js 的前端构建全家桶,包含但不限于 JavaScript、TypeScript、JSON、HTML、Markdown 和 CSS,在 2021 年 9 月 21 日 宣布计划使用 Rust 重构。
dprint[18]:使用 Rust 编写,比 Prettier 快 30x 倍
postcss-rs[19]:使用 Rust 编写,比 Postcss 快 20x 倍
Github(34.7 star):https://github.com/tauri-apps/tauri[20]
在很长一段时间里,包括现在,Electron[21] 都是最流行的跨平台桌面应用开发框架,目前在 Github 上有 101K 个 star,它允许你使用纯粹的前端技术(HTML、CSS、JS、Node.JS)来构建桌面应用,不过它也有两个比较明显的缺陷被人诟病:包体积太大和内存占用高,而造成这两个问题的根本原因是 Electron 是基于 Chromium 和 Node.JS 构建的。
Tauri 是 Electron 的代替品,现在 Tauri 试图去除 Chromium 转而使用 Rust 去和系统内置 Webview 进行绑定,简单来说就是在 Electon 时代你的应用永远使用 Chromium 内核,而在 Tauri 时代,你的应用在 Windows 上使用 Edge/Webview2,在 macOS 上使用 WebKit,在 Linux 上使用 WebKitGTK。基于 Rust 和 Webview 的好处很明显:包体积极小且内存占用极低。以下是官方提供的数据:
Detail | Tauri | Electron |
---|---|---|
Installer Size Linux | 3.1 MB | 52.1 MB |
Memory Consumption Linux | 180 MB | 462 MB |
Launch Time Linux | 0.39s | 0.80s |
Interface Service Provider | WRY | Chromium |
Backend Binding | Rust | Node.js (ECMAScript) |
Underlying Engine | Rust | V8 (C/C++) |
FLOSS | Yes | No |
Multithreading | Yes | Yes |
Bytecode Delivery | Yes | No |
Multiple Windows | Yes | Yes |
Auto Updater | Yes | Yes1 |
Custom App Icon | Yes | Yes |
Windows Binary | Yes | Yes |
MacOS Binary | Yes | Yes |
Linux Binary | Yes | Yes |
iOS Binary | Soon | No |
Android Binary | Soon | No |
Desktop Tray | Yes | Yes |
Sidecar Binaries | Yes |
看起来还不错的样子,不过别忘了如果你使用 Tauri 开发的话,后端(Electron 中叫主进程)目前只能使用 Rust,这将带来不小的学习成本,除此之外 Tauri 还有很长的路需要走,不过也算文艺复兴式的创新。
WebAssembly 是一种新的编码方式,具有紧凑的二进制格式,可以在现代的网络浏览器中以接近原生的性能运行,目前 Rust 和 WebAssembly 结合有两大主要用例:
整个 Web 应用都基于 Rust 开发:比如 Yew 等框架
在现存的 JavaScript 前端中使用 Rust
Github(Github 20k star):https://github.com/yewstack/yew[22]
Yew 是一个设计先进的 Rust 框架,目的是使用 WebAssembly[23] 来创建多线程的前端应用,它有几个特点:
一个简单的 Yew 应用代码如下所示:
use yew::prelude::*;
enum Msg {
AddOne,
}
struct Model {
value: i64,
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
value: 0,
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::AddOne => {
self.value += 1;
// the value has changed so we need to
// re-render for it to appear on the page
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
// This gives us a component's "`Scope`" which allows us to send messages, etc to the component.
let link = ctx.link();
html! {
<div>
<button onclick={link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
<p>{ self.value }</p>
</div>
}
}
}
fn main() {
yew::start_app::<Model>();
}
Github(5.0k star):https://github.com/rustwasm/wasm-bindgen[26]
目前 WebAssembly 类型系统还很小,只有四种数字类型,如果要使用复杂类型(例如字符串、对象、数组、结构体),需要花点心思:
但是每次转换它们(序列化为线性内存,并提供它们所在位置的引用)是一项枯燥的工作并且容易出错,幸运的是,Rust world 想出了 wasm-bindgen
来促进 WebAssembly 模块和 JavaScript 之间的高级交互,其使用方式也非常简单:
创建一个 Rust 项目
$ cargo new --lib hello_world
Created library `hello_world` package
打开 Cargo.toml
文件并添加 wasm-bindgen
依赖项
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Sendil Kumar <sendilkumarn@live.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.56"
打开src/lib.rs
文件并将内容替换为以下内容
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn hello_world() -> String {
"Hello World".to_string()
}
编译成 wasm 模块
cargo build --target=wasm32-unknown-unknown
安装 wasm-bindgen-cli
,并将 wasm 文件转换成 JavaScript 文件
cargo install wasm-bindgen-cli
wasm-bindgen target/wasm32-unknown-unknown/debug/hello_world.wasm --out-dir .
# ls -lrta
# 76330 hello_world_bg.wasm
# 1218 hello_world.js
# 109 hello_world.d.ts
# 190 hello_world_bg.d.ts
然后你就可以使用 hello_world.js
文件了,它可以帮你加载 wasm 文件。
Github(4.1k star):https://github.com/rustwasm/wasm-pack[27]
这是一个可以直接将你的 Rust 代码打包成 npm 包的工具,用法十分简单,只有 4 个命令:
new
:使用模板生成一个新的 Rust Wasm 项目build
: 从 rustwasm crate 生成一个 npm wasm pkgtest
:运行浏览器测试pack
和 publish
:创建压缩包,发布到镜像仓库值得注意的是,WebAssembly 目前还并不是提高 Web 应用性能的万金油,就目前来说,在 WebAssembly 中使用 DOM API 仍然比从 JavaScript 中调用要慢。但只是暂时性问题的,WebAssembly Interface Types[28] 计划将解决这个问题。如果你想要了解更多关于这方面的信息,可以查看 Mozilla 的这篇文章[29] 。
Github(2.2k star):https://github.com/napi-rs/napi-rs[30]
NAPI-RS 是一个用于在 Rust 中构建预编译的 Node.js 插件的框架,SWC 便基于此库。
详细信息可以看《用 Rust 和 N-API 开发高性能 Node.js 扩展》[31]这篇文章。
随着前端开发复杂度的不断上升,配套工具链的效率将不容被忽视,受限于 Node.js 语言本身的效率问题,近几年将会有更多工具会被 Rust 重写,效率有望数倍乃至数十倍的提升,另外,Rust 和 WebAssembly 的结合也令人感到激动,但就目前来看离大规模上生产还有相当一段路需要走。
无论如何,对前端开发者而言现在是学习 Rust 的最佳时机。
Graydon Hoare: https://github.com/graydon
[2]Rust 不仅是编程语言或编译器: https://graydon2.dreamwidth.org/247406.html
[3]Rust 让一切触手可及: https://www.thefeedbackloop.xyz/safety-is-rusts-fireflower/
[4]Rocket: https://rocket.rs/
[5]Restify: http://restify.com/
[6]Nickel: https://github.com/nickel-org/nickel.rs
[7]Restana: https://github.com/BackendStack21/restana
[8]Web Frameworks Benchmark: https://web-frameworks-benchmark.netlify.app/compare?f=express,rocket
[9]https://github.com/denoland/deno: https://github.com/denoland/deno
[10]Ryan Dahl: https://github.com/ry
[11]《Design Mistakes in Node》: https://liguo.run/posts/node-mistakes
[12]deno.land/std: https://deno.land/std
[13]https://github.com/swc-project/swc: https://github.com/swc-project/swc
[14]https://github.com/parcel-bundler/parcel: https://github.com/swc-project/swc
[15]SWC: https://swc.rs/
[16]https://github.com/rome/tools: https://github.com/rome/tools
[17]Rome: https://rome.tools/blog/2020/08/08/introducing-rome
[18]dprint: https://github.com/devongovett/dprint-node
[19]postcss-rs: https://github.com/postcss-rs/postcss-rs
[20]https://github.com/tauri-apps/tauri: https://github.com/tauri-apps/tauri
[21]Electron: https://www.electronjs.org/
[22]https://github.com/yewstack/yew: https://github.com/yewstack/yew
[23]WebAssembly: https://webassembly.org/
[24]React: https://reactjs.org/
[25]Elm: https://elm-lang.org/
[26]https://github.com/rustwasm/wasm-bindgen: https://github.com/rustwasm/wasm-bindgen
[27]https://github.com/rustwasm/wasm-pack: https://github.com/rustwasm/wasm-pack
[28]WebAssembly Interface Types: https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md
[29]这篇文章: https://hacks.mozilla.org/2019/08/webassembly-interface-types/
[30]https://github.com/napi-rs/napi-rs: https://github.com/napi-rs/napi-rs
[31]《用 Rust 和 N-API 开发高性能 Node.js 扩展》: https://zhuanlan.zhihu.com/p/234914336