likes
comments
collection
share

JavaScript 开发者的Rust 教程:从 0 到 80%[译文]

作者站长头像
站长
· 阅读数 16

作者:Michael Salim

原文链接:Rust from 0 to 80% for JavaScript Developers

译文首发链接:zhuanlan.zhihu.com/p/515590234 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果你是一名 JavaScript 开发者,这里是一个话题列表,可以帮助你快速理解 Rust。有非常多关于 Rust 从零开始的教程。但是如果你已经知道一些 JS 的知识,为什么不比较下它们和 rust 的差异呢?

这些是我希望在你开始学习 Rust 前,要知道的一些差异。

免责声明!

我离非常熟悉 Rust 还有不少的距离。这些只是在我有限的知识里解释这些概念,仅此而已。如果你想要知道细节,你还是应该去阅读 Rust 的文档。学习rust 去阅读一些 rust 的书也是很好的开始。我的目标是,列出来对于你(还有未来的我)可以忽略的常识性编程概念,可以专注在作为 JavaScript 开发者已经了解的知识与 rust 的差异。

类型

Rust 是一个类型语言,更像 TypeScript。如果你已经了解 TypeScript,相信你去学 rust 会有更好的体验。

有很多语法,rust 和 TS 是一样的,例如 variable_name: Type。

snake_case

习惯它吧。

这些符号是什么?

问号(?)

你会在很多方程调用后面看到 ? :my_function()?

这个可不是可选链。这个是处理可以错误的函数的错误处理语法糖(译者:magic 翻译为语法糖)。当处理异步函数时,会大量用到这个语法。后面也会详细讲相关的内容。

(译者注:rust 大量吸收了函数式编程的精华,内置了非常强大的容器系统,每个函数都返回一个可能成功或者失败的容器。不需要明白任何函数式编程的内容,只要习惯这种写法就好了。成本是,每个函数都要自己处理好成功做什么,失败做什么。 rust 的错误处理机制非常好,但是需要每个开发自己实现自己的错误处理机制,这一点在初学时可以先忽略,有个出名的 anyhow 包,用起来就好了。)

方程后面的感叹号(!)

例如:println!("{:?}", my_variable);

这个符号说明 println 是一个宏。JS 没有这样的语法。宏时用代码在编译时生成代码的技术。你可以认为是一系列的语法糖。使用这些宏就好了。

& 符号

例如:&your_variable

这个符号获得了这个变量的引用。如果你用过 C 或者 go,应该见过这个符号。

语法

  1. 分号 ; 在每个代码行最后,必须写,这一点和 JS 不一样。
  2. 例外:如果有一行没有写分号,是 return 的语法糖
  3. 函数声明的语法和 JS/TS 不太一样。习惯就好了
fn foo(num: i32) -> i32 {
  3 // 见第二点
  // 或者
  // return 3;
}

  1. 装饰器语法也不一样。被称为 Attributes。

这些关键字是什么?

struct

这个相当于一个 JSON 对象。(当然这么说是为了更符合 JavaScript 的感觉,更细节请参阅官方文档。但是初期这么理解没什么问题。)

// TypeScript
type Person = {
  firstName: string;
  lastName: string;
};

// Rust
struct Person {
    first_name: String,
    last_name: String,
}

trait

可以理解为 interface。(译者注,TypeScript 的 interface 和 trait 非常像,而且因为 TypeScript 的interface 本身支持鸭子类型,实际上可以比 trait 的限制更小,也就是说,实际上两者可以设计为有相当一样的编程范式,当然前提是如果你愿意。)

impl

trait 的实现。和 JS 里比较相近的概念是 class(译者:里的 implement)。 这个语法是 trait 和 类型的桥梁。我还没用过。

enum

和 TypeScript 的 enum 类似。但是你可以往里面存数据。在异步代码里是一个非常重要的概念,非常强大。

Console.log

很遗憾,比 JS 的复杂很多。更像其他语言的 printf。

println!("{:?}", my_variable);

库/依赖

使用 Cargo.toml, 而不是 package.json。你需要手动增加依赖,而不是使用像 yarn add 这样的命令。

文档

例子:

[dependencies]
chrono = "0.4"
egg-mode = "0.16.0"

Importing(外部依赖)

Rust 有模块系统。(译者注:JS有一个一样好的就好了。)和 JS 的区别非常大。

更像名字空间。下面是一个引入依赖的例子:

use rocket::serde::{json::Json, Deserialize, Serialize};

use 相当于 js 的 import。

rocket 是包的名字。

:: 获取模块的符号。

serde 模块的名字

{json::Json, Deserialize, Serialize} 引入模块内具体的东西。

一些更多的语法:

use chrono::prelude::*;

use rusqlite::Result;

Importing 本地文件

最好的解释文档:File hierarchy - Rust By Example

使用 mod 去告诉编译器你想引入的文件夹或者文件。

然后使用 use 去引入你想引入的内容。也有不写 mod 的方法,需要使用 crate 前缀。(译者注:crate 相当于我们一般在 tsconfig 或者 vite 里设置的 @ 的快捷链接,相当于从根目录开始寻址。)

use crate::your_file_or_module;

注意:mod.rs 是一个特殊的文件名相当于 index.js

更多的例子请看上面的链接。(译者注:rust 这一块非常复杂,因为类的 public private 等访问限制也是在这一层做的,和 js 不一样,rust 可以在一个文件内有多个模块。当这些规则混在一起时,有非常多的可能性。但是一般来说,形成自己的习惯只是时间的问题。初学 rust 会觉得它没有 oop 的设计,但是实际上它不是没有,而是把 oop 的设计四散到例如模块管理,trait 实现、装饰器、宏等地方。rust 的表现力非常强,如果你喜欢语言的表现力,rust 绝对会让你爱不释手。而且 rust 的代码比 c++ 好读太多了。)

Const 与 let

(译者注:与 js 完全不一样,这一点要谨记在心)

在 JavaScript 里使用 const 去声明一个不可变变量。(对于复杂类型,表示地址不可变)

在 Rust 里,这一点实际是由 let 实现的。并且不可变是默认的情况。如果你想要可变,你需要加上 mut 关键字。const 是为了给实际常量(表示你不可以从其他变量计算获得这个值)使用的。

let immutable_variable = ...;
let mut mutable_variable = ...;
const MY_CONSTANT = "CONSTANT";

库文档

如果你在 github 上找不到这个链接,你可以在 crates.io 的对应页面找:

JavaScript 开发者的Rust 教程:从 0 到 80%[译文]

编辑

添加图片注释,不超过 140 字(可选)

JavaScript 开发者的Rust 教程:从 0 到 80%[译文]

编辑

添加图片注释,不超过 140 字(可选)

异步

到目前为止,rust 最让我迷惑的两个概念是 futures 和 ownership。如果你想要了解这些概念,我建议你找专门的文档好好学习他们。这里我们先谈 future。

Future 和 Promise 有点像。但是不像 JS,Rust 有一个对于 promise/future 的结果类型 Result。这个类型可以接收错误的范型(我希望 JS 也有这个类型,译者注:我也希望)。你也可以在不使用 future 的时候用 Result。

执行(或者消费)future

原生的库因为太裸了,并不能直接使用,你需要使用外部的库(有点像 JS 的 bluebird)。你需要一个执行器去执行 future。我推荐 github.com/tokio-rs/to… ,请详细阅读它的文档。

.await 去 await 一个方程

async_function().await; 这是一个有趣的语法,不是么?你不需要像 JS 一样加括号。

处理 Result

这是非常重要的一个概念。Rust 是一个安全的语言,意味着你要处理所有的事情(译者注:很多人认为 rust 是一个安全的语言是 rust 帮你处理了安全,这种理解是不对的,还是开发者处理的,rust 暴漏了更多问题给开发者,在编写时要去处理。)。是的,不像 JS,所有的错误问题(译者注:和 TypeScript 有类似的地方,但是比 TypeScript 要更严格,尤其是内存方面。特别希望 TypeScript 也能出一个生命周期的机制,处理变量深浅拷贝的问题太让人郁闷了)。

Result 枚举类型有两个属性,一个是 Ok,一个是 Err。如果 future 成功了,返回 Ok,否则返回 Err。

一个例子:

let f = File::open("hello.txt");

let mut f = match f {
    Ok(file) => file,
    Err(e) => return Err(e),
};

上面这个例子用了 Rust 异常强大的模式识别(译者注:太强大了,以至于所有的语言几乎都在 2022年开始抄这个特性)。

这么写太麻烦了,有两种通用的办法简化它:

  1. 使用 .unwrap()

例子:let my_value = async_function().await.unwrap();

这个写法会在成功时获得值,失败时 panic。

使用这个写法,你最好确定一定不会有问题。

  1. 使用 ? 语法

这个会把 error 传递下去。所以你的函数也要传递 error(一般通过 Result 或者 Option)

更多的例子请参考

Ownership 和 References

你听说过 rust,应该听说过 borrow checker 吧?我在这不会说太多。这是这里最难的部分,因为这是 rust 独有的东西。如果你以前从来没有处理过 references,这一块对你会非常难。

但是,感谢 rust 的文档:简单说,好好读4.1,4.2,4.3。(译者注:这一块一开始不明白是无所谓的,这一块必须通过时间,和写的代码多了去理解,光看是没意义的。)

嗯,就这些!

这个列表比我想象的还要短。希望对你的 rust 之旅有帮助。