Rust 是 2010 年起源于 Mozilla Research 的一种编程语言。现在,所有大公司都在应用它。
亚马逊和微软都认可它是其零碎中 C / C ++ 的最佳替代品,然而 Rust 并不止于此。像 Figma 和 Discord 这样的公司当初也通过在客户端利用中应用 Rust 来引领潮流。
本篇 Rust 教程旨在简要介绍 Rust,如何在浏览器中应用它,以及何时应该思考应用它。我将从比拟 Rust 和 JavaScript 开始,而后疏导你实现 Rust 在浏览器中运行的步骤。最初,我将介绍一个应用 Rust 和 JavaScript 的 COVID simulator web 应用程序的疾速性能评估。
简而言之
Rust 在概念上与 JavaScript 十分不同。但也有相似之处须要指出,让咱们来看看问题的两面。
相似之处
这两种语言都有一个现代化的包管理系统。JavaScript 有 npm,Rust 有 Cargo。Rust 有 Cargo.toml
来代替 package.json
进行依赖治理。要创立一个新的我的项目,应用 cargo init
,要运行它,应用 cargo run
。不太生疏吧?
Rust 中有许多很酷的性能,你曾经从 JavaScript 中晓得了,只是语法略有不同。利用这个常见的 JavaScript 模式,对数组中的每个元素都利用一个闭包:
let staff = [{ name: "George", money: 0},
{name: "Lea", money: 500000},
];
let salary = 1000;
staff.forEach((employee) => {employee.money += salary;});
在 Rust 中,咱们能够这样写:
let salary = 1000;
staff.iter_mut().for_each(|employee| { employee.money += salary;}
);
诚然,习惯这种语法须要工夫,用管子 (|
) 代替括号。但在克服了最后的难堪之后,我发现它比另一组括号读起来更清晰。
再举一个例子,这是 JavaScript 中的对象解构:
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;
共享性能举不胜举:
- Rust 具备
async
+await
语法。 - 数组能够像让
let array = [1,2,3]
一样简略地创立。 - 代码按模块组织,有明确的导入和导出。
- 字符串是用 Unicode 编码的,解决特殊字符没有问题。
我能够持续列举上来,但我想我的观点当初曾经很分明了。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 教程中看到一些。
Hello 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:
rustup --version
应该产生rustup 1.23.0(00924c9ba 2020-11-27)
。
装置 wasm-pack。 这是为了将编译器与 npm 集成。
- 通过输出
wasm-pack --version
来查看装置,这应该为您提供wasm-pack 0.9.1
之类的货色。
咱们还须要 Node 和 npm。咱们有一篇残缺的文章解释了装置这两个的最佳办法。
编写 Rust 代码
当初所有都装置好了,让咱们来创立我的项目。最终的代码也能够在这个 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 集成,后者将主动为咱们实现这项工作。
JavaScript 整合
在这个例子中,我决定将 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
Rust 不是 JavaScript 的个别替代品。它只能通过 Wasm 在浏览器中运行,这在很大水平上限度了它的作用。即便你能够用 Rust 替换简直所有的 JavaScript 代码,如果你真的想的话,那是一个坏主意,而且不是 Wasm 的目标。例如,Rust 并不适宜与你网站的 UI 进行交互。
我认为 Rust + Wasm 是一个额定的选项,能够用来更无效地运行 CPU 重的工作负载。以较大的下载量为代价,Wasm 防止了 JavaScript 代码面临的解析和编译开销。这一点,再加上编译器的强力优化,可能会带来更好的性能。这通常是公司为特定项目选择 Rust 的起因。抉择 Rust 的另一个起因可能是语言偏好,但这是一个齐全不同的探讨,我不会在这里探讨。