Common Concepts
Table of content
Chapter 1 - Common Concepts
Section 1 - Variables and Mutability
变量定义
Rust 定义变量的方式
let x = 10
Rust 的变量默认是 Immutable,可通过mut关键字修改。
let x = 10
x = 20 //error: cannot assign twice to immutable variable
let mut x = 10
x = 20 //success
Immutable 与 const 的区别
Rust 变量默认不可重复赋值,const 常量同样也不允许重复赋值,这二者的区别在于:
- 变量用
let关键字,常量用const关键字。 - 变量可以用
mut关键字修饰,常量不行。 - 常量初始化要带数据类型声明,例如:
const NUM:i32 = 10
- 常量可以在任何上下文中定义,变量不能在定义在全局上下文中。
- 常量大部分情况下只用于常量赋值表达式,而不会用于赋值函数运算结果等运行时计算的表达式。
变量覆盖(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
之前提到,设置一个变量 mutable 可以用关键字mut,可以对其进行重复赋值。变量覆盖看起来功能相似,但是它在改变了变量值的同时,保持了 Rust 的 Immutable 特性,其安全性没有降低。另外,变量覆盖本质上是定义了一个新的变量,因此我们可以用同一个变量名但是使用不同的数据类型。
let x = 5;
println!("{}", x);
let x = "string";
println!("{}", x);
//output
// 5
// string
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 ~ $2^n - 1$,有符号整型范围:$-2^{n-1}$ ~ $2^{n-1} - 1$
整型字面量
| Number | literals Example |
|---|---|
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
| Byte | (u8 only) b’A’ |
:::tip 溢出(Integer Overflow) 当发生整型溢出,debug 模式程序会报错退出,release 编译模式会将高位截断(u8256 -> 0)。 :::
浮点(Floating—Point)
浮点数表示遵循 IEEE-754 标准,浮点数有两种基本类型f32单精度浮点和f64双精度浮点,Rust 默认是f64,因为在现代 cpu 中,f64的运算速度和f32相当,同时具有更高的精度。
布尔(Boolean)
只有true和false两个值,大小为 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
}
数组(Array)
数组中的元素类型必须一致,不可动态扩容。与 Vector 不同,Vector 是标准库提供的一种数据存储结构,支持动态扩容。
数组定义方式
// 编译器推断类型和数量
let x = [1, 2, 3];
// 指定类型和数量
let x:[i32; 3] = [1, 2, 3];
// 所有元素都是相同的
let x = [3; 5];
数组访问
通过下标的形式,不允许越界。
x[0]
x[5] //error bounds:
Section 3 - Functions
函数由fn关键字、函数名、参数列表(parameters)、返回类型、作用域块组成。
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
// output
// Hello, world!
// Another function.
Rust 对声明顺序不敏感,调用的函数只要有声明就行。
函数参数
函数的参数列表是函数声明的一部分,参数列表多个参数用逗号分割。形参(parameters),实参(arguments)。函数参数列表(parameters)必须指明参数类型
函数体
函数体由表达式(expression)结尾的一系列语句(statement)组成。statement 表示执行某些操作,不会返回值;expression 会进行计算并返回值。
let x = (let y = 6);
以上代码会报错,let y = 6是一个声明语句(statement),statement 是不能作为赋值语句的右值的,因为赋值语句的右值必须能返回值赋给左值。
函数调用和作用域块都是表达式。
// function invoke
let y = func();
// block scope
let y = {
let x = 3;
x + 1
};
注意 x + 1 后面没有分号,这也是表达式和语句的区别,语句都以分号结尾,而表达式不包含分号。表达式加分号会成为语句。
:::tip tip Rust 中有一个空类型(),所有语句和没有返回值的函数,Rust 会自动返回空类型。 :::
函数返回值
函数返回类型定义在参数列表(parameters)之后。
fn func() -> i32 {
1
}
Rust 在函数体最后可以用表达式代替 return 关键字返回值(该表达式不能加分号,否则会变成语句,且该表达式只能位于函数体最后)。
Section 4 - Comments
注释,对代码加以说明,起辅助作用,给人读的内容而不是机器。用//开头,不能跨行。
// 一行注释
let a = 10;
Section 5 - Control Flow
if 条件表达式
由关键字if开始,后面紧跟一个条件表达式,该表达式返回值只能是boolean类型。之后跟一个作用域块,条件不匹配时使用else关键字,后面接一个作用域块。
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
多条件时用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");
}
在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
循环
Rust 有三种执行循环的方式,loop,while和for。
loop
loop {
// do something
if condition {
break //something
}
}
loop循环执行同一段代码块。可以通过 break 关键字从loop循环中返回一个值。
while
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
while循环整合了loop、if、else、break的功能,让代码块更清晰,没有很深的嵌套。
for
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
for用来遍历集合中的元素。同while通过下标访问的方式相比,for更快更安全,原因在于:
- 编译器会加入 runtime 代码,在每次
while循环时检查循环条件。 - 通过下标访问难免出现越界和遗漏等 bug。 基于以上优点,
for循环使用的频率最高,就算在非遍历集合的场景下。
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
// 3!
// 2!
// 1!
// LIFTOFF!!!
上面代码中(1..4)是标准库中的Range类型。按顺序生成左闭右开的集合序列,可以通过rev方法进行逆转。