Rust 是 2010 年起源于 Mozilla Research 的一种编程语言。现在,全部大公司都在使用它。css
亚马逊和微软都承认它是其系统中 C / C ++的最佳替代品,可是 Rust 并不止于此。像 Figma 和 Discord 这样的公司如今也经过在客户端应用中使用 Rust 来引领潮流。html
本篇 Rust 教程旨在简要介绍 Rust,如何在浏览器中使用它,以及什么时候应该考虑使用它。我将从比较 Rust 和 JavaScript 开始,而后引导你完成 Rust 在浏览器中运行的步骤。最后,我将介绍一个使用 Rust 和 JavaScript 的 COVID simulator web 应用程序的快速性能评估。node
Rust 在概念上与 JavaScript 很是不一样。但也有类似之处须要指出,让咱们来看看问题的两面。webpack
这两种语言都有一个现代化的包管理系统。JavaScript 有 npm,Rust 有 Cargo。Rust 有 Cargo.toml
来代替 package.json
进行依赖管理。要建立一个新的项目,使用 cargo init
,要运行它,使用 cargo run
。不太陌生吧?c++
Rust 中有许多很酷的功能,你已经从 JavaScript 中知道了,只是语法略有不一样。利用这个常见的 JavaScript 模式,对数组中的每一个元素都应用一个闭包:git
let staff = [ { name: "George", money: 0 }, { name: "Lea", money: 500000 }, ]; let salary = 1000; staff.forEach((employee) => { employee.money += salary; });
在 Rust 中,咱们能够这样写:github
let salary = 1000; staff.iter_mut().for_each( |employee| { employee.money += salary; } );
诚然,习惯这种语法须要时间,用管子( |
)代替括号。但在克服了最初的尴尬以后,我发现它比另外一组括号读起来更清晰。web
再举一个例子,这是 JavaScript 中的对象解构:npm
let point = { x: 5, y: 10 }; let { x, y } = point;
一样在 Rust 中:编程
let point = Point { x: 5, y: 10 }; let Point { x, y } = point;
主要的区别是,在 Rust 中咱们必须指定类型(Point
),更广泛的是,Rust 须要在编译时知道全部类型。但与大多数其余编译语言不一样的是,编译器尽量本身推断类型。
为了进一步解释这个问题,下面是在 C++和许多其余语言中有效的代码。每一个变量都须要明确的类型声明。
int a = 5; float b = 0.5; float c = 1.5 * a;
在 JavaScript 以及 Rust 中,这段代码是有效的:
let a = 5; let b = 0.5; let c = 1.5 * a;
共享功能不胜枚举:
async
+ await
语法。let array = [1,2,3]
同样简单地建立。我能够继续列举下去,但我想个人观点如今已经很清楚了。Rust 有一系列丰富的功能,这些功能在现代 JavaScript 中也有使用。
Rust 是一种编译语言,这意味着没有运行时能够执行 Rust 代码。一个应用程序只能在编译器(rustc
)完成它的魔法以后运行。这种方法的好处一般是更好的性能。
幸运的是,Cargo 为咱们解决了调用编译器的问题。而有了 webpack,咱们还能够将 Cargo
隐藏在 npm run build
后面。有了这个指南,只要为项目设置好 Rust,就能够保留 Web 开发者的正常工做流程。
Rust 是一种强类型语言,这意味着在编译时全部类型必须匹配。例如,你不能调用一个参数类型错误或参数数量错误的函数。编译器会在你运行时遇到这个错误以前为你捕捉到它。显而易见的比较是 TypeScript,若是你喜欢 TypeScript,那么你极可能会喜欢 Rust。
但别担忧:若是你不喜欢 TypeScript,Rust 可能仍是适合你。Rust 是近几年从头开始构建的,它考虑到了过去几十年来人类在编程语言设计方面所学到的一切。其结果是一种使人耳目一新的简洁语言。
Rust 中的模式匹配是我最喜欢的一个特征,其余语言有 switch
和 case
来避免像这样的长链:
if (x == 1) { // ... } else if (x == 2) { // ... } else if (x == 3 || x == 4) { // ... } // ...
Rust 使用了以下更优雅的匹配项:
match x { 1 => { /* Do something if x == 1 */}, 2 => { /* Do something if x == 2 */}, 3 | 4 => { /* Do something if x == 3 || x == 4 */}, 5...10 => { /* Do something if x >= 5 && x <= 10 */}, _ => { /* Catch all other cases */ } }
我认为这是很是整洁的,我但愿 JavaScript 开发人员也能欣赏这种语法扩展。
不幸的是,咱们还得谈谈 Rust 的黑暗面。直言不讳地说,使用严格的类型系统有时会让人感受很是繁琐。若是你认为 C++或 Java 的类型系统很严格,那么请准备好迎接 Rust 的艰难之旅吧。
就我我的而言,我很喜欢 Rust 这部分。我依赖于严格的类型系统,所以能够关闭大脑的一部分——每当我发现本身在编写 JavaScript 时,大脑的一部分就会剧烈地兴奋起来。可是我知道对于初学者来讲,老是和编译器做对是很烦人的。咱们将在稍后的 Rust 教程中看到一些。
如今,让咱们用 Rust 在浏览器中运行一个 hello world
,咱们首先要确保全部必要的工具都已安装。
使用 rustup 安装 Cargo + rustc。 Rustup 是推荐的安装 Rust 的方法,它将安装最新的稳定版 Rust 的编译器(rustc)和包管理器(Cargo)。它将安装 Rust 最新稳定版本的编译器(rustc)和包管理器(Cargo)。它还能够管理 beta 版和每夜构建版,但对于本例来讲,这不是必需的。
cargo --version
来检查安装状况,你应该能够看到 cargo 1.48.0 (65cbdd2dc 2020-10-14)
这样的内容。rustup --version
应该产生 rustup 1.23.0(00924c9ba 2020-11-27)
。安装 wasm-pack。 这是为了将编译器与 npm 集成。
wasm-pack --version
来检查安装,这应该为您提供 wasm-pack 0.9.1
之类的东西。咱们还须要 Node 和 npm。咱们有一篇完整的文章解释了安装这两个的最佳方法。
如今一切都安装好了,让咱们来建立项目。最终的代码也能够在这个GitHub 仓库中找到。咱们从一个能够编译成 npm 包的 Rust 项目开始,以后会有导入该包的 JavaScript 代码。
要建立一个名为 hello-world
的 Rust 项目,请使用 cargo init --lib hello-world
。这将建立一个新目录并生成 Rust 库所需的全部文件:
├──hello-world ├── Cargo.toml ├── src ├── lib.rs
Rust 代码将放在 lib.rs
中,在此以前咱们必须调整 Cargo.toml
。它使用 TOML 定义了依赖关系和其余包的信息。若是想在浏览器中看到 hello world,请在 Cargo.toml
中的某个地方添加如下行数(例如,在文件的最后)。
[lib] crate-type = ["cdylib"]
这告诉编译器在 C 兼容模式下建立一个库。显然咱们在咱们的例子中没有使用 C。C-compatible 只是意味着不是 Rust 专用的,这是咱们使用 JavaScript 中的库所须要的。
咱们还须要两个外部库,将它们做为单独的一行添加到依赖关系部分。
[dependencies] wasm-bindgen = "0.2.68" web-sys = {version = "0.3.45", features = ["console"]}
这些都是来自 crates.io 的依赖项,它是 Cargo 使用的默认包仓库。
wasm-bindgen是必要的,以建立一个咱们之后能够从 JavaScript 中调用的入口点。(你能够在这里找到完整的文档。)值 ”0.2.68"
指定了版本。
web-sys包含了全部 Web API 的 Rust 绑定,它将使咱们可以访问浏览器控制台。请注意,咱们必须明确地选择控制台功能,咱们最终的二进制文件将只包含这样选择的 Web API 绑定。
接下来是 lib.rs
内部的实际代码。自动生成的单元测试能够删除。只需使用如下代码替换文件的内容:
use wasm_bindgen::prelude::*; use web_sys::console; #[wasm_bindgen] pub fn hello_world() { console::log_1("Hello world"); }
顶部的 use
语句是用于从其余模块导入项目。这与 JavaScript 中的 import
相似)。
pub fn hello_world(){...}
声明一个函数。 pub
修饰符是“public”的缩写,做用相似于 JavaScript 中的 export
。注释 #[wasm_bindgen]
特定于 Rust 编译为WebAssembly (Wasm)")")。咱们在这里须要它来确保编译器将包装函数公开给 JavaScript。
在功能主体中,“Hello world”被打印到控制台上。 Rust 中的 console :: log_1()
是对 console.log()
的调用的包装。
你是否注意到函数调用中的 _1
后缀?这是由于 JavaScript 容许使用可变数量的参数,而 Rust 不容许。为了解决这个问题, wasm_bindgen
为每种参数数量生成一个函数。是的,这很快就会变得丑陋!但这有效。 在web-sys 文档中提供了一个能够在 Rust 控制台中调用的完整函数列表。
如今咱们应该已经一切就绪,试着用下面的命令编译它。这将下载全部的依赖项并编译项目,第一次可能会花一些时间。
cd hello-world wasm-pack build
哈!Rust 编译器对咱们不满意。
error[E0308]: mismatched types --> src\lib.rs:6:20 | 6 | console::log_1("Hello world"); | ^^^^^^^^^^^^^ expected struct `JsValue`, found `str` | = note: expected reference `&JsValue` found reference `&'static str
注意:若是您看到其余错误(error: linking with cc failed: exit code: 1
)而且你使用的是 Linux,则说明缺乏交叉编译依赖性。 sudo apt install gcc-multilib
应该能够解决此问题。
正如我前面提到的,编译器很严格。当它指望一个 JsValue
的引用做为一个函数的参数时,它不会接受一个静态字符串。为了知足编译器的要求,必须进行显式转换。
console::log_1(&"Hello world".into());
方法 into()")") 将一个值转换为另外一个值。Rust 编译器很聪明,它能够推迟哪些类型参与转换,由于函数签名只留下了一种可能性。在这种状况下,它将转换为 JsValue
,这是一个由 JavaScript 管理的值的包装类型。而后,咱们还得加上 &
,经过引用而不是经过值来传递,不然编译器又会抱怨。
尝试再次运行 wasm-pack build
,若是一切顺利,则最后一行应以下所示:
[INFO]: :-) Your wasm pkg is ready to publish at /home/username/intro-to-rust/hello-world/pkg.
若是你能走到这一步,你如今就能够手动编译 Rust 了。下一步,咱们将把它与 npm 和 webpack 集成,后者将自动为咱们完成这项工做。
在这个例子中,我决定将 package.json
放在 hello-world
目录内。咱们也能够为 Rust 项目和 JavaScript 项目使用不一样的目录,这是个口味问题。
如下是个人 package.json
文件。遵循的最简单方法是将其复制并运行 npm install
,或者运行 npm init
并仅复制 dev
依赖项:
{ "name": "hello-world", "version": "1.0.0", "description": "Hello world app for Rust in the browser.", "main": "index.js", "scripts": { "build": "webpack", "serve": "webpack serve" }, "author": "Jakob Meier <inbox@jakobmeier.ch>", "license": "(MIT OR Apache-2.0)", "devDependencies": { "@wasm-tool/wasm-pack-plugin": "~1.3.1", "@webpack-cli/serve": "^1.1.0", "css-loader": "^5.0.1", "style-loader": "^2.0.0", "webpack": "~5.8.0", "webpack-cli": "~4.2.0", "webpack-dev-server": "~3.11.0" } }
如你所见,咱们使用的是 webpack 5。Wasm-pack 也能够和旧版本的 webpack 一块儿使用,甚至能够不使用捆绑程序。但每一个设置的工做方式都有些不一样,我建议你在跟随这个 Rust 教程时使用彻底相同的版本。
另外一个重要的依赖项是 wasm-pack-plugin
。这是一个 Webpack 插件,专门用于加载使用 wasm-pack 构建的 Rust 软件包。
继续,咱们还须要建立 webpack.config.js
文件来配置 webpack。它应该是这样的:
const path = require("path"); const webpack = require("webpack"); const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); module.exports = { entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "index.js", }, plugins: [ new WasmPackPlugin({ crateDirectory: path.resolve(__dirname, "."), }), ], devServer: { contentBase: "./src", hot: true, }, module: { rules: [ { test: /\.css$/i, use: ["style-loader", "css-loader"], }, ], }, experiments: { syncWebAssembly: true, }, };
全部的路径都配置为 Rust 代码和 JavaScript 代码并排。index.js
将在 src
文件夹中,紧挨着 lib.rs
。若是你喜欢不一样的设置,能够随时调整这些。
你还会注意到,咱们使用webpack experiments,这是 webpack 5 引入的新选项。请确保将 syncWebAssembly
设置为 true。
最后,咱们必须建立 JavaScript 入口点 src/index.js
:
import("../pkg") .catch((e) => console.error("Failed loading Wasm module:", e) ) .then((rust) => rust.hello_world());
咱们必须异步加载 Rust 模块。调用 rust.hello_world()
会调用一个生成的封装函数,而这个函数又会调用 lib.rs
中定义的 Rust 函数 hello_world
。
如今,运行 npm run serve
应该能够编译全部内容并启动开发服务器。咱们没有定义 HTML 文件,所以页面上没有任何显示。你可能还必须手动转到 http://localhost:8080/index
,由于http://localhost:8080
只是列出文件而不执行任何代码。
打开空白页后,打开开发人员控制台。 Hello World 应该有一个日志条目。
好吧,对于一个简单的 hello world 来讲,这是至关多的工做。但如今一切都到位了,咱们能够轻松地扩展 Rust 代码,而不用担忧这些。保存对 lib.rs
的修改后,你应该会自动看到从新编译和浏览器中的实时更新,就像 JavaScript 同样。
Rust 不是 JavaScript 的通常替代品。它只能经过 Wasm 在浏览器中运行,这在很大程度上限制了它的做用。即便你能够用 Rust 替换几乎全部的 JavaScript 代码,若是你真的想的话,那是一个坏主意,并且不是 Wasm 的目的。例如,Rust 并不适合与你网站的 UI 进行交互。
我认为 Rust + Wasm 是一个额外的选项,能够用来更有效地运行 CPU 重的工做负载。以较大的下载量为代价,Wasm 避免了 JavaScript 代码面临的解析和编译开销。这一点,再加上编译器的强力优化,可能会带来更好的性能。这一般是公司为特定项目选择 Rust 的缘由。选择 Rust 的另外一个缘由多是语言偏好,但这是一个彻底不一样的讨论,我不会在这里讨论。