Rust 探索(三)—— 通用编程概念(下)

上篇主要是关于类型、变量等内容的介绍,如果已经学习过其他编程语言,那么这一块并不会有太大难度,或许仅仅是一些关键字的替换

而且,在了解其中的一些概念的时候,似乎还隐隐约约能够看到一些接触过的其他编程语言的影子,就比如说元组、列表的概念与Python如出一辙,解构赋值的写法让我想到了TypeScript,区分可变、不可变变量以及类型推导的特性又让我联想到Kotlin,或许Rust也有不断地借鉴其他语言设计的优秀之处吧,亦或是这正是代表了语言演进的方向

1. 函数

函数用来对功能逻辑进行封装,能够增强复用、提高代码的可读

在Rust中,函数通过fn关键字进行声明,并且函数的名称遵循蛇形命名法,即名称使用小写字母进行组合,单词与单词之间使用下划线进行分割,这点倒是和Python中的函数命名相一致

1
2
3
4
5
6
7
8
9
fn make_money() {   // 蛇形命名法
println!("赚钱");
}

fn main() { // 作为入口的main()函数
// 函数调用
make_money();
}

函数名后面的括号放置参数列表、花括号中编写具体的业务逻辑,这些算得上行业通用了

函数可以先定义或后定义,只要对于使用区域可见即可

1
2
3
4
5
6
7
8
fn main() {

make_money();
}
// 放在后面也可以
fn make_money() {
println!("赚钱");
}

1.1. 函数参数

函数的参数可以将外部的变化灵活地告诉内部的逻辑,在框架不变的前提下随机应变,作为函数签名的一部分,就像在文档上看到的那样

image-20230305100319825

1
2
3
4
5
6
7
8
fn main() {

make_money(3); // 传入具体的参数
}

fn make_money(times: i32) { // 定义可传入的参数
println!("赚钱{}次", times);
}

定义参数的目的是为了外界能够给它传值,就好像坐公交车一样,投币口是参数列表,有一个参数,是硬币(让你投的),当外界传值时(投币),根据你的输入执行(让你上车)

在函数签名当中,需要显式声明每个参数的类型(Rust的设计者慎重考虑的结果),以免编译器根据其他部分的代码进行推导后能明确意图,显式地标准总好过复杂的外部情形,对吧?

1
2
3
4
5
6
7
8
fn main() {

make_money(3, 100);
}
// 多个参数,指出类型,“,”分隔
fn make_money(times: i32, value: i32) {
println!("赚钱{}次, 每次{}", times, value);
}

1.2. 函数体中的语句和表达式

函数体,也就是先前{}包裹的部分,其中有若干条语句组成,并且可以以一条表达式作为结尾

1.2.1. 语句 vs 表达式

语句是执行操作但是不返回值的指令

表达式是会进行计算并且得到一个值作为结果的指令

image-20230305102340410

绿框的部分是语句,因为它们没有返回值,只是在描述一步步地操作,但是第二条语句的右侧红框部分是一条表达式,描述了sentence变量与234比较的结果,返回了bool类型

1
2
3
4
5
let sentence = 125;
let result = {
let temp = sentence / 2 ;
temp == 234 // 没有分号,用作表达式返回
};

复杂些的场景(比如套{}的多行语句),在末尾使用表达式,不带分号可以返回结果

1.3. 函数的返回值

函数的返回值也是签名的一部分,可以向调用者返回值,使用->,并在其后面声明其类型

1
2
3
fn sum(a: i32, b: i32) -> i32 {   // 定义类型
a + b // 表达式返回
}
1
2
let result = sum(3, 2);  // 返回5
println!("result={}", result);

如果为函数的表达式结果加上分号,那么就会编程语句,进而无法匹配返回值类型

image-20230305104046962

除了使用表达式作为最后的返回,也可以采用return返回

1
2
3
fn sum(a: i32, b: i32) -> i32 {
return a + b;
}

2. 注释

注释的主要作用是用于说明代码的逻辑和功能,便于自己和一起写代码其他人理解,增强代码的可读性

编译器会自动忽略注释,通常单行注释使用//

1
2
3
4
// 计算求和
fn sum(a: i32, b: i32) -> i32 {
return a + b;
}

而多行注释则是单行注释的复制,在每行前面加上//

1
2
3
4
5
6
// 计算
// 两数相加
// 之和
fn sum(a: i32, b: i32) -> i32 {
return a + b;
}

3. 控制流

程序往往不是平铺直叙的,需要包含循环、判断等控制逻辑使其更加丰满

3.1. if表达式

if表达式主要是根据条件选择分支,许多的编程语言中都有类似的表达

1
2
3
4
5
6
7
8
let assets = 100;
if assets < 100 { // 条件分支
println!("穷人")
} else if assets >= 100 && assets < 10_000 {
println!("普通工薪阶级")
} else { // 兜底
println!("大佬")
}

if会计算对应分支的条件表达式的bool值,为true则执行对应的代码块的内容,为false则将跳过整个代码块(Rust不会尝试将非布尔类型的值转换为布尔类型

并且,else ifelse总是伴随if,一同组成多条件的分支

3.1.1. 表达式

由于if其实是表达式,那么本身能够返回值,因此可以直接将if的整体判断和返回内容一同放置到值的位置上

1
2
3
4
5
6
7
8
9
let assets = 100;
let identity = if assets < 100 { // 表达式作为右值
"穷人" // 返回值
} else if assets >= 100 && assets < 10_000 {
"普通工薪阶级"
} else {
"大佬"
};
println!("身份={}", identity);

所有分支返回的类型必须统一

image-20230305140151090

编译器会对分支进行检查,以免类型无法匹配

3.2. 循环

计算机很擅长做重复的工作,并且这样的工作也非常适合它们

在各种编程语言中,提供的循环的结构也都大同小异,Rust也是基于这些原型进行一些优化和改造

Rust提供了3种循环:

  • loop
  • while
  • for

3.2.1. loop循环

使用loop{}可以定义一段无限循环

1
2
3
loop {
print!("放我出去!")
}

但是,通常我们不会进行无意义的无限循环,还是需要满足一定的条件的时候让它处理一些事情,这个时候就需要使用break跳出

1
2
3
4
5
6
7
8
let mut i = 1;
let result = loop {
i += 1;
if i % 3 == 1 {
break i; // 满足条件结束并返回 4
}
};
println!("result={}", result);

3.2.2. while条件循环

while循环会在每次执行循环体之前判断一次条件,条件为true就执行,否则就跳出

1
2
3
4
5
let mut num = 10;
while num > 0 {
println!("倒数={}", num);
num -= 1;
}

3.2.3. for循环

使用for最大的好处是可以方便地遍历数组、元组等容器里的元素

1
2
3
4
5
let teams = ["姆巴佩", "哈兰德", "克瓦拉茨赫利亚", "贝林厄姆", "穆德里克"];
println!("开始点名!");
for player in teams.iter() {
println!("{}", player);
}

在遍历元素方面,for显得更加安全简捷

1
2
3
for i in 0 .. teams.len() {   // 也可以使用Range通过区间取出索引
println!("{}", i);
}