文章目录
- [1. 变量和可变性](#1. 变量和可变性)
-
- [1.1 常量](#1.1 常量)
- [1.2 遮蔽](#1.2 遮蔽)
- [2. 数据类型](#2. 数据类型)
-
- [2.1 标量](#2.1 标量)
-
- [2.1.1 整型](#2.1.1 整型)
- [2.1.2 浮点型](#2.1.2 浮点型)
- [2.1.3 数值运算](#2.1.3 数值运算)
- [2.1.4 布尔类型](#2.1.4 布尔类型)
- [2.1.5 字符类型](#2.1.5 字符类型)
- [2.2 复合](#2.2 复合)
-
- [2.2.1 元组类型](#2.2.1 元组类型)
- [2.2.2 数组类型](#2.2.2 数组类型)
-
- [2.2.2.1 访问数组元素](#2.2.2.1 访问数组元素)
- [2.2.2.2 无效的数组访问](#2.2.2.2 无效的数组访问)
- [3. 函数](#3. 函数)
-
- [3.1 参数](#3.1 参数)
- [3.2 语句和表达式](#3.2 语句和表达式)
- [3.3 具有返回值的函数](#3.3 具有返回值的函数)
- [4. 注释](#4. 注释)
- [5. 控制流](#5. 控制流)
-
- [5.1 if表达式](#5.1 if表达式)
-
- [5.1.1 使用else if处理多重条件](#5.1.1 使用else if处理多重条件)
- [5.1.2 在let语句使用if](#5.1.2 在let语句使用if)
- [5.2 循环](#5.2 循环)
-
- [5.2.1 loop](#5.2.1 loop)
- [5.2.2 从循环返回值](#5.2.2 从循环返回值)
- [5.2.3 循环标签](#5.2.3 循环标签)
- [5.2.4 while条件循环](#5.2.4 while条件循环)
- [5.2.5 for循环](#5.2.5 for循环)
- 参考
1. 变量和可变性
当变量不可变时,一旦值被绑定一个名称上,就不能更改这个值。
新建项目:cargo new variables。
rust
fn main() {
let x: u32 = 5;
println!("The value of x is {x}");
x = 6;
println!("The value of x is {x}");
}
cargo run:

错误信息是can not assign twice to immutable variable x。Rust编译器保证,如果一个值声明不可变,就真的不可变。但可以通过在变量名前加上mut关键字来使变量可变,便于理解代码。
rust
fn main() {
let mut x: u32 = 5;
println!("The value of x is {x}");
x = 6;
println!("The value of x is {x}");
}
cargo run:

1.1 常量
和不可变变量类似,常量也是绑定到某个名称且不允许改变的值,不过常量和变量之间还是有一些区别。
不能对常量使用mut,常量永远不可变。声明常量要用const关键字,必须注明值的类型。常量可以在任何作用域中声明,包括全局作用域。常量必须是常量表达式,不能是运行时计算出来的值。
rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
常量的名字是THREE_HOURS_IN_SECONDS。Rust对常量的命名约定是全部大写并用下划线分隔单词。关于常量表达式的相关内容之后学习。
1.2 遮蔽
可以定义一个与之前变量同名的新变量,这种情况下称为第一个变量被第二个变量遮蔽。第二个变量使得后续所有对该名称的使用都指向第二个变量,直到它自己被遮蔽或者作用域结束。可以重复使用同一个变量名并再次写出let关键字来遮蔽第一个变量。
rust
fn main() {
let x: u32 = 5;
println!("The value of x is {x}"); // 5
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is {x}"); // 12
}
println!("The value of x is {x}"); // 6
}

这段代码首先将x绑定到值5,接着通过let 遮蔽了x,并创建一个新变量,把之前的值加1,x变为6。在花括号创建的内部作用域中,第三个let语句再次遮蔽了x,并创建了一个新变量,将之前的值乘以2,x变为12。当这个作用域结束的时候,内部遮蔽结束,x又回到6。
遮蔽和把变量标记为mut不一样。如果在没有使用let关键字的情况下重新给变量赋值,会得到编译时错误------不能给不可变变量进行第二次赋值。而通过let,可以对值做变换,然后在变换完成后继续保持不变。
再次使用let的时候,实际上是在创建一个新的变量(immutable),因此可以改变值的类型,同时继续复用相同的名字。
rust
let spaces = " ";
let spaces = spaces.len();
.第一个spaces变量是字符串类型,第二个spaces变量是数字类型。遮蔽让我们不必想出不同的名字。
rust
let mut spaces = " ";
spaces = spaces.len();

这个错误表明,不能修改变量类型。
rust
fn main() {
let mut spaces = String::from(" ");
spaces.push_str("a");
let spaces = spaces.len();
println!("The length of the string is {spaces}");
}

可变变量也可以被遮蔽,如果遮蔽的变量还需要变化,仍可以使用mut。
rust
fn main() {
let mut spaces = String::from(" ");
spaces.push_str("a");
let mut spaces = spaces.len() as u64;
println!("The length of the string is {spaces}");
spaces += 1;
println!("The value of the spaces variable is {spaces}");
}

2. 数据类型
Rust有两种基本数据类型:标量scalar和复合compound。Rust是静态类型,必须在编译时知道所有变量的类型。编译器通常可以根据值以及它的使用方式推断出类型。在parse将String转换为数值类型时,就必须加上类型注解。
rust
let guess: u32 = "42".parse().expect("Not a number!');
不加上类型注解,会报如下编译错误:

2.1 标量
标量类型代表一个单独的值。Rust有四种基本标量类型:整型、浮点型、布尔类型和字符串类型。
2.1.1 整型
整型是没有小数部分的数字。
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| 架构相关 | isize | usize |
每一个有符号的变体可储存包含 − 2 n − 1 -2^{n-1} −2n−1到 2 n − 1 − 1 2^{n-1}-1 2n−1−1在内的数字,这里 n n n是变体使用的位数。无符号的变体可以储存0到 2 n − 1 2^{n}-1 2n−1的数字。iszie和usize依赖运行程序的计算机架构:64位架构上它们是64位的,32位架构上它们是32位的。
数字字面值可以使用下划线作为视觉分隔符。数字字面值也可以带上类型后缀,如57u8。
| 数字字面值 | 例子 |
|---|---|
| Decimal十进制 | 98_222 |
| Hex十六进制 | 0xff |
| Octal八进制 | 0o77 |
| Binary二进制 | 0b1111_0000 |
| Byte字节字面值,仅限u8 | b'A' |
Rust中整型的默认类型是i32。
补充:当一个整型类型的变量保存超出范围的值时,会发生整型溢出,并导致在debug模式时存在整型溢出检查而导致程序在运行时panic或者在release模式下不存在整型溢出检查而将超出范围的值回绕该类型所能表达的最小值。比如对于u8来说,256会变成0,257会变成1。标准库存在处理整型溢出的数字类型方法,这里不提。
2.1.2 浮点型
Rust的浮点数类型是f32和f64,默认是f64,都是有符号浮点数。
rust
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
2.1.3 数值运算
Rust中所有数字类型都支持加法、减法、乘法、除法和取余。整数除法向零舍入到最接近的整数。
rust
fn main() {
let sum = 5 + 10;
let difference = 95.5 -4.3;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // -1
let remainder = 43 % 5;
}
2.1.4 布尔类型
Rust的布尔类型有两个可能的值:true和false。
rust
fn main() {
let t = true;
let f: bool = false;
}
2.1.5 字符类型
Rust中的char类型是语言中最原始的字母类型。
rust
fn main() {
let c = 'z';
let z: char = 'Z';
let fire = '🔥';
}
使用单引号来表示char字面值,字符串字面值使用的是双引号。Rust的char类型大小为4个字节,并表示一个Unicode标量值。Unicode标量值的范围包括U+0000到U+D7FF,以及U+E000到U+10FFFF。
2.2 复合
复合类型可以把多个值组成一个类型。Rust有两种原生的复合类型:元组和数组。
2.2.1 元组类型
通过在圆括号中写一组由逗号分隔的值来创建元组。元组中的每个位置都有一个类型,而且这些不同位置上的值类型不必相同。
rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
变量tup绑定到整个元组上,元组本身会被视为一个单独的复合值。为了从元组中取出单个值,可以使用模式匹配pattern matching来解构元组destructure:
rust
fn main() {
let tup = (500, 6.4, 1);
let (_x, y, _z) = tup;
println!("The value of y is {y}");
}

程序首先创建了一个元组并绑定到tup变量上。接着使用了let和一个模式将tup拆成了三个不同的变量x,y,z,这就是解构。最后程序打印出了y的值。
可以使用点号后跟值的索引来直接访问所需的元组元素。
rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
println!("{five_hundred}, {six_point_four}, {one}");
}

这个程序创建了一个元组x,使用各自的索引访问元组中的每个元素。元组的第一个索引值是0。
不带任何值的元组有一个名字,叫作单元unit。这种值以及对应的类型都写作(),表示空值或空的返回类型。如果一个表达式没有返回任何其他值,它就会隐式地返回单元值。
2.2.2 数组类型
另一种包含多个值的方式是数组。和元组不同,数组中的每个元素都必须具有相同类型。Rust中数组长度是固定的。
把数组的值写成在方括号内,用逗号分隔的列表:
rust
fn main() {
let a = [1, 2, 3, 4, 5];
}
标准库中存在一个类似数组的集合类型vector,允许长度增长或缩小。
可以像这样编写数组的类型:在方括号中包含每个元素的类型,后跟分号,再后跟数组元素的数量:
rust
let a: [i32; 5] = [1, 2, 3, 4, 5];
i32是元素的类型,5表明该数组包含五个元素。
还可以通过在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组:
rust
let a = [3; 5];
变量名为a的数组将包含5个元素,这些元素的值最初都将被设置为3。
2.2.2.1 访问数组元素
数组是分配在栈上分配的一整块、大小已知且固定的内存。
rust
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
2.2.2.2 无效的数组访问
rust
use std::io;
fn main() {
let a= [1, 2, 3, 4, 5];
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
let element = a[index];
println!("The value of the element at index {index} is {element}");
}
输入越界的索引,会得到如下的报错(我这里配置了环境变量RUST_BACKTRACE=1):



程序在索引操作使用了无效值,产生运行时错误。程序带着错误信息退出,没有执行println!语句。这是Rust内存安全原则的一个例子。
3. 函数
main函数,是很多程序的入口点。fn关键字用来声明新函数。Rust代码中函数名和变量通常使用snake case风格,所有字母都使用小写,并用下划线分隔单词。
rust
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}

在Rust中通过输入fn后面跟着函数名和一对圆括号来定义函数。大括号告诉编译器哪里是函数体的开始和结尾。
可以使用函数名后跟圆括号来调用我们定义过的任意函数。Rust不关心函数定义所在的位置,只要函数被调用时出现在调用之处可见的作用域内就行。
3.1 参数
可以定义带有参数的函数,参数是特殊变量,是函数签名的一部分。当函数带有参数时,可以为这些参数提供具体的值。从严格意义上说,这些具体值叫作arguments。
rust
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is {x}");
}

在函数签名中,必须声明每个函数的类型。定义多个参数时,使用逗号分隔:
rust
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is {value}{unit_label}");
}

3.2 语句和表达式
函数体由一系列语句组成,并且可以选择以一个表达式结束。语句是执行一些操作但不返回值的指令;表达式计算并产生一个值。
rust
fn main() {
let y = 6;
}
函数定义本身也是语句,调用函数不是语句。语句不返回值。
rust
fn main() {
let x = (let y = 6);
}

let y = 6这条语句不会返回值,因此没有什么东西可以绑定到x上。表达式会计算出一个值,并且大部分Rust代码由表达式组成。表达式可以是数学运算5 * 6,可以是语句的一部分,如let y = 6中的6,可以是函数调用,可以是宏调用,可以是大括号创建的的一个新的块作用域。
rust
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is {y}");
}
这个表达式
rust
{
let x = 3;
x + 1
}
是一个代码块,它的值为4。这个值作为let语句的一部分被绑定到y上。注意x + 1这一行在结尾没有分号。表达式的末尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值。
3.3 具有返回值的函数
函数可以把值返回给调用它的代码。我们不会给返回值命名,但必须在箭头->后面声明它的类型。Rust中函数返回值等于函数体中最后一个表达式的值。也可以用return关键字并指定一个值从函数中提前返回;大多数函数会隐式返回最后一个表达式的值。
rust
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is {x}");
}

rust
fn main() {
let x = plus_one(5);
println!("The value of x is {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1;
}

错误信息是类型不匹配,函数定义要求返回i32,但语句不会求值为某个值,语句对应的类型是单元。在这段输出中,Rust提供了一个建议删除分号。
4. 注释
Rust中惯用的注释风格是用两个斜杠开始一条注释,并让注释持续到该行末尾。
多行注释:
rsut
// So we're doing something complicated here, long enough that we need
// mulitple lines of comments to do it! Whew! Hopefully, this comment will
// explain what's going on.
注释可放在代码的行末尾。
rust
fn main() {
let lucky_number = 7; // I'm feeling lucky today
}
注释放在代码上一行。
rust
fn main() {
// I'm feeling lucky today
let lucky_number = 7;
}
5. 控制流
Rust中最常见的控制执行流的结构是if表达式和循环。
5.1 if表达式
if表达式允许根据条件执行不同的代码分支。提供一个条件并表示如果条件满足,运行这段代码,否则不运行这段代码。
rust
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}

所有if表达式都以if关键字开头,后面紧跟一个条件。与if表达式中各个条件关联的代码块称为arms,也可以包含一个可选的else表达式来提供一个在条件为false时应执行的代码块。
条件必须是bool,如果条件不是bool,会得到如下的错误:
rust
fn main() {
let number = 3;
if number {
println!("number was three");
}
}

Rust不会自动尝试把非布尔类型转换成布尔类型。必须显式地为if提供一个布尔值作为条件。
5.1.1 使用else if处理多重条件
可以将else if表达式与if和else组合来实现多重条件。
rust
fn main() {
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");
}
}

过多的else if会使代码杂乱,可以考虑使用match。
5.1.2 在let语句使用if
if是一个表达式,可以在let右侧使用它。
rust
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is {number}");
}

变量number会被绑定到if表达式结果所有产生的那个值。在将if表达式在let语句使用的时候必须保证每个分支结果类型相同。
rust
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is {number}");
}

5.2 循环
Rust提供了多种循环------loop、while和for。
5.2.1 loop
loop关键字告诉Rust反复执行一段代码,要么永远执行下去,要么直到你明确要求它停止。
rust
fn main() {
loop {
println!("again!");
}
}

这个程序只能通过Ctrl+C手动终止陷入无限循环的程序。可以使用break关键字停止循环。也可以使用continue跳过本次循环迭代,进入下一次迭代。
5.2.2 从循环返回值
loop的一个用途是重试那些你知道可能失败的操作。可能希望把这个操作的结果传递给其他代码。可以在用于停止循环的break表达式后面加上想要返回的值,这个值会作为循环的返回值返回。
rust
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}

在循环内部使用return,也可以从中返回。break只会退出当前循环,return总会退出当前函数。
5.2.3 循环标签
如果循环中又套了循环,那么break和continue默认只作用于当前最内层的那个循环。可以选择给某个循环加上一个循环标签,然后把这个标签和break或continue一起使用,这样这些关键字就会作用于被标记的循环,而不是最内层循环。
rust
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}");
}

5.2.4 while条件循环
while条件循环可以通过组合loop、if、else和break来实现。
rust
fn main() {
let mut number = 3;
while number != 0 {
println!("{nuber}");
number -= 1;
}
println!("LIFTOFF!!!");
}
rust
fn main() {
let mut number = 3;
loop {
if number == 0 {
break;
}
println!("{number}");
number -= 1;
}
println!("LIFTOFF!!!");
}

5.2.5 for循环
可以使用while来遍历集合中的元素,比如数组。
rust
fn main() {
let a: [u32; 5] = [10, 20, 30, 40, 50];
let mut index: usize = 0;
while index < 5 {
println!("the value is {}", a[index]);
index += 1;
}
}

通过while来遍历数组,很容易因为索引值和条件写错,导致程序panic。更为简洁的方案是使用for循环。
rust
fn main() {
let a: [u32; 5] = [10, 20, 30, 40, 50];
for element in a {
println!("the value is {element}");
}
}
for循环消除了越界、遍历不完整的问题。for循环的安全性和简洁性,使它成为Rust中最常用的循环结构。
使用标准库中的Range生成倒计时,需要用rev方法反转range:
rust
fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}

参考
1、常见编程概念