基本概念

1 数据类型(data type)

Rust静态类型语言(statically typed),每一个值都属于某一个数据类型,编译时必须知道所有变量常量的类型。通常编译器可以推断出数据类型,但有时必须添加类型注解。

接下来将介绍两类数据类型子集:标量(scalar)复合(compound)

1.1 标量

标亮类型代表一个单独的值。Rust有四种基本的标量类型:整型(int)浮点型(float)布尔类型(bool)字符类型(char)

整形

整型是一个没有小数部分的数字。

表1-1: Rust中的整型

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch-bit isize usize

每一个有符号的数据类型可以存储 $-(2^{n-1})$ ~ $2^{n-1}-1$ 在内的数字,无符号的数据类型则是 $0$ ~ $2^{n}-1$。

isizeusize类型依赖运行程序的计算机架构:64位架构上它们是64位的,32位架构上它们是32位的。

Rust默认使用i32类型。

在 Debug 模式下,可以检测出溢出的数据,并且使得程序 panic,但是在 release 模式下并不会检测溢出,而是使用回环操作(wrapping),例如在 u8 数据类型下,值 256 变成 0,257 变成 1。

表1-2: Rust中的整型字面值

Number literals Example
Decimal(十进制) 98_222
Hex(十六进制) 0xff
Octal(八进制) 0o77
Binary(二进制) 0b1111_0000
Byte(单字节字符,仅限于u8) b'A'

数字字面值允许使用类型后缀,例如57u8来指定类型,同时也允许使用 _ 做为分隔符以方便读数,例如 1_000

浮点型

Rust中浮点数类型为f32f64,分别占32位与64位,默认类型为f64,采用IEEE-754标准。

例1.1

fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}

布尔型

Rust中的布尔类型有两个可能的值:truefalse,使用 bool 表示。

例1.2

fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}

字符类型

Rust使用单引号声明char字面量,大小为四个字节,并代表了一个Unicode 标量值(Unicode Scalar Value)

例1.3

fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '🥰';
}

1.2 复合

Rust有两个原生的复合类型:元组(tuple)数组(array)

元组类型

元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:使用tup声明,一旦声明,其长度不会增大或缩小。

例1.4

fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}

可以使用.来访问指定元素:

例1.5

fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let a1 = x.0;
let a2 = x.1;
let a3 = x.2;
}

不带任何值的元组有个特殊的名称,叫做单元(unit)元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。

数组类型

与元组不同,数组中的每个元素的类型必须相同,并且数组长度是固定的

例1.6

fn main() {
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
let c = [3; 5]; // let c = [3, 3, 3, 3, 3];
}

使用索引访问指定数组元素:

例1.7

fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}

Rust会自动检查索引是否小于数组长度,如果超出了则会 panic,这是其安全性的一个体现。

2 变量(variables)

Rust 中,变量默认是不可改变的:immutable,或许让人非常奇怪,不可变的变量还能叫做变量?当然Rust中可以使用可变变量,但是默认不可变能够提升代码的安全性。

例2.1:

fn main (){
let x = 5;
println!("the value of x is {}", x);
x = 6;
println!("the value of x is {}", x);
}

在这里编译器会进行报错:

cannot assign twice to immutable variable ‘x’ scannot assign twice to immutable variable

执行cargo build会显示出错信息,并且还会告知如何解决。

error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("the value of x is {}", x);
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++

For more information about this error, try `rustc --explain E0384`.
error: could not compile `HelloWorld` (bin "HelloWorld") due to 1 previous error

Rust中,在变量名前添加mut来声明此变量可变:

例2.2:

fn main (){
let mut x = 5;
println!("the value of x is {}", x);
x = 6;
println!("the value of x is {}", x);
}

此代码将正确执行:

   Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/HelloWorld`
the value of x is 5
the value of x is 6

3 常量(constants)

类似于不可变变量,常量是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。

首先,不允许对常量使用mut常量不光默认不可变,它总是不可变。声明常量使用const关键字而不是let,并且必须注明值的类型。

例3.1:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

4 隐藏(Shadowing)

重新使用let声明一个已经出现过的变量时,编译器将看到新的变量。实际上,新的变量“遮蔽”了原先的变量,此时任何使用该变量名的行为中都会视为是在使用新的变量,直到新的变量自己也被隐藏或作用域结束。

fn main() {
let x = 5;
let x = x + 1; // cover the first x
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}

隐藏与mut的一个区别是,当再次使用let时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。

let spaces = "   "; // str
let spaces = spaces.len(); // u32

5 语句与表达式

语句(Statements)是执行一些操作但不返回值的指令。表达式(Expressions)计算并产生一个值。

函数调用是一个表达式,宏调用是一个表达式,用大括号创建的一个新的块作用域也是一个表达式。

例5.1:

fn main() {
let x = 5; // Statements
let y = { // Expression
let x = 3;
x + 1 // no `;`
};
}

6 函数

Rust中通过输入fn后面跟着函数名和一对圆括号来定义函数。大括号告诉编译器哪里是函数体的开始和结尾。

Rust代码中的函数和变量名使用snake case规范风格,所有字母都是小写并使用下划线分隔单词。

例6.1:

fn main(){
println!("hello world!");
}

fn some_function(){
println!("some function");
}

具有返回值的函数需要使用箭头->指明其返回值类型,可以使用return进行返回,也可以隐式返回最后一个表达式(注意表达式不加;)。

例6.2:

fn some_function() -> i32{
5
}

7 if 表达式

Rust中,if表达式与其他语言类似,具体形式如下:

例7.1:

fn main() {
let number = 3;
if number < 5 {
println!("smaller than 5");
} else if number >5 {
println!("greater than 5");
} else {
println!("equal 5")
}
}

可以在let语句中配合使用if表达式

例7.2:

fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}

if let 表达式

if let表达式的代替条件操作数的是一个关键字let,再后面是一个模式、一个=和一个检验对象(scrutinee)操作数。 如果检验对象操作数的值与模式匹配,则执行相应的块。 否则,如果存在else块,则继续处理后面的else块。

例7.3:

let dish = ("Ham", "Eggs");
// 此主体代码将被跳过,因为该模式被反驳
if let ("Bacon", b) = dish {
println!("Bacon is served with {}", b);
} else {
// 这个块将被执行。
println!("No bacon will be served");
}

// 此主体代码将被执行
if let ("Ham", b) = dish {
println!("Ham is served with {}", b);
}

if let _ = 5 {
println!("不可反驳型的模式总是会匹配成功的");
}

ifif let表达式可以混合使用:

例7.4:

fn main() {
let x = Some(3);
let a = if let Some(1) = x {
1
} else if x == Some(2) {
2
} else if let Some(y) = x {
y
} else {
-1
};
}

8 循环控制

Rust除了whilefor以外,还提供loop循环,无限执行直到明确的停止。

例8.1:

fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}

如果存在嵌套循环,breakcontinue应用于此时最内层的循环。你可以选择在一个循环上指定一个循环标签(loop label),然后将循环标签breakcontinue一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。

例8.2:

fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}

第一个break只退出内层循环,带标签的break将退出外层循环。

# output
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2

for语言中,可以使用一种更简洁的方式来遍历一个集合中的元素:

例8.3:

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

for element in a {
println!("the value is: {element}");
}
}