3.1 变量与可变性、常量
变量
我们在前面提过,Rust中变量默认为immutable,例如
1 | let x = 3; |
将会报错,除非我们将x
生命为mutable类型,即
1 | let mut x = 3; |
常量
除了immutable类型的变量之外,Rust中还有常量类型,其主要特点为:
- 常量永远不可变,因此不可用
mut
关键字 - 声明常量需要用
const
关键字并标明其类型 - 常量可以被声明在任何作用域,包括全局作用域
- 常量只能被绑定到常量表达式,无法被绑定到函数的调用结果,或者是只在运行时才能获得的值
- 在程序运行期间,常量在其声明的作用域内一直有效
常量的命名规范:
- 全部使用大写字母
- 单词间使用下划线隔开
例如
1 | const NUMBER: i32 = 100_000; |
其中下划线_
用于增强可读性,类似用逗号在“100,000”的作用
Shadowing
在Rust中,允许声明相同名字的变量,此时,新变量会shadow旧的变量,例如
1 | let x = 2; |
是合法的
shadow和把变量标记为mut
是不同的,因为shadow声明的同名新变量的类型可以和旧变量不同,例如
1 | let space = "xxxx"; |
3.2 标量类型
Rust是静态语言,因此需要声明数据类型
在多数情况下,编译器会进行自动推断(类似C++14的auto
,例如前面将String
解析成整数的parse
方法),但当可能的类型较多的时候,就必须添加标注,否则会报错,例如
1 | let x = "32".parse().expect("Failed to parse number"); |
将会报错
整数类型
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 |
u8 |
16-bit | i16 |
u16 |
32-bit | i32 |
u32 |
64-bit | i64 |
u64 |
128-bit | i128 |
u128 |
arch | isize |
usize |
- 有符号整型范围:[-2^n-1, 2^n-1]
- 无符号整型范围:[0, 2^n-1]
其中”arch”是”architecture”的缩写,即其位数由计算机架构决定
整数字面值
Number literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) |
b'A' |
整数的默认类型为
i32
,总体来讲速度最快,即便在64位系统中除了Byte类型之外,所有整数字面值都允许使用类型后缀,例如
57u8
例如
1 | let a = 3; |
中a
被默认视作i32
类型
整数溢出
类型u8
的范围为[0, 255],若将其复制为256,则
- 在调试模式下,Rust会检查溢出,并在运行时panic
- 在发布模式下,Rust则不会检查相关溢出,
- 若溢出则开启cycling模式,如256->0,257->1
- 程序不会panic
浮点类型
Rust包含两种基础浮点类型
f32
,32位浮点数,单精度f64
,64位浮点数,双精度
Rust的浮点数采用IEEE-754标准,且f64
是Rust的默认浮点类型
- 在现代CPU上,它和
f32
速度相当,但精度更高
例如
1 | let x = 3.0; |
则默认将x
识别为f64
类型
加、减、乘、除、取余
和其他语言基本一致,例如
1 | let sum = 2 + 3; |
布尔类型
Rust的布尔类型有true
和false
两个值,占用1个字节大小,对应符号bool
,例如
1 | let f = false; |
字符类型
Rust中char
用来代表单个字符
- 字面值的表示使用单引号
- 占用4个字节
- Unicode标量值,不局限于ASCII码,可以表示包括
- 拼音、中日韩文字、零长度空白字符、emoji表情
例如
1 | let a = 'a'; |
3.3 复合类型
元组(Tuple)
- Tuple允许多个类型的值放在一个类型里
- Tuple长度是固定的,在声明后不可修改
例如
1 | let tup: (i32, i64, f64) = (1, 2, 3.3); |
- 上述的点标记法用于索引Tuple中的元素值,例如
tup.0
可以通过destructure一个Tuple,并借助模式匹配来获取内部的元素值,例如
1 | let tup: (i32, i64, f64) = (1, 2, 3.3); |
数组(Array)
- 和Tuple不同之处在于,其要求所有元素是相同类型
- Array的长度也必须固定,在声明后不可修改
例如
1 | let arr = [1, 2, 3]; |
和C++类似,Rust这样直接定义的数组也是存放在stack上而不是heap上
- 更加灵活的数据结构是Vector,类似C++的动态数组,也由标准库提供
数组是stack上的单块连续内存,若访问索引超过数组范围,编译可能会通过,但运行访问会panic
3.4 函数、语句和表达式
函数的声明使用fn
关键字,按照官方标准,函数和变量的命名采取snake case:
- 所有字母都是小写,单词之间用下划线隔开
示例
1 | fn main() { |
- 和C++不同,在
main()
中调用print_func()
不需要保证其提前被声明,只需保证在作用域范围可访问即可
和大多数语言一样,函数相关的参数包括parameter(形参)和argument(实参)
- 在函数定义中,必须声明参数类型;多个参数之间用逗号分隔
区分语句(statement)和表达式(expression),例如
1 | let x = 3; |
- 第一行是一个statement,因此没有返回值,不能被在第二行被赋值
又例如
1 | let y = { |
- 中给
y
赋值的是事实上是一个expression,如果expression的最后一行没有分号,则将最后一行作为赋值传递给y
,否则返回的是一个空Tuple
Rust需要在函数定义中声明返回值类型(语法和Python语法糖差不多),但不可以为返回值命名!
- Rust默认函数体里最后一个表达式为返回值
- 若需要提前返回,则使用
return
关键字指定相关值
例如
1 | fn func() -> i32 { |
- 注意,和前面的expression一样,
5
后面不可以加分号,否则会被识别成statement
Rust的注释语法和C++一样,例如
1 | // 这是单行注释 |
3.5 控制流
If-Else表达式(expression)
1 | fn main() { |
该控制流的总体语法和C++及大多数语言类似,但
if
等关键字后必须跟Bool类型,否则编译器和报错这和C++等不同,不会进行类型转换
若出现多余一个
else if
,则建议使用match
来重构代码
由于if ...
为expression,所以可以被赋值,例如
1 | let condition = true; |
3.6 循环
Loop循环
相当于C++中的while (true)
,可以在循环中使用break
关键字来告诉程序退出循环
While循环
示例
1 | let mut n = 4; |
For循环
示例1
1 | let arr: [i32; 3] = [1, 2, 3]; |
element
类型为&i32
iter()
为数组自带的方法,能够iterate数组元素- 相比
while
方法(自行判断和增加索引),for
自带的方法安全且高效
- 相比
示例2
1 | for number in (1..4).rev() { |
Range
是标准库提供的类型,可以指定一个开始数字和结束的数字(左闭右开)rev()
方法用于reverse