背景
我写这篇文章时,是去年写的,就是全面放开以后(羊了个羊),公司就我没有中招,闲着也是闲着,找点乐子充实一下无聊生活,都说 Rust 很火,前端基建都拥抱它。
为什么写完没有发了,电脑坏了,后面修复了,就一直忙工作,这不快过年了,又闲的慌,把它找出来,整理一下,也是一个学习总结吧。
其实一开始我只是把它当作一个学习对比心态来写这个文章,在今年参加一个 c++
考试改变我这个心态。语言是相通的,它们是有共性的,到考试前我也没有时间看书,去真正了解 c++
语法特性,考前做了几套模拟试卷,考试前一晚还在加班,空余时间就问了 ChatGPT
几个关于 c++
语言层面的问题,它告诉我一些大概的写法和简单概念。最终考试 77
,最后一道大题15分没做,要我打开一个文件,对文件里的内容进行排序然后打印出来。这要我用 Nodejs
写,很容易,要我用 c++
写就不是那么容易了。
为什么能这么轻松应对考试,那是因为我写了这篇文章,一个一个语法去实践,去类比。虽然 c++
和 Rust
很多地方不一样。只要你有语言基础(特别有静态类型的语言),学其他语言都能快速入门。
废话不多说,赶紧上车吧!
开发环境搭建
古人云:"工欲善其事,必先利其器"。
学习一门新语言,先要搭建必要语言运行环境。
前往 www.rust-lang.org/install.htm... 并按照说明安装 Rust
。
Windows
需要安装 Microsoft C++ Build Tools,也不是必须,如果你有安装Visual Studio
可以跳过。(注意 :不是vs code
)
我是Windows
, 下载rustup-init.exe
文件。
打开它,根据自己选择:
- Proceed with installation (default)
- Customize installation
- Cancel installation
选择 1
,一路回车就行了。出现 Rust is installed now. Great!
安装完成了。
被安装在 C:\Users\{PC}\.cargo\bin
里:
text
cargo.exe
cargo-clippy.exe
cargo-fmt.exe
cargo-miri.exe
clippy-driver.exe
rls.exe
rustc.exe
rustdoc.exe
rustfmt.exe
rust-gdb.exe
rust-gdbgui.exe
rust-lldb.exe
rustup.exe
打开命令行:
bash
rustc --version
然后就会看到:
bash
rustc 1.67.1 (d5a82bbd2 2023-02-07)
如果有表示你安装成功了,如果没有,把 C:\Users\{PC}\.cargo\bin
添加到环境变量 PATH 里,如果没有生效,重启一下即可。
如果一切正确但
Rust
仍不能使用,建议重装个系统,实在不行换个电脑。再不行就放弃治疗,别玩了,浪费生命。
前端开发一般都用 Nodejs。
在官网下载 nodejs.exe
或者使用 nvm 安装。
第一件事就是查看版本:
bash
node -v
npm -v
那对应 Rust
bash
rustc -V
cargo -V
注意 :node和npm 使用是
-v
小写,rustc和cargo 使用是-V
大写。
通过 rustc
把 .rs
文件编译成执行文件 .exe
,它是是 Rust
的编译器,类似于 Typescript
中的 tsc
,把 .ts
编译成 .js
。
cargo
就是是 Rust
的包管理器,类似于 Nodejs
中的 npm
。
对于其他系统安装,这里不展开,自行查阅官网的文档安装
Hello World
网上流行:当我们学习一门编程语言时,都是从 Hello, World!
开始。
学习 Rust
也不例外。
我使用 VS code 作为开发工具。
- rust-analyzer Rust 官方插件
先用 Typescript
实现,然后使用 ts-node 运行:
需要建立一个项目:
hello.ts
ts
function main() {
console.log("Hello, world!");
}
(() => {
main();
})()
命令行运行:
bash
npx tsc hello.ts
需要建立一个项目:
rs
fn main() {
println!("Hello, world!");
}
命令行运行:
bash
rustc hello.rs
./hello # Windows 上需要输入 `.\hello`
很多语言都用一个入口函数(main 函数)。你发现平常写 js 都没有这种讲究,实际你已经在使用入口函数,只不过方式不同,现代框架里面都是 SPA 单入口形式,Webpack 默认配置打包就是
main.js
。
有了一门语言的基础,学起其他语言来相对比较轻松,学习一门语言基础无非就是:
- 数据类型与数据结构
- 变量定义与常量
- 条件判断与循环语句
- 字符串操作与数字运算符和运算
- 函数和数组(元组)
- 类(对象、结构体)
- 模块(包管理)
- 错误处理
以上列举一些基础。相当于是学习新语言的模板。
特此说明:以下所有写法尽量靠近 Rust 语法。所有的 Rust 都是包裹在 fn main() {} 里,后面除了特殊要求写明外。
注意:
- Rust 一定要注意分号(;)的使用,在 JSer 界是流派(要分号流,不要分号流),但在 Rustacean 界强制使用。
- Rust 一定要注意引号(")的使用,在 JSer 界是流派(单引号流,双引号流),但在 Rustacean 界强制使用双引号。
打印输出日志
- 基本写法:
Typescript
:
ts
console.log('hello world!!');
Rust
:
rs
println!("hello world!!");
- 占位符写法
Typescript
:
ts
console.log('hello world!! %d', 100_000);
// 多个占位符
console.log('%d hello world!! %d', 100_000, 100_000);
Typescript
占位符的种类:
- %s 类型是 String 字符串
- %d or %i 类型是 Integer 整数
- %f 类型是 Float 浮点数
- %o 类型是 Object 对象
Typescript
里使用 %[s|d|f|o]
作为占位符,如果需要输出 %[s|d|f|o]
占位符,需要 %%[s|d|f|o]
这样书写。
Rust
:
rs
println!("hello world!! {}", 100_000);
// 多个占位符
println!("{} hello world!! {}", 100_000, 100_000);
// 更简单写法
println!("{0} hello world!! {0}", 100_000);
- 打印基本变量值
Typescript
:
ts
let num: number = 100_000;
console.log(`number: ${num}`);
Rust
:
rs
let num: u32 = 100_000;
println!("number: {num}");
Rust
里使用 {}
作为占位符,如果需要输出 {}
字符串,需要 {{}}
这样书写。
- 打印复杂变量值
Typescript
:
ts
let num: number = 100_000;
console.log(`number: ${num}`);
Rust
:
rs
let num: u32 = 100_000;
println!("number: {num}");
数据类型
Typescript
和 JavaScript
整体书写语法层面,并没有太大差别,最大区别就是类型系统。 JavaScript
就被戏称 AnyScript
。
概念:
- 数据类型是指编程语言中用来定义数据的分类或类型。
- 数据结构是指在计算机中组织和存储数据的方式和方法。数据类型是数据的分类,而数据结构是数据的组织方式。
常见的数据类型包括:
- 布尔类型(Boolean):表示真(true)或假(false)的值。
- 数字类型(Number):表示整数或浮点数。
- 字符串类型(String):表示文本。
- 数组类型(Array):表示按顺序排列的值的集合。
- 对象类型(Object):表示键值对的集合。
- 函数类型(Function):表示可执行的代码块。
- 空类型(Null):表示一个空值。
- 未定义类型(Undefined):表示一个未定义的值。
基本所有语言都包含这些类型,只是叫法不一样而已,有些有更多扩展类型。
常见的数据结构包括:
- 数组(Array):按顺序存储多个值的集合。
- 链表(Linked List):通过节点和指针连接起来的数据结构。
- 栈(Stack):遵循后进先出(LIFO)原则的数据结构。
- 队列(Queue):遵循先进先出(FIFO)原则的数据结构。
- 树(Tree):由节点和边组成的层次结构。
- 图(Graph):由节点和边组成的非线性数据结构。
- 哈希表(Hash Table):使用键值对存储数据的数据结构。
- 堆(Heap):按照特定规则组织的完全二叉树。
数据结构定义了数据如何组织和存储。有些语言对这些数据结构做了封装直接提供使用。几乎所有语言都提供了数组这种底层的数据结构。
数据类型和数据结构是编程中非常重要的概念。了解不同的数据类型和数据结构可以帮助你更好地组织和处理数据,提高代码的效率和可读性。
变量定义与常量
变量是存储在内存中的值,这就意味着在创建变量时会在内存中开辟一个空间。
基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中。
因此,变量可以指定不同的数据类型,这些变量可以存储整数,浮点数或字符。
变量的命名规则
- 变量名中可以包含
字母
、数字
和下划线
,例如:user_name
、age18
- 变量名必须以
字母
或下划线
开头,例如:abc
,_abc
- 变量名是
区分大小
写的,例如:Abc
和abc
是 2 个 变量 - 变量名不能是语言中的
关键字
、保留字
,例如:for
、package
Typescript
和 Rust
基本都遵守上面规则。
Rust
不能使用 $
,否则就会抛出 expected pattern
错误。
Typescript
是允许使用 $
,当你看见 $
就会想起 jQuery,看见 click$
就知道你在使用 Rxjs。
声明语法
定义变量
使用 let
关键字定义变量
text
let variable_name = value; // 不指定变量类型(根据值自动推导类型)
let variable_name:dataType = value; // 指定变量类型
在 Typescript
语法规范里,let
关键字在同一个作用域中变量名不能重复:
ts
let num: number = 25_000;
let num: number = 25_000;
// ts-node Error: Cannot redeclare block-scoped variable 'num'
// js Error: Uncaught SyntaxError: Identifier 'num' has already been declared
num = 35_000;
JavaScript 可以随意修改变量值和类型。Typescript 因为有类型系统存在,就做了类型约束限制。
Rust
里默认情况下是不可变更的。允许变量名重复,我们称之为第一个变量被第二个隐藏(Shadowing)
了,这意味着当你使用变量的名称时,编译器将看到第二个变量。(简单理解就是被 var
折磨过的 JSer
一样特性)
ts
var num: number = 25_000;
var num: number = 25_000;
// ok
num = 35_000;
rs
let num: number = 25_000;
let num: number = 25_000;
// Compile ok
num = 35_000;
// error[E0384]: re-assignment of immutable variable `num`
如果想要实现不报错,需要提供了 mut
关键字表示可变更。
text
let mut variable_name = value; // 不指定变量类型(根据值自动推导类型)
let mut variable_name:dataType = value; // 指定变量类型
rs
let mut num: number = 25_000;
let mut num: number = 25_000;
num = 35_000;
// Compile ok
Rust
里使用 let
定义变量不能修改,想要修改要么加上关键词 mut
要么隐藏(重复 let 一次),那该怎么选择:
- 如果变量保持不可变,使用隐藏
let
- 如果需要变量类型,使用隐藏
let
(当再次使用 let 时,实际上创建了一个新变量) - 其他情况可以使用
mut
还有一种特例:
ts
let a: boolean;
if (10 > 20) {
a = false;
} else {
a = true;
}
console.log(a);
rs
let a: bool;
if 10 > 20 {
a = false;
} else {
a = true;
}
println!("The value of x is: {a}");
使用之前,一定要先赋值,Typescript
只是显示 undefined
,而 Rust
就会抛出异常 "a" used here but it isn't initialized
。如果不定义类型,Rust
会自动按第一个赋值的类型推导。防止手滑,最好加上类型限制。我看文档上都没有这样的写法,这是 JSer
陋习呀,哈哈哈哈。
整体来说定义变量都没有太大区别,唯一区别是可变和可重复。里面小坑自己把握。
定义常量
使用 const
关键字定义常量,常量名一般都是 大写字母
,常量名不能重复,不能重新赋值,必须赋值,必须定义类型。
text
const VARIABLE_NAME:dataType = value; // 指定变量类型
ts
Number.MAX_VALUE
Number.MIN_VALUE
Math.PI
在 Typescript
常量也是大写字母,但是自己书写时并没有做要求,最佳风格规范里,都是要求开发人员使用大写字母
。
ts
const a;
// Error: 'const' declarations must be initialized.
const a = 1;
// Compile ok
const a:number = 1;
// Compile ok
a = 5;
// Error: Cannot assign to 'a' because it is a constant.
const a:number = 5;
// ts-node Error: Cannot redeclare block-scoped variable 'a'
// js Error:Uncaught SyntaxError: Identifier 'a' has already been declared
rs
const a;
// Error: help: provide a definition for the constant: `= <expr>;`
const a = 1;
// Error: help: provide a type for the constant: `: i32`
const a:i32 = 1;
// Compile ok
a = 5;
// Error: cannot assign to this expression
const a:number = 5;
// Error: `a` redefined here
// note: `a` must be defined only once in the value namespace of this block
定义常量基本表现一致,有些人可能要说 Typescript 的对象,数组,Map,Set 等可以修改,你要明白赋值和修改,它们是有区别的。Typescript 是可以修改对象属性和值,但是对象引用地址不能改。
变量定义基本类型写法
Typescript
定义:
ts
let num: number = 25_000;
let str: string = 'hello';
let flag: boolean = true;
const PI: number = 3.14;
Rust
定义:
rs
let num: i32 = 25_000;
let str:char = 'hello';
let flag: bool = true;
const PI:f32 = 3.14;
在 Javascript
语言有个区别:无论整型(int),浮点型(float)和数字有关包括 NaN
都是数字类型。其中浮点数采用 IEEE-754
标准表示,默认浮点数使用 64 位二进制格式来表示,其中包括一个符号位、一个指数位和一个尾数位。
条件判断
if/else
有些书或者文档里面叫流程控制。最基本 if/else
,大多数语言都是相通的,甚至写法也出奇一致。
- if (expression) { // executable true }
- if (expression) { // executable true } else { // executable false }
- if (expression) { // executable true } else if (expression) { // executable true }
表达式 expression
为 true
执行 if
括号里,否则执行 else
括号里。
还有一种风骚三目写法:
Typescript
写法:let flag = expression ? true : false
。
Rust
需要这样实现:let flag = if expression { true } else { false }
。
ts
let number = 3;
if (number) {
console.log("number was three");
}
// [log] "number was three"
if (number < 5) {
console.log("condition was true");
} else {
console.log("condition was false");
}
// [log] "condition was true"
if (number % 4 == 0) {
console.log("number is divisible by 4");
} else if (number % 3 == 0) {
console.log("number is divisible by 3");
} else if (number % 2 == 0) {
console.log("number is divisible by 2");
} else {
console.log("number is not divisible by 4, 3, or 2");
}
// [log] number is divisible by 3
let flag = number === 3 ? true : false;
console.log("flag is ", flag);
// [log] flag is true
rs
let number = 3;
if number {
println!("number was three");
}
Error: expected `bool`, found integer
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
// [log] condition was true
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
// [log] number is divisible by 3
let flag = if number == 3 { true } else { false };
println!("flag is {}", flag);
// [log] flag is true
Rust 的表达式 expression 不需要括号 ,expression 必须时一个 bool 类型,没有隐式转换特性。
类型转换表,这个表老实用了,在执行隐式转换规则时,可以参考这个对照表。
switch/match
switch
用于基于不同的条件来执行不同的动作。
text
switch(expression){
case value:
// 语句
break; // 可选
case value:
// 语句
break; // 可选
//你可以有任意数量的case语句
default : // 可选
// 默认语句
}
首先设置表达式 expression
(通常是一个变量)。随后表达式的值会与结构中的每个 case
的值做比较。如果存在匹配,则与该 case
关联的代码块会被执行。请使用 break
来阻止代码自动地向下一个 case
运行。
Typescript
中熟悉常见的 switch
:
ts
let grade: string = 'C';
switch (grade) {
case 'A': {
console.log("优秀");
break;
}
case 'B':
case 'C': {
console.log("良好");
break;
}
case 'D': {
console.log("及格");
break;
}
case 'F': {
console.log("你需要再努力努力");
break;
}
default: {
console.log("未知等级");
}
}
console.log("你的等级是 " + grade);
// [LOG]: "良好"
// [LOG]: "你的等级是 C"
Rust
没有 switch
,但有一个类似叫 match
:
text
match expression {
value => { // 类似 case
// 语句;
},
value => { // 类似 case
// 语句;
},
//你可以有任意数量的case语句
_ => { // 类似 default (不能省略,否则编译出错)
// 默认语句
}
};
rs
let grade: char = 'C';
match grade {
'A' => {
println!("优秀")
},
'B' | 'C' => {
println!("良好");
},
'D' => {
println!("及格");
},
'F' => {
println!("你需要再努力努力");
},
_ => {
println!("未知等级");
}
}
println!("你的等级是 {}", grade);
match
的 case
中匹配 pattern
有很多种:
- 固定单一值,数字:1,字符:'A',布尔:true | false
- 固定多个值,使用
|
分开,数字:1 | 2 | 3,字符:'B' | 'C' - 循环多个值,数字:13..=19,字符:'\0'..='@'、'E' 或 'G'..='\u{d7ff}'
- 如果只有布尔值可以省略默认
_
,其他类型不能省略。
rs
let boolean = true;
// Match is an expression too
let binary = match boolean {
// The arms of a match must cover all the possible values
false => 0,
true => 1,
// TODO ^ Try commenting out one of these arms
};
println!("{} -> {}", boolean, binary);
这种写法类似 Typescript
的箭头函数:
ts
let boolean = true;
// 隐式转换 Number(true)|+true => 1 Number(false)|+false => 0
let binary = ((boolean) => +boolean)(boolean);
console.log("%s -> %d", boolean, binary);
注意:千万不能加分号,否则就抛出异常错误。
break
在 Typescript
里 break
有两层意思:
- 一个
case
穿透,可以匹配多个,类似Rust
这样的写法:1 | 2 | 3
,如果不使用break
,它会一直匹配case
,共用一份匹配语句代码。
ts
switch (num) {
case 1:
case 2:
case 3: {
console.log("这是数字", num);
break;
}
default: {
console.log("未知");
}
}
- 一个阻断代码执行,
break
之后代码不会执行,你正常写代码不会在代码没有结束之间写break
,一般会配合条件判断if
来阻断代码继续执行。
ts
switch (num) {
case 1: {
if (Math.random() > 0.5) {
break;
}
// 如果前面if条件成立 这里无法执行
console.log("这是数字", num);
}
default: {
console.log("未知");
}
}
一般这种写法不多,更多使用的是把
break
换成return
在 Rust
里 break
:
可以实现直接使用 case
穿透,匹配多个条件,我们直接看第二个意思,阻断代码。
一开始是没有 break
,你需要自己创建一个宏:
rs
macro_rules! block {
($xs:block) => {
loop { break $xs }
};
}
match x {
1 => block!({
...
if y == 0 { break; }
...
})
_ => {}
}
可以利用 loop
(后面会介绍):
rs
fn main() {
let x = 1;
loop {
match x {
1 => {
let y = 0;
/* break early ... */
if y == 0 {
break;
}
assert!(y != 0, "y was 0!");
/* do other stuff in here. */
}
_ => {}
}
break;
}
println!("done matching");
}
现在版本可以放心使用:
rs
fn main() {
let x = 1;
match x {
1 => 'label: {
let y = 0;
/* break early ... */
if y == 0 {
break 'label;
}
assert!(y != 0, "y was 0!");
/* do other stuff in here. */
}
_ => {}
}
println!("done matching");
}
你会看到语法很奇特,这里要注意细节,"'label" 没有结束的 "'"。 在 Rust 里字符串要使用双引号,不能使用单引号(在 JSer 要特别注意这个坑) 在 Rust 里如果不使用引号,它认为是一个类型
return
在 Typescript
和 Rust
里 return
语句只能在函数体中使用。return
之后代码永远不会执行,我们叫它 Unreachable code detected
。
我们可以利用这个特性,创建一个函数,return
返回值并且阻断后续代码执行。
ts
function fn() {
switch (num) {
case 1: {
if (Math.random() > 0.5) {
return true;
}
// 如果前面if条件成立 这里无法执行
console.log("这是数字", num);
}
default: {
console.log("未知");
}
}
return false;
}
rs
fn match_it(val: i8) -> i8 {
let mut a: i8 = 0;
match random_or(0, 1) {
1 => {
a = 5;
// other ev1l stuff
if a == 6 { return 1 }
},
0 => a = -5,
_ => (),
};
a // return a
}
如果我不想有返回值,又想阻断代码那该如何?可以利用闭包特性:
ts
let num = 2;
(() => {
switch (num) {
case 1: {
if (Math.random() > 0.5) {
return;
}
// 如果前面if条件成立 这里无法执行
console.log("这是数字", num);
}
default: {
console.log("未知");
}
}
})()
rs
fn main() {
let x = 1;
(|| {
match x {
1 => {
let y = 0;
/* break early ... */
if y == 0 { return; } // Ok!
assert!(y != 0, "y was 0!");
/* do other stuff in here. */
}
_ => {}
}
})();
println!("done matching");
}
语言都是相通性,条件判断这是多个语言都是类似,
Typescript
需要注意特别隐式转换问题。switch 对标 match 都已经把常用写法都列出了,你可以实现想要的功能。有没有发现match
比switch
写起来更爽。哈哈哈
循环语句
循环
其实就是一种重复,在满足指定的条件下,重复的做某些事情。
编程语言也有循环这种概念,也可以在满足指定的条件下,重复的执行一部分代码,我们把这种重复执行的代码称之为循环语句
。
大部分语言都用 2 个基本的循环语句: for
和 while
。
在介绍这个 2 个循环之前,我们先介绍 2 个通用的循环语句关键词:break
和 continue
。
break 和 continue
break
有跳出,打破,阻断意思。跳出循环后,继续执行该循环之后的代码。
continue
有继续,移动意思。中断当前的循环中的迭代,然后继续循环下一个迭代。
都可以指定循环标签,使这些关键字应用于已标记的循环而不是最内层的循环。
ts
let i, j;
loop1:
for (i = 0; i < 3; i++) { //The first for statement is labeled "loop1"
loop2:
for (j = 0; j < 3; j++) { //The second for statement is labeled "loop2"
if (i == 1 && j == 1) {
break loop1;
}
console.log("i = " + i + ", j = " + j);
}
}
ts
let i, j;
loop1:
for (i = 0; i < 3; i++) { //The first for statement is labeled "loop1"
loop2:
for (j = 0; j < 3; j++) { //The second for statement is labeled "loop2"
if (i === 1 && j === 1) {
continue loop1;
}
console.log('i = ' + i + ', j = ' + j);
}
}
while
while
语句只要指定条件为 true,循环就可以一直执行代码块。
text
while(condition) {
statement
}
如果忘记增加条件中所用变量的值或该值是一个 true,该循环永远不会结束。这可能导致程序崩溃。传说中的死循环。
在 Typescript
里 while
:
ts
let result = '';
let i = 0;
while (i < 5) {
i += 1;
result += i + ' ';
};
console.log(result);
// "1 2 3 4 5 "
带有循环标签跳出:
ts
let n = 0;
top: while (n < 3) {
if(n === 2) {
break top;
}
n++;
}
console.log(n);
// 2
在 Typescript
还有类似 do...while
:
do...while
语法:
text
do
statement
while (condition);
do...while
语句创建一个执行指定语句的循环,直到 condition
值为 false
。在执行 statement
后检测 condition
,所以指定的 statement
至少执行一次。
ts
let result = '';
let i = 0;
do {
i += 1;
result += i + ' ';
} while (i < 5);
console.log(result);
// "1 2 3 4 5 "
你会发现 while
和 do...while
返回结果一样,如果你把 i
改成 5
, while
返回 ""
,do...while
返回 "6 "
。
很多人可能对 while
和 do...while
分辨得可能不是很清楚。
简单来说,while
与 do...while
最大的区别便是 while
会先判断再执行语句,而 do...while
便是先 do
执行再判断,也就是说 do...while
不管条件是否成立,都会先执行一次。而 while
则先判断是否成立,若不成立则退出循环,即一次也不执行。
那么最后我们该如何选择使用哪一种语句呢?
一般来说 while
是通用的语法,可以任意语言切换。这是因为按照一般原则是在执行循环之前测试条件比较好,而且测试放在循环的开头可使程序的可读性更高。还有就是在许多应用中,要求在一开始不满足测试条件时就直接跳过整个循环。如果你希望无论条件是否成立都执行一次,那就使用 do...while
。否则推荐使用 while
。
在 Rust
里 while
:
rs
fn main() {
// A counter variable
let mut n = 1;
// Loop while `n` is less than 101
while n < 101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
// Increment counter
n += 1;
}
}
带有循环标签跳出:
rs
fn main() {
// A counter variable
let mut n = 1;
'top: while n < 3 {
if n == 2 {
break 'top;
}
n += 1;
}
println!("{}", n);
// 2
}
Rust
和 Typescript
整体没太大差别,condition
一定要是布尔值,跟 if/else
条件判断一样。没有隐式转换
特性,这个要特别注意。循环标签前一定要加上 '
,否则就会抛出异常,前面已经解释过了。
相比
for
来说,while
语句基本表现一致(除了条件是否有括号语法和隐式转换特性)
for
通用 for
循环遍历数组,字符串,或者数字区间。
在 Typescript
里 for
:
for
按条件遍历
text
for (initialization; condition; afterthought)
statement
initialization, condition, afterthought 都是可选的,如果省略需要有 ;
。
ts
let i = 0;
for (;;) {
if (i > 3) break;
console.log(i);
i++;
}
效果相同:
let i = 0;
while (i <= 3) {
console.log(i);
i++;
}
省略 condition:
ts
for (let i = 0; ; i++) {
console.log(i);
if (i > 3) break;
}
效果相同:
let i = 0;
while (true) {
console.log(i);
if (i > 3) break;
i++;
}
省略 initialization:
ts
let i = 0;
for (; i < 9; i++) {
console.log(i);
// more statements
}
正常写法:
ts
for (let i = 0; i < 9; i++) {
console.log(i);
// more statements
}
// 经典面试题:请问输出什么,是否是顺序输出
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// 为什么 let i = 0 可以解决问题
for...in
代对象的所有枚举字符串属性,包括继承的枚举属性
text
for (variable in object)
statement
ts
for(let attr in obj) {
console.log('key: ' + attr, 'value: ', obj[attr]);
}
如果不希望枚举继承属性,就需要使用 Object.hasOwn() 判断:
ts
for (const prop in obj) {
if (Object.hasOwn(obj, prop)) {
console.log(`obj.${prop} = ${obj[prop]}`);
}
}
推荐使用 Object.keys() | Object.getOwnPropertyNames()
for...of
遍历带有迭代器对象
ts
for (variable of iterable)
statement
ES6 开始新增规则,遍历带有 [@@iterator]()
迭代器的对象,内置有 Array,String,TypedArray,Map,Set,arguments,NodeList,Generator function。
ts
// Array
const iterable = [10, 20, 30];
for (const value of iterable) {
console.log(value);
}
// String
const iterable = "boo";
for (const value of iterable) {
console.log(value);
}
// TypedArray
const iterable = new Uint8Array([0x00, 0xff]);
for (const value of iterable) {
console.log(value);
}
// Map
const iterable = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for(const [key, value] in iterable) {
console.log('key: ' + key, 'value: ', value);
}
// Set
const iterable = new Set([1, 1, 2, 2, 3, 3]);
for (const value of iterable) {
console.log(value);
}
// arguments
function foo() {
for (const value of arguments) {
console.log(value);
}
}
foo(1, 2, 3);
// NodeList
const articleParagraphs = document.querySelectorAll("article > p");
for (const paragraph of articleParagraphs) {
paragraph.classList.add("read");
}
// Generator function
function* source() {
yield 1;
yield 2;
yield 3;
}
const generator = source();
for (const value of generator) {
console.log(value);
}
自定义迭代器对象:
ts
// 基本写法
const iterable = {
[Symbol.iterator]() {
let i = 1;
return {
next() {
if (i <= 3) {
return { value: i++, done: false };
}
return { value: undefined, done: true };
},
};
},
};
for (const value of iterable) {
console.log(value);
}
// 另一种写法
let i = 1;
const iterator = {
next() {
if (i <= 3) {
return { value: i++, done: false };
}
return { value: undefined, done: true };
},
[Symbol.iterator]() {
return this;
},
};
for (const value of iterator) {
console.log(value);
}
// 生成器方法迭代对象
const iterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
for (const value of iterable) {
console.log(value);
}
for await...of
遍历带有异步迭代对象
ECMAScript 2017 引入 Promise + GeneratorFunction 语法糖 async/await。
ECMAScript 2018 引入异步迭代对象 [Symbol.asyncIterator]
。
ts
const LIMIT = 3;
const asyncIterable = {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
const done = i === LIMIT;
const value = done ? undefined : i++;
return Promise.resolve({ value, done });
},
return() {
// This will be reached if the consumer called 'break' or 'return' early in the loop.
return { done: true };
},
};
},
};
(async () => {
for await (const num of asyncIterable) {
console.log(num);
}
})();
那它和 for...of
有什么区别和联系,联系就是迭代器对象,同步和异步区别。
ts
function* generator() {
yield 0;
yield 1;
yield Promise.resolve(2);
yield Promise.resolve(3);
yield 4;
}
(() => {
for (const numOrPromise of generator()) {
console.log('for...of', numOrPromise);
}
})();
// es2017
(async () => {
for (const numOrAwait of generator()) {
console.log('async/await', await numOrAwait);
}
})();
// es2018
(async () => {
for await (const num of generator()) {
console.log('for await...of', num);
}
})();
for await...of
也算是 for...of
+ async/await
的一种语法糖。
在 Rust
里 for
:
- for and range:遍历区间
rs
fn main() {
// `n` will take the values: 1, 2, ..., 100 in each iteration
for n in 1..101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
}
}
还可以使用 1..=100 这种区间语法
- for and iterators:迭代器遍历
迭代器模式是一个设计模式,大部分语言都实现类似迭代器模式里迭代器接口。Rust
看起来 Typescript
差不多,写法很风骚。
通用方法 next()
都没有什么可秀的,真正秀儿 skip()
和 take()
。如果用过 Rxjs 可能知道这两个操作符。如果用过后台数据库 ORM
操作数据库也会接触这 2 个方法,经常用于分页查询。一个是跳过多少条数据,一个是拿多少条数据。
Rust
iterators 示例。
- iter():这通过每次迭代借用集合中的每个元素。从而使集合保持不变,并且在循环之后可以重用。
rs
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter() {
println!("Hello {}", name);
}
println!("names: {:?}", names);
}
// 或者
fn main() {
let names = ["Bob", "Frank", "Ferris"];
for name in names {
println!("Hello {}", name);
}
println!("names: {:?}", names);
}
// Hello name Bob
// Hello name Frank
// Hello name Ferris
// names: ["Bob", "Frank", "Ferris"]
- into_iter():这将消耗集合,以便在每次迭代中提供准确的数据。一旦集合被消耗,它就不再可用,因为它已经在循环中"移动"了。
rs
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.into_iter() {
println!("Hello name {}", name);
}
println!("names: {:?}", names);
}
// 编译错误 for name in names.clone().into_iter() {
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in names.clone().into_iter() {
println!("Hello name {}", name);
}
println!("names: {:?}", names);
}
// 或者
fn main() {
let names = vec!["Bob", "Frank", "Ferris"];
for name in (&names).into_iter() {
println!("Hello name {}", name);
}
println!("names: {:?}", names);
}
// 或者
fn main() {
let names = & ["Bob", "Frank", "Ferris"];
for name in names.into_iter() {
println!("Hello name {}", name);
}
println!("names: {:?}", names);
}
// Hello name Bob
// Hello name Frank
// Hello name Ferris
// names: ["Bob", "Frank", "Ferris"]
发现输出和 iter()
一致,这里有个变更迁移。
如果定义数组不加 vec!
使用 into_iter()
就会抛出错误:
text
for name in names.iter() {
use `.iter()` instead of `.into_iter()` to avoid ambiguity`
or remove `.into_iter()` to iterate by value
- for name in names.into_iter() {
+ for name in names {
- iter_mut():这将可变地借用集合的每个元素,从而允许对集合进行适当的修改。
rs
fn main() {
let mut names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter_mut() {
println!("Hello name {}", name);
*name = "Hello";
}
println!("names: {:?}", names);
}
// Hello name Bob
// Hello name Frank
// Hello name Ferris
// names: ["Hello", "Hello", "Hello"]
或者
fn main() {
let slice = &mut [1, 2, 3];
for element in slice.iter_mut() {
*element += 1;
}
println!("{slice:?}");
// [2, 3, 4]
}
对于数组以及奇怪的语法暂时按下不表,后面会深入探讨。
Rust
里面还有一个 loop
无限循环语句:
text
loop {
// code
}
你可以简单理解:
text
for(;;) {
// code
}
rs
fn main() {
let mut count = 0u32;
println!("Let's count until infinity!");
// Infinite loop
loop {
count += 1;
if count == 3 {
println!("three");
// Skip the rest of this iteration
continue;
}
println!("{}", count);
if count == 5 {
println!("OK, that's enough");
// Exit this loop
break;
}
}
}
可以使用 break
跳出循环,continue
跳过当前迭代,进行下一次迭代。
如果不使用 break 或 continue 或者函数里 loop 不使用 return,就会出现程序崩溃,传说的死循环。
整体来说,循环就 2 大类:
- while
- for
- range 区间
- iterators 迭代器
你可能要说 Typescript
没有这样区间,但你忘了 for(let i = 0; i < 100; i++) {}
,这不就是所谓区间。只不过需要你手动写,Rust
这个区间相对 Typescript
来说就是一个语法糖。至于迭代器语法,Rust
和 Typescript
都是由需要提供迭代器接口,才能正常工作。
在本节结束秀一个帅气的代码:
rs
fn main() {
for number in (1..=4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}
发给 Python 同事表示这个代码太秀儿了。
写在最后
通过这篇文章,让我收获颇丰,不仅回顾了 Typescript
类型和 JavaScript
语法。在学习 Rust
过程中看到语言语法通用性,文章中也是一篇学习一门模板,从 Hello world
上车到跑路。学完上面的基础语法和奇怪写法,可以很容易看懂 Github
里 Rust
相关项目大部分代码。这只是开始,学无止境,后续阅读官网文档深入学习。
后续还有字符串,函数等其他入门类型,由于包含大量代码,导致篇幅过长,剩余部分将会在下一篇继续学习这些有意思的东西。
今天就到这里吧,伙计们,玩得开心,祝你好运
谢谢你读到这里。下面是你接下来可以做的一些事情:
- 找到错字了?下面评论
- 如果有问题吗?下面评论
- 对你有用吗?表达你的支持并分享它。