当前位置:主页 > 查看内容

针对JavaScript开发人员的Rust简介

发布时间:2021-05-29 00:00| 位朋友查看

简介:Rust是2010年起源于Mozilla Research的一种编程语言。如今,所有大公司都在使用它。 亚马逊和微软都认可它是其系统中C / C ++的最佳替代品,但是Rust并不止于此。像Figma和Discord这样的公司现在也通过在客户端应用中使用Rust来引领潮流。 本篇Rust教程旨在……

 

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模式,对数组中的每个元素都应用一个闭包:

  1. let staff = [ 
  2.    {name"George", money: 0}, 
  3.    {name"Lea", money: 500000}, 
  4. ]; 
  5. let salary = 1000; 
  6. staff.forEach( (employee) => { employee.money += salary; } ); 

在Rust中,我们可以这样写:

  1. let salary = 1000; 
  2. staff.iter_mut().for_each(  
  3.     |employee| { employee.money += salary; } 
  4. ); 

诚然,习惯这种语法需要时间,用管子( | )代替括号。但在克服了最初的尴尬之后,我发现它比另一组括号读起来更清晰。

再举一个例子,这是JavaScript中的对象解构:

  1. let point = { x: 5, y: 10 }; 
  2. let {x,y} = point; 

同样在Rust中:

  1. let point = Point { x: 5, y: 10 }; 
  2. let Point { x, y } = point; 

主要的区别是,在Rust中我们必须指定类型(Point),更普遍的是,Rust需要在编译时知道所有类型。但与大多数其他编译语言不同的是,编译器尽可能自己推断类型。

为了进一步解释这个问题,下面是在C++和许多其他语言中有效的代码。每个变量都需要明确的类型声明。

  1. int a = 5; 
  2. float b = 0.5; 
  3. float c = 1.5 * a; 

在JavaScript以及Rust中,这段代码是有效的:

  1. let a = 5; 
  2. let b = 0.5; 
  3. 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 来避免像这样的长链:

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

Rust使用了如下更优雅的匹配项:

  1. match x { 
  2.   1 => { /* Do something if x == 1 */}, 
  3.   2 => { /* Do something if x == 2 */}, 
  4.   3 | 4 => { /* Do something if x == 3 || x == 4 */}, 
  5.   5...10 => { /* Do something if x >= 5 && x <= 10 */}, 
  6.   _ => { /* 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。我们有一篇完整的文章[1]解释了安装这两个的最佳方法。

编写Rust代码

现在一切都安装好了,让我们来创建项目。最终的代码也可以在这个GitHub仓库[2]中找到。我们从一个可以编译成npm包的Rust项目开始,之后会有导入该包的JavaScript代码。

要创建一个名为 hello-world 的Rust项目,请使用 cargo init --lib hello-world。这将创建一个新目录并生成Rust库所需的所有文件:

  1. ├──hello-world 
  2.     ├── Cargo.toml 
  3.     ├── src 
  4.         ├── lib.rs 

Rust代码将放在 lib.rs 中,在此之前我们必须调整 Cargo.toml。它使用 TOML[3] 定义了依赖关系和其他包的信息。如果想在浏览器中看到hello world,请在 Cargo.toml 中的某个地方添加以下行数(例如,在文件的最后)。

  1. [lib] 
  2. crate-type = ["cdylib"

这告诉编译器在C兼容模式下创建一个库。显然我们在我们的例子中没有使用C。C-compatible只是意味着不是Rust专用的,这是我们使用JavaScript中的库所需要的。

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

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

这些都是来自 crates.io[4] 的依赖项,它是 Cargo 使用的默认包仓库。

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

web-sys[6]包含了所有Web API的Rust绑定,它将使我们能够访问浏览器控制台。请注意,我们必须明确地选择控制台功能,我们最终的二进制文件将只包含这样选择的Web API绑定。

接下来是 lib.rs 内部的实际代码。自动生成的单元测试可以删除。只需使用以下代码替换文件的内容:

  1. use wasm_bindgen::prelude::*; 
  2. use web_sys::console; 
  3.  
  4. #[wasm_bindgen] 
  5. pub fn hello_world() { 
  6.     console::log_1("Hello world"); 

顶部的 use 语句是用于从其他模块导入项目。这与JavaScript中的 import 类似)。

pub fn hello_world(){...} 声明一个函数。pub 修饰符是“public”的缩写,作用类似于JavaScript中的 export。注释 #[wasm_bindgen] 特定于Rust编译为[WebAssembly (Wasm)](https://webassembly.org/ "wasm_bindgen] 特定于Rust编译为[WebAssembly (Wasm "wasm_bindgen] 特定于Rust编译为[WebAssembly (Wasm)")")。我们在这里需要它来确保编译器将包装函数公开给JavaScript。

在功能主体中,“Hello world”被打印到控制台上。Rust中的 console :: log_1() 是对 console.log() 的调用的包装。

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

现在我们应该已经一切就绪,试着用下面的命令编译它。这将下载所有的依赖项并编译项目,第一次可能会花一些时间。

  1. cd hello-world 
  2. wasm-pack build 

哈!Rust编译器对我们不满意。

  1. error[E0308]: mismatched types 
  2.  --> src\lib.rs:6:20 
  3.   | 
  4. 6 |     console::log_1("Hello world"); 
  5.   |                    ^^^^^^^^^^^^^ expected struct `JsValue`, found `str` 
  6.   | 
  7.   = note: expected reference `&JsValue` 
  8.              found reference `&'static str 

注意:如果您看到其他错误(error: linking with cc failed: exit code: 1)并且你使用的是Linux,则说明缺少交叉编译依赖性。sudo apt install gcc-multilib 应该可以解决此问题。

正如我前面提到的,编译器很严格。当它期望一个 JsValue 的引用作为一个函数的参数时,它不会接受一个静态字符串。为了满足编译器的要求,必须进行显式转换。

  1. console::log_1(&"Hello world".into()); 

方法 [into()](https://doc.rust-lang.org/std/convert/trait.Into.html "into( "into()")") 将一个值转换为另一个值。Rust 编译器很聪明,它可以推迟哪些类型参与转换,因为函数签名只留下了一种可能性。在这种情况下,它将转换为 JsValue,这是一个由JavaScript管理的值的包装类型。然后,我们还得加上 &,通过引用而不是通过值来传递,否则编译器又会抱怨。

尝试再次运行 wasm-pack build,如果一切顺利,则最后一行应如下所示:

  1. [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 依赖项:

  1.     "name""hello-world"
  2.     "version""1.0.0"
  3.     "description""Hello world app for Rust in the browser."
  4.     "main""index.js"
  5.     "scripts": { 
  6.         "build""webpack"
  7.         "serve""webpack serve" 
  8.     }, 
  9.     "author""Jakob Meier <inbox@jakobmeier.ch>"
  10.     "license""(MIT OR Apache-2.0)"
  11.     "devDependencies": { 
  12.         "@wasm-tool/wasm-pack-plugin""~1.3.1"
  13.         "@webpack-cli/serve""^1.1.0"
  14.         "css-loader""^5.0.1"
  15.         "style-loader""^2.0.0"
  16.         "webpack""~5.8.0"
  17.         "webpack-cli""~4.2.0"
  18.         "webpack-dev-server""~3.11.0" 
  19.     } 

如你所见,我们使用的是webpack 5。Wasm-pack也可以和旧版本的webpack一起使用,甚至可以不使用捆绑程序。但每个设置的工作方式都有些不同,我建议你在跟随这个Rust教程时使用完全相同的版本。

另一个重要的依赖项是 wasm-pack-plugin。这是一个Webpack插件,专门用于加载使用wasm-pack构建的Rust软件包。

继续,我们还需要创建 webpack.config.js 文件来配置webpack。它应该是这样的:

  1. const path = require('path'); 
  2. const webpack = require('webpack'); 
  3. const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); 
  4.  
  5. module.exports = { 
  6.     entry: './src/index.js'
  7.     output: { 
  8.         path: path.resolve(__dirname, 'dist'), 
  9.         filename: 'index.js'
  10.     }, 
  11.     plugins: [ 
  12.         new WasmPackPlugin({ 
  13.             crateDirectory: path.resolve(__dirname, "."
  14.         }), 
  15.     ], 
  16.     devServer: { 
  17.         contentBase: "./src"
  18.         hot: true
  19.     }, 
  20.     module: { 
  21.         rules: [{ 
  22.             test: /\.css$/i, 
  23.             use: ["style-loader""css-loader"], 
  24.         }, ] 
  25.     }, 
  26.     experiments: { 
  27.         syncWebAssembly: true
  28.     }, 
  29. }; 

所有的路径都配置为Rust代码和JavaScript代码并排。index.js 将在 src 文件夹中,紧挨着 lib.rs。如果你喜欢不同的设置,可以随时调整这些。

你还会注意到,我们使用webpack experiments[8],这是webpack 5引入的新选项。请确保将 syncWebAssembly 设置为true。

最后,我们必须创建JavaScript入口点 src/index.js:

  1. import("../pkg").catch(e => console.error("Failed loading Wasm module:", e)).then
  2.     rust => 
  3.         rust.hello_world() 
  4. ); 

我们必须异步加载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的另一个原因可能是语言偏好,但这是一个完全不同的讨论,我不会在这里讨论。

参考资料

[1]文章: https://www.sitepoint.com/quick-tip-multiple-versions-node-nvm/

[2]GitHub仓库: https://github.com/sitepoint-editors/rust-wasm-hello-world

[3]TOML: https://github.com/toml-lang/toml

[4]crates.io: https://crates.io/

[5]wasm-bindgen: https://crates.io/crates/wasm-bindgen

[6]web-sys: https://crates.io/crates/web-sys

[7]web-sys文档: https://rustwasm.github.io/wasm-bindgen/api/web_sys/console/index.html

[8]webpack experiments: https://webpack.js.org/configuration/experiments/

本文转载自微信公众号「前端全栈开发者」,可以通过以下二维码关注。转载本文请联系前端全栈开发者公众号


本文转载自网络,原文链接:https://mp.weixin.qq.com/s/0YJwk9mBts8by6Lx68a_6Q
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐