3.1 变量与可变性、常量

变量

我们在前面提过,Rust中变量默认为immutable,例如

1
2
let x = 3;
x = 2;

将会报错,除非我们将x生命为mutable类型,即

1
2
let mut x = 3;
x = 2;

常量

除了immutable类型的变量之外,Rust中还有常量类型,其主要特点为:

  • 常量永远不可变,因此不可用mut关键字
  • 声明常量需要用const关键字并标明其类型
  • 常量可以被声明在任何作用域,包括全局作用域
  • 常量只能被绑定到常量表达式,无法被绑定到函数的调用结果,或者是只在运行时才能获得的值
  • 在程序运行期间,常量在其声明的作用域内一直有效

常量的命名规范:

  • 全部使用大写字母
  • 单词间使用下划线隔开

例如

1
const NUMBER: i32 = 100_000;

其中下划线_用于增强可读性,类似用逗号在“100,000”的作用


Shadowing

在Rust中,允许声明相同名字的变量,此时,新变量会shadow旧的变量,例如

1
2
let x = 2;
let mut x = x + 1;

是合法的

shadow和把变量标记为mut是不同的,因为shadow声明的同名新变量的类型可以和旧变量不同,例如

1
2
let space = "xxxx";
let space = space.len();

3.2 标量类型

Rust是静态语言,因此需要声明数据类型

在多数情况下,编译器会进行自动推断(类似C++14的auto,例如前面将String解析成整数的parse方法),但当可能的类型较多的时候,就必须添加标注,否则会报错,例如

1
2
let x = "32".parse().expect("Failed to parse number");
println!("{}", x);

将会报错

整数类型

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
2
3
4
5
let sum = 2 + 3;
let difference = 1.9 - 0.4;
let product = 3 * 4;
let quotient = 11.3 / 42.5;
let reminder = 54 % 5;

布尔类型

Rust的布尔类型有truefalse两个值,占用1个字节大小,对应符号bool,例如

1
2
let f = false;
let t: bool = true;

字符类型

Rust中char用来代表单个字符

  • 字面值的表示使用单引号
  • 占用4个字节
  • Unicode标量值,不局限于ASCII码,可以表示包括
    • 拼音、中日韩文字、零长度空白字符、emoji表情

例如

1
2
let a = 'a';
let human = '人';

3.3 复合类型

元组(Tuple)

  • Tuple允许多个类型的值放在一个类型里
  • Tuple长度是固定的,在声明后不可修改

例如

1
2
let tup: (i32, i64, f64) = (1, 2, 3.3);
println!("{}, {}, {}", tup.0, tup.1, tup.2);
  • 上述的点标记法用于索引Tuple中的元素值,例如tup.0

可以通过destructure一个Tuple,并借助模式匹配来获取内部的元素值,例如

1
2
3
let tup: (i32, i64, f64) = (1, 2, 3.3);
let (x, y, z) = tup;
println!("{}, {}, {}", x, y, z);

数组(Array)

  • 和Tuple不同之处在于,其要求所有元素是相同类型
  • Array的长度也必须固定,在声明后不可修改

例如

1
2
let arr = [1, 2, 3];
println!("{}, {}, {}", arr[0], arr[1], arr[2]);
  • 和C++类似,Rust这样直接定义的数组也是存放在stack上而不是heap上

    • 更加灵活的数据结构是Vector,类似C++的动态数组,也由标准库提供
  • 数组是stack上的单块连续内存,若访问索引超过数组范围,编译可能会通过,但运行访问会panic

3.4 函数、语句和表达式

函数的声明使用fn关键字,按照官方标准,函数和变量的命名采取snake case:

  • 所有字母都是小写,单词之间用下划线隔开

示例

1
2
3
4
5
6
7
fn main() {
print_func();
}

fn print_func() {
println!("Hello, world!");
}
  • 和C++不同,在main()中调用print_func()不需要保证其提前被声明,只需保证在作用域范围可访问即可

和大多数语言一样,函数相关的参数包括parameter(形参)argument(实参)

  • 在函数定义中,必须声明参数类型;多个参数之间用逗号分隔

区分语句(statement)表达式(expression),例如

1
2
let x = 3;
let y = (let x = 3);
  • 第一行是一个statement,因此没有返回值,不能被在第二行被赋值

又例如

1
2
3
4
5
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {}", y);
  • 中给y赋值的是事实上是一个expression,如果expression的最后一行没有分号,则将最后一行作为赋值传递给y,否则返回的是一个空Tuple

Rust需要在函数定义中声明返回值类型(语法和Python语法糖差不多),但不可以为返回值命名!

  • Rust默认函数体里最后一个表达式为返回值
  • 若需要提前返回,则使用return关键字指定相关值

例如

1
2
3
4
5
6
7
8
fn func() -> i32 {
5
}

fn main() {
let x = func();
println!("x is {}", x);
}
  • 注意,和前面的expression一样,5后面不可以加分号,否则会被识别成statement

Rust的注释语法和C++一样,例如

1
2
3
4
// 这是单行注释
/* 这是多行注释
这是多行注释
这是多行注释 */

3.5 控制流

If-Else表达式(expression)

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let x = 1;
if x > 2 {
println!("x is greater than 2");
}
else if x == 2 {
println!("x is equal to 2");
}
else {
println!("x is not equal to 2");
}
}

该控制流的总体语法和C++及大多数语言类似,但

  • if等关键字后必须跟Bool类型,否则编译器和报错

  • 这和C++等不同,不会进行类型转换

  • 若出现多余一个else if,则建议使用match来重构代码

由于if ...为expression,所以可以被赋值,例如

1
2
3
let condition = true;
let x = if condition {5} else {6};
println!("The value of x is {}", x);

3.6 循环

Loop循环

相当于C++中的while (true),可以在循环中使用break关键字来告诉程序退出循环

While循环

示例

1
2
3
4
5
let mut n = 4;
while n != 2 {
n = n - 1;
}
println!("n = {}", n)

For循环

示例1

1
2
3
4
let arr: [i32; 3] = [1, 2, 3];
for element in arr.iter() {
println!("{}", element);
}
  • element类型为&i32
  • iter()为数组自带的方法,能够iterate数组元素
    • 相比while方法(自行判断和增加索引),for自带的方法安全且高效

示例2

1
2
3
for number in (1..4).rev() {
println!("{}", number);
}
  • Range是标准库提供的类型,可以指定一个开始数字和结束的数字(左闭右开)
  • rev()方法用于reverse