Rust教程。锈的介绍--JavaScript开发人员的Rust介绍

241 阅读9分钟

Rust是一种编程语言,2010年起源于Mozilla研究院。今天,所有的大公司都在使用它。

亚马逊微软都认可它是他们系统中C/C++的最佳替代品。但Rust并没有止步于此。像FigmaDiscord这样的公司现在也在他们的客户端应用程序中使用Rust,从而引领了潮流。

本篇Rust教程旨在简要介绍Rust,如何在浏览器中使用它,以及何时应该考虑使用它。我将首先比较Rust和JavaScript,然后引导你完成在浏览器中启动和运行Rust的步骤。最后,我将介绍我的COVID模拟器网络应用的快速性能评估,该应用使用了Rust和JavaScript。

Rust的简述

Rust在概念上与JavaScript非常不同。但也有一些相似之处需要指出。让我们来看看这枚硬币的两面。

相同点

两种语言都有一个现代的包管理系统。JavaScript有npm,Rust有Cargo。Rust有package.json ,而不是Cargo.toml ,用于依赖性管理。要创建一个新项目,使用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,就可以保留网络开发者的正常工作流程。

Rust是一种强类型语言,这意味着所有类型在编译时必须匹配。例如,你不能用错误类型的参数或错误数量的参数来调用一个函数。编译器会在你运行时遇到错误之前为你捕捉到这个错误。明显的比较是TypeScript。如果你喜欢TypeScript,那么你很可能会喜欢Rust。

但不要担心:如果你不喜欢TypeScript,Rust可能仍然适合你。Rust是近年来从头开始构建的,考虑到了人类在过去几十年中对编程语言设计的所有认识。其结果是一种令人耳目一新的简洁语言。

Rust中的模式匹配是我最喜欢的一个功能。其他语言有switchcase ,以避免像这样的长链。

if ( x == 1) { 
    // ... 
} else if ( x == 2 ) {
    // ...
}
else if ( x == 3 || x == 4 ) {
    // ...
} // ...

Rust使用更优雅的match ,像这样工作。

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

现在,让我们用Rust在浏览器中运行一个hello world 。我们首先要确保所有必要的工具都已安装。

工具

  1. 使用rustup安装Cargo + rustc。Rustup是安装Rust的推荐方式。它将为最新的稳定版本的Rust安装编译器(rustc)和软件包管理器(Cargo)。它也可以管理测试版和夜间版,但对这个例子来说没有必要。
    • 在终端键入cargo --version ,检查安装情况。你应该看到类似cargo 1.48.0 (65cbdd2dc 2020-10-14) 的东西。
    • 同时检查Rustup:rustup --version 应该产生rustup 1.23.0 (00924c9ba 2020-11-27)
  2. 安装wasm-pack。这是为了将编译器与npm集成。
    • 通过输入wasm-pack --version 来检查安装情况,你应该看到类似wasm-pack 0.9.1 的东西。
  3. 我们还需要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-兼容只是意味着不是Rust专用的,这就是我们需要使用JavaScript的库的原因。

我们还需要两个外部库。将它们作为单独的行添加到依赖项部分。

[dependencies]
wasm-bindgen = "0.2.68"
web-sys = {version = "0.3.45", features = ["console"]}

这些是来自crates.io的依赖,crates.io是Cargo使用的默认包库。

wasm-bindgen是创建一个入口点的必要条件,我们以后可以从JavaScript中调用。(你可以在这里找到完整的文档。)值"0.2.68" ,指定版本。

web-sys包含Rust与所有Web API的绑定。它将使我们能够访问浏览器的控制台。注意,我们必须明确选择控制台功能。我们最终的二进制文件将只包含像这样选择的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() { ... } 声明了一个函数。 修饰符是 "public "的缩写,作用类似于JavaScript中的 。注释 是Rust编译到pub export #[wasm_bindgen] WebAssembly(Wasm)时特有的。我们在这里需要它来确保编译器向JavaScript暴露一个包装函数。

在函数的主体中,"Hello world "被打印到控制台。在Rust中,console::log_1() 是一个调用console.log() 的包装器。(在这里阅读更多内容。)

你是否注意到函数调用处的后缀_1 ?这是因为JavaScript允许可变数量的参数,而Rust不允许。为了解决这个问题,wasm_bindgen ,为每个参数的数量生成一个函数。是的,这可能很快就会变得很难看!但它是有效的。但它是有效的。在Rust中可以在控制台调用的函数的完整列表可以在web-sys文档中找到。

我们现在应该已经准备好了一切,。试着用下面的命令来编译它。这将下载所有的依赖项并编译该项目。第一次可能要花点时间。

cd hello-world
wasm-pack build

Huh!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整合起来,它们将自动为我们完成这项工作。

继续阅读Rust教程。SitePoint为JavaScript开发人员介绍Rust