德布罗煜
Rust中通用的编程概念

Rust中通用的编程概念

在这篇文章中,我们将学习一些在 Rust 中通用的编程概念:

  • 变量与可变性
  • 数据类型
    • 标量类型
    • 复合类型
  • 函数
  • 控制流

变量与可变性

变量时不可变的

在 Rust 中,我们通过关键字 let 来声明变量。默认情况下,变量处于不可变(immutable)的状态。
这里的不可变意思是,当我们通过赋值操作,将一个值绑定到了一个变量上后,这个变量就不可以被重新赋值了。
如果我们需要对这个变量重新赋值,则需要在声明变量的同时,声明变量为可变(mutable)的,声明的方式为在变量名前加上 mut 关键字。
下面我们来看一个例子:

1
2
3
4
5
6
7
fn main() {
let a = 0;
let mut b = 0;
b = 1;
println!("{}, {}", a, b); // 0, 1
}

在上面的代码中,我们声明了变量 bmut,也就是可变的,因此我们才能在声明并赋值变量后,再通过赋值语句 b = 1; 将其绑定的值修改为 1
而对于变量 a,我们没有为其声明可变。如果我们在后面强行加上 a = 1; 的话,编译器则会报错,编译无法通过。

变量与常量

虽然在 Rust 中,变量默认是不可变的,但是请不要将它与常量的概念进行混淆。在 Rust 中,是有专门的叫做常量(constant)的概念的。
常量在绑定值后也是不可变的,但是它和不可变变量也有很多区别:

  • 首先,声明常量时不能加上 mut,无论在何种情况下,常量都是不能改变的。
  • 声明常量需要使用 const 关键字,同时其类型必须在声明常量时显式标注。
  • 常量可以在任意作用域进行声明,包括全局作用域。
  • 常量只可以绑定到常量表达式,无法绑定到函数的调用结果或是只能在运行时才能计算出的值。

在 Rust 中,常量的命名规范是 全部使用大写字母,单词之间以下划线分割开来,比如:

  • APP_VERSION
  • MAX_SCORE

隐藏 - Shadowing

在 Rust 中,Shadow 是一个比较重要的概念。
我们可以使用相同的名字声明新的变量,新的变量就会 shadow 掉之前声明的同名变量。
我们来看下面这个例子:

1
2
3
4
5
fn main() {
let x = 0;
let x = x + 1;
println!("{}", x);
}

我们前面说过,一个变量声明后默认是不可变的。如果我们在声明 let x = 0; 之后,通过语句 x = x + 1 重新为其赋值是不符合 Rust 的语法规则的。
但是在这段代码中,我们通过 let x = x + 1; 重新声明了一个 x,并通过这个新的声明覆盖(隐藏)掉了最开始声明的变量 x。后续我们再使用这个变量则会指向我们最新声明的 x

但是需要注意的是,shadow 这个行为与声明变量为 mut 是不一样的。
不管新旧变量,如果没用 mut 进行标记,则都是不可变的;同时,通过 shadow 声明的新变量,它的类型可以与之前的不同。

数据类型

Rust 是一门静态类型语言,在编译时必须知道所有变量的类型。基于使用的值,编译器通常能够自行推断出变量的具体类型。但是,如果可能的类型比较多,就必须添加显式的类型标注,否则编译会报错。
类型的声明方式为在声明变量时,在变量名后面加上英文冒号。冒号后面写上声明的类型。比如 let x: u32 = 0;

标量类型

一个标量类型代表一个单个的值。Rust 有四个标量类型:

  • 整数类型
  • 浮点类型
  • 布尔类型
  • 字符类型

整数类型

整数类型是没有小数部分的,类型通常以 u/i 开头。其中 u 表示无符号整数类型,i 表示有符号整数类型。
比如 u32 就是无符号32位整数类型,i8 就是有符号8位整数类型。
同时,还有 usizeisize 类型,它们的位宽与程序运行时的计算机架构所决定的,如果是64位计算机,那就是64位的。

浮点类型

在 Rust 中,有两种基础的浮点类型,分别是 f32f64

  • f32,32位,单精度浮点类型
  • f64,64位,双精度浮点类型

Rust 的浮点类型使用 IEEE-754 标准进行表述。

布尔类型

Rust 的布尔类型也具有 truefalse 两个值。占据一个字节大小。

字符类型

Rust 语言中,char 类型被用来描述语言中最基础的单个字符。
字符类型的字面值使用单引号。
char 类型占据四个字节大小,是 Unicode 标量值。

复合类型

复合类型可以将多个值放在一个类型里。
Rust 提供了两种基础的复合类型:元组(tuple)和数组。

元组

Tuple 可以将多个类型的多个值放在一个类型里。Tuple 的长度是固定的,一旦声明就无法改变其长度。
创建 Tuple 只需要将多个值写在小括号里,每个值用逗号分开:

1
2
3
4
5
fn main() {
let tup: (i32, f64, bool, char) = (16, 3.14, true, 'δ');
println!("{}, {}, {}, {}", tup.0, tup.1, tup.2, tup.3); // 16, 3.14, true, δ
}

除了上面的例子中给出的根据索引号获取 tuple 中对应值的方法外,我们还可以通过模式匹配解构(destructure) tuple 中的值,比如上面的代码我们也可以写成下面这种样子:

1
2
3
4
5
fn main() {
let tup: (i32, f64, bool, char) = (16, 3.14, true, 'δ');
let (a, b, c, d) = tup;
println!("{}, {}, {}, {}", a, b, c, d); // 16, 3.14, true, δ
}

数组

数组也可以将多个值放在一个类型中,但是它只能存放类型相同的元素,同时其长度也是固定的。声明数组时,只需要像这样通过中括号括起来所有需要的元素即可:let arr = [1, 2, 3];

如果要对数组进行类型声明,则应当采用这种形式 —— **[类型; 长度]**。
例如:let arr: [u8; 3] = [1, 2, 3];

如果数组内所有元素值都相同,那么可以通过下面的方式进行声明:

1
let arr = [0; 3]; // 它相当于 let arr = [0, 0, 0];

函数

接下来我们介绍一下 Rust 中的函数。

在 Rust 中,声明函数需要用到 fn 关键字,比如 fn callback() {} 。同时,和对变量命名的规范相同,函数的命名也使用 snake case 命名规范,所有的字母都是小写的,单词之间使用下划线(_)进行连接。

函数的参数

在 Rust 中,必须为函数的签名里的每个变量声明类型,就像下面这样:

1
2
3
fn print_string(s: String) {
print!("{}", s);
}

语句和表达式

Rust 的函数体由一系列的语句组成,可以由一个表达式结束(也可以不以表达式结束)。

  • 语句:语句是执行一些动作的指令 let x = 6; let y = 7;
  • 表达式:表达式会运算得到一个值 x + y

函数的定义也是一个语句。语句没有返回值,所以我们不可以用 let 将一个语句赋给一个变量。

1
2
3
4
5
6
7
8
9
fn main() {
let x = 3;
let y = {
let z = 1;
x + z // 没有 “;” 是一个表达式
};

println!("y => {}", y);
}

函数的返回值

在 Rust 中声明的函数如果存在返回值,需要通过符号 -> 在函数参数后声明返回值类型。返回值就是函数体里最后一个表达式的值,或第一个通过 return 关键字返回的值。

1
2
3
4
5
6
7
fn return_zero() -> usize {
0
}

fn main() {
println!("value => {}", return_zero()); // value => 0
}

控制流

if 表达式

if 表达式允许您根据条件来执行不同的代码分支,这个条件必须是 bool 类型。
if 表达式中,与条件相关联的代码块被称作**分支(arm)**,可以在后面加上 else 表达式。

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let x = 0;

if x < 0 {
println!("x < 0");
} else if x == 0 {
println!("x = 0");
} else {
println!("x > 0");
}
}

因为 if 是一种表达式,所以可以使用 let 将 if 赋值给变量。在 Rust 中,不存在三目运算符,因此无法像编写 JavaScript 那样使用方便的三目运算:

1
2
let y = 3;
let x = y > 5 ? y - 5 : y;

但是通过 if 表达式,我们就可以巧妙地实现三目运算符的效果:

1
2
3
4
fn main() {
let y = 3;
let x = if y > 5 { y - 5 } else { y };
}

Rust 的循环

在 Rust 中,提供了三种循环类型:loopwhilefor

loop 循环

loop 关键字告诉 Rust 反复执行一段代码,直到你主动跳出循环,实际效果类似 while(true)

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut times = 0;
let result = loop {
times += 1;
if times == 10 {
break times * 3;
}
};

println!("{}", times); // 30
}

while 循环

while 循环的写法与其他主流的高级语言写法基本类似,在 while 关键字后写上条件表达式,当表达式为 false 时跳出循环:

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut count = 10;

while count > 0 {
println!("{}", count);
count -= 1;
}

println!("FIRE");
}

for 循环

在 Rust 中,for 循环一般被用来遍历集合体。

1
2
3
4
5
6
7
fn main() {
let arr = [1, 2, 3 , 4, 5];

for i in arr.iter() {
println!("{}", i);
}
}

除了集合体,我们也可以通过标准库提供的 Range 类型执行指定次数的 for 循环。声明一个 Range 只需要指定起始的数字和结束的数字,Range会生成 [起始数字, 结束数字) 区间内的数字集合。同时,通过 rev 方法可以反转 Range

1
2
3
4
5
fn main() {
for i in (0..10).rev() {
println!("{}", i);
}
}
本文作者:德布罗煜
本文链接:https://kira.host/blog/Rust/Rust中通用的编程概念/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可