# Chapter 1 - Common Concepts

# Section 1 - Variables and Mutability

# 变量定义

Rust定义变量的方式

let x = 10
1

Rust的变量默认是Immutable,可通过mut关键字修改。

let x = 10
x = 20 //error: cannot assign twice to immutable variable

let mut x = 10
x = 20 //success
1
2
3
4
5

# Immutable与const的区别

Rust变量默认不可重复赋值,const常量同样也不允许重复赋值,这二者的区别在于:

  • 变量用let关键字,常量用const关键字。
  • 变量可以用mut关键字修饰,常量不行。
  • 常量初始化要带数据类型声明,例如:
const NUM:i32 = 10
1
  • 常量可以在任何上下文中定义,变量不能在定义在全局上下文中。
  • 常量大部分情况下只用于常量赋值表达式,而不会用于赋值函数运算结果等运行时计算的表达式。

# 变量覆盖(Shadows)

let关键字可重复声明同一变量名,后声明的会覆盖之前声明的。

let x = 5;

let x = x + 1;

println!("The value of x is: {}", x);

let x = x * 2;

println!("The value of x is: {}", x);

// output
// The value of x is: 6
// The value of x is: 12
1
2
3
4
5
6
7
8
9
10
11
12
13

之前提到,设置一个变量mutable可以用关键字mut,可以对其进行重复赋值。变量覆盖看起来功能相似,但是它在改变了变量值的同时,保持了Rust的Immutable特性,其安全性没有降低。另外,变量覆盖本质上是定义了一个新的变量,因此我们可以用同一个变量名但是使用不同的数据类型。

let x = 5;

println!("{}", x);

let x = "string";

println!("{}", x);

//output
// 5
// string
1
2
3
4
5
6
7
8
9
10
11

# Section 2 - Data Types

# 整型(Integer)

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

无符号整型范围:0 ~ ,有符号整型范围: ~

# 整型字面量

Number literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte (u8 only) b'A'

溢出(Integer Overflow)

当发生整型溢出,debug模式程序会报错退出,release编译模式会将高位截断(u8256 -> 0)。

# 浮点(Floating—Point)

浮点数表示遵循IEEE-754标准,浮点数有两种基本类型f32单精度浮点和f64双精度浮点,Rust默认是f64,因为在现代cpu中,f64的运算速度和f32相当,同时具有更高的精度。

# 布尔(Boolean)

只有truefalse两个值,大小为1byte。

# 字符(Character)

用单引号表示char类型(双引号是string),大小为4byte,为Unicode编码。

# 元组(Tuple)

元组是常用的将一组数字类型(浮点、整型)的数据组合的类型,不可动态扩容。解构元组时定义的变量要和元组的数据量对应,或者用_占位,也可以通过索引访问。

fn main() {
// tuple声明
let x = (1,2,3);
// 解构
let (a, b, c) = x;

let (a, _, _) = x;
let (_, b, _) = x;
let (_, _, c) = x;

let first = x.0;
let second = x.1;
let third = x.2;

//let (a, b) = x; error
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 数组(Array)

数组中的元素类型必须一致,不可动态扩容。与Vector不同,Vector是标准库提供的一种数据存储结构,支持动态扩容。

# 数组定义方式
// 编译器推断类型和数量
let x = [1, 2, 3];
// 指定类型和数量
let x:[i32; 3] = [1, 2, 3];
// 所有元素都是相同的
let x = [3; 5];
1
2
3
4
5
6

# 数组访问

通过下标的形式,不允许越界。

x[0]

x[5] //error bounds:
1
2
3

# Section 3 - Functions

函数由fn关键字、函数名、参数列表(parameters)、返回类型、作用域块组成。

fn main() {
println!("Hello, world!");

another_function();
}

fn another_function() {
println!("Another function.");
}

// output
// Hello, world!
// Another function.
1
2
3
4
5
6
7
8
9
10
11
12
13

Rust对声明顺序不敏感,调用的函数只要有声明就行。

# 函数参数

函数的参数列表是函数声明的一部分,参数列表多个参数用逗号分割。形参(parameters),实参(arguments)。函数参数列表(parameters)必须指明参数类型

# 函数体

函数体由表达式(expression)结尾的一系列语句(statement)组成。statement表示执行某些操作,不会返回值;expression会进行计算并返回值。

let x = (let y = 6);
1

以上代码会报错,let y = 6是一个声明语句(statement),statement是不能作为赋值语句的右值的,因为赋值语句的右值必须能返回值赋给左值。

函数调用和作用域块都是表达式。

// function invoke
let y = func();

// block scope
let y = {
let x = 3;
x + 1
};
1
2
3
4
5
6
7
8

注意x + 1后面没有分号,这也是表达式和语句的区别,语句都以分号结尾,而表达式不包含分号。表达式加分号会成为语句。

tip

Rust中有一个空类型(),所有语句和没有返回值的函数,Rust会自动返回空类型。

# 函数返回值

函数返回类型定义在参数列表(parameters)之后。

fn func() -> i32 {
1
}
1
2
3

Rust在函数体最后可以用表达式代替return关键字返回值(该表达式不能加分号,否则会变成语句,且该表达式只能位于函数体最后)。

# Section 4 - Comments

注释,对代码加以说明,起辅助作用,给人读的内容而不是机器。用//开头,不能跨行。

// 一行注释
let a = 10;
1
2

# Section 5 - Control Flow

# if条件表达式

由关键字if开始,后面紧跟一个条件表达式,该表达式返回值只能是boolean类型。之后跟一个作用域块,条件不匹配时使用else关键字,后面接一个作用域块。

let number = 3;

if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
1
2
3
4
5
6
7

# 多条件时用else if

let number = 6;

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");
}
1
2
3
4
5
6
7
8
9
10
11

#let语句中使用if表达式

由于if是一个表达式,所以它可以用于let语句的右值。这种情况下,if表达式各条件分支返回的数据类型必须一致,这是因为在编译阶段Rust必须确定变量的类型,编译器不支持运行时动态确定变量类型,这样会使编译器更加复杂并且安全性降低。

let condition = true;
let number = if condition { 5 } else { 6 };

let number = if condition { 5 } else { "six" };
// error if and else have incompatible types
1
2
3
4
5

# 循环

Rust有三种执行循环的方式,loop,whilefor

# loop

loop {
// do something
if condition {
break //something
}
}
1
2
3
4
5
6

loop循环执行同一段代码块。可以通过break关键字从loop循环中返回一个值。

# while

fn main() {
let mut number = 3;

while number != 0 {
println!("{}!", number);

number -= 1;
}

println!("LIFTOFF!!!");
}
1
2
3
4
5
6
7
8
9
10
11

while循环整合了loopifelsebreak的功能,让代码块更清晰,没有很深的嵌套。

# for
fn main() {
let a = [10, 20, 30, 40, 50];

for element in a.iter() {
println!("the value is: {}", element);
}
}
1
2
3
4
5
6
7

for用来遍历集合中的元素。同while通过下标访问的方式相比,for更快更安全,原因在于:

  • 编译器会加入runtime代码,在每次while循环时检查循环条件。
  • 通过下标访问难免出现越界和遗漏等bug。 基于以上优点,for循环使用的频率最高,就算在非遍历集合的场景下。
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
// 3!
// 2!
// 1!
// LIFTOFF!!!
1
2
3
4
5
6
7
8
9
10

上面代码中(1..4)是标准库中的Range类型。按顺序生成左闭右开的集合序列,可以通过rev方法进行逆转。