1.通用编程概念
1.1关键字
关键字与其他编程语言类似,Rust 也拥有一系列只能被用于语言本身的保留关键字。要记住,你不能使用这些关键字来命名自定义的变量或函数。大部分关键字都有特殊的含义,你会使用它们来完成 Rust 程序中各式各样的任务;还有一些关键字目前没有任何功能,但它们被预留给了未来可能会添加的功能。
1.2变量与可变性
1.2.1变量的可变性
Rust 的变量机制是其安全性和性能的基石,当一个变量不可变时,一旦它被绑定到某个值上,这个值就再也无法改变了。
fn main() {
// 默认情况下,变量是不可变的
let x = 5;
println!("x = {}", x);
// 以下代码会编译错误:不能给不可变变量赋值
x = 6; // ❌ 错误: cannot assign twice to immutable variable
}
这里的错误提示信息告诉你cannot assign twice to immutable variablex(不能对不可变变量x进行二次赋值。在Rust中,变量默认是不可变的。如果要使变量可变,就需要使用mut关键字:
n main() {
// 默认情况下,变量是不可变的
let mut x = 5;
println!("x = {}", x);
x = 6;
println!("x = {}", x);
}
同时,还需要注意的一个点是,Rust 中 let 创建的是所有权绑定关系,不是简单的赋值。
fn main() {
let x = 5; // 在栈上分配一个i32,存入5
let y = x; // 在栈上分配另一个i32,复制5进去
// 让我们看看内存地址
println!("x 的值: {}, 地址: {:p}", x, &x);
println!("y 的值: {}, 地址: {:p}", y, &y);
// 输出类似:
// x 的值: 5, 地址: 0x7ffeefbff5fc
// y 的值: 5, 地址: 0x7ffeefbff5f8
// ↑ 两个不同的地址!
}
这一点在String这一种数据类型的体现上尤为明显,看如下代码:
// Rust - 编译错误!
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有权从 s1 移动到 s2
println!("s1 = {}", s1); // ❌ 编译错误:value borrowed here after move
println!("s2 = {}", s2); // ✅ 正确
}
在上面代码中,创建 s1,它拥有 String 数据的所有权,然后s1 的所有权移动给了 s2,最后又尝试使用已经失去所有权的 s1
1.3常量
像不可变变量一样,绑定到常量上的值无法被其他代码修改,但常量和变量之间还是存在一些细微的差别的。 首先,不能使用mut关键字来修饰常量。常量不仅默认是不可变的——它们总是不可变的。
其次,需要使用_**const**_关键字而不是let关键字来声明常量。_**在声明的同时,必须显式地标注值的类型**_。
再次,常量可以被声明在任何作用域中,甚至包括全局作用域。这在一个值需要被不同部分的代码共同引用时十分有用。
最后,你只能将常量绑定到一个常量表达式上,而无法将一个函数的返回值或其他需要在运行时计算的值绑定到常量上。
1.4Rust命名规范
2.Rust的数据类型
Rust 是一门静态类型语言。这意味着它在编译程序的过程中需要知道所有变量的具体类型。在大部分情况下,编译器都可以根据我们如何绑定、使用变量的值来自动推导出变量的类型。
2.1标量类型
标量类型是单个值类型的统称。Rust 中内建了4种基础的标量类型:整数、浮点数、布尔值和字符。
2.1.1整数类型
注意:
- 默认整数类型:
i32 isize/usize:指针大小,用于索引和长度;它们的长度取决于程序运行的目标平台。在64位架构下,它们就是64位的;而在32位架构下,它们就是32位的。
2.1.2浮点数数据类型
2.1.3布尔类型
2.1.4 字符数据类型
注意:char的字面量使用单引号指定,而不同于字符串使用双引号指定。
2.2复合类型(Compound Types)
复合类型(compound type)可以将多个不同类型的值组合为一个类型。Rust提供了两种内置的基础复合类型:元组(tuple)和数组(array)。
2.2.1元组(Tuple)
元组是一种相当常见的复合类型,它可以将其他不同类型的多个值组合到一个复合类型中。元组还拥有一个固定的长度:你无法在声明结束后增加或减少其中的元素数量。
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // 解构
let first = tup.0; // 访问第一个元素
元组的解构
fn main() {
// 1. 创建一个元组,包含三个不同类型的元素
// - 第一个元素: i32 整数 (500)
// - 第二个元素: f64 浮点数 (6.4)
// - 第三个元素: i32 整数 (1)
let tup = (500, 6.4, 1);
// 2. 使用模式匹配解构元组
// tup 被拆分成三个独立的变量
// x = 500, y = 6.4, z = 1
let (x, y, z) = tup;
// 3. 打印解构后的变量 y 的值
// 使用 Rust 1.58+ 的简写语法 {y} 代替 {0}
println!("The value of y is: {y}");
// 4. 也可以直接访问元组的元素(不解构的情况)
println!("直接访问元组元素:");
println!("第一个元素: {}", tup.0); // 500
println!("第二个元素: {}", tup.1); // 6.4
println!("第三个元素: {}", tup.2); // 1
// 5. 显示各个变量的类型和值
println!("解构后的变量:");
println!("x = {} (类型: i32)", x);
println!("y = {} (类型: f64)", y);
println!("z = {} (类型: i32)", z);
}
2.2.2数组(Array)
数组中的每一个元素都必须具有相同的类型。Rust 中的数组拥有固定的长度,一旦声明就再也不能随意更改大小,这与其他某些语言有所不同。
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let same = [0; 5]; // [0, 0, 0, 0, 0]
let first = arr[0];
数组由一整块分配在栈上的、固定大小的内存组成,可以通过索引来访问一个数组中的所有元素。
非法数组访问
// 需要导入 io 模块
use std::io;
fn main() {
// 1. 定义一个固定大小的数组
let a = [1, 2, 3, 4, 5];
// 2. 提示用户输入数组索引
println!("Please enter an array index.");
// 3. 创建可变字符串来存储用户输入
let mut index = String::new();
// 4. 读取用户输入
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
// 5. 将字符串转换为数字索引
let index: usize = index
.trim() // 去除换行符和空白字符
.parse() // 解析为数字
.expect("Index entered was not a number");
// 6. 访问数组元素(这里可能会 panic! 如果索引越界)
let element = a[index];
// 7. 打印结果(修正了字符串格式化)
println!(
"The value of the element at index {index} is: {element}"
);
}
有许多底层语言选择不提供类似的检查机制,一旦使用了非法索引,就会访问某块无效的内容。Rust 选择在这种错误出现时立即中断程序。
2.2.3切片(Slice)
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..4]; // [2, 3, 4]
2.3字符串类型
2.3.1字符串切片
let s: &str = "Hello, Rust!";
let s2: &'static str = "静态字符串";
2.3.2堆分配字符串
内存布局:
- 栈上:指针(8) + 长度(8) + 容量(8) = 24字节
- 堆上:UTF-8字节数据
let mut s = String::from("hello");
s.push_str(" world");
2.4 指针类型
2.4.1引用**(Reference)**
let x = 5;
let r: &i32 = &x;
let mut y = 10;
let mr: &mut i32 = &mut y;
2.4.2裸指针(Raw Pointer)
let x = 5;
let raw_ptr: *const i32 = &x as *const i32;
unsafe { println!("{}", *raw_ptr); }
2.4.3 智能指针
use std::rc::Rc;
use std::cell::RefCell;
let boxed = Box::new(5); // 堆分配
let rc = Rc::new(42); // 引用计数
let cell = RefCell::new(42); // 内部可变
2.5集合类型
use std::collections::{HashMap, VecDeque};
let mut vec = Vec::new();
vec.push(1);
let mut map = HashMap::new();
map.insert("key", 42);
let mut deque = VecDeque::new();
deque.push_back(1);
2.5.1序列
2.5.2Map
2.5.3集合
2.5.4其他
2.6标准库常用类型
2.6.1Option
Option<T> —— 处理可能为空的值
-
用途:替代其他语言的
null或nil,安全地表示“可能有值,可能没有” -
成员:
`Some(T)` —— 有值,值为 `T` `None` —— 无值
// 安全处理除数为0的情况
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
let result = divide(10.0, 2.0); // Some(5.0)
let result2 = divide(10.0, 0.0); // None
2.6.2Result
Result<T, E> —— 处理可能出错的操作
- 用途:函数可能成功返回
T,也可能失败返回错误E - 成员:
Ok(T)—— 操作成功,返回结果TErr(E)—— 操作失败,返回错误E
fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path) // 成功返回文件内容,失败返回io错误
}
let content = read_file("hello.txt");
match content {
Ok(text) => println!("文件内容: {}", text),
Err(e) => println!("读取失败: {}", e),
}
2.6.3迭代器
迭代器主要用于处理集合遍历。
2.6.3.1 Iterator
- 任何实现了
Iterator的类型都可以被迭代 - 关键方法:
.next()返回Option<Item>
let vec = vec![1, 2, 3];
let mut iter = vec.iter(); // 创建迭代器
while let Some(num) = iter.next() {
println!("{}", num); // 1, 2, 3
}
2.6.3.2 IntoIterator
- 可以被转换为迭代器的类型
- 实现了这个特质,就可以用
for循环
let vec = vec![1, 2, 3];
// 因为 Vec 实现了 IntoIterator,所以可以直接遍历
for num in vec {
println!("{}", num);
}
// 等同于
for num in vec.into_iter() {
println!("{}", num);
}
2.7.4时间类型
2.7.4.1Duration —— 时间段
- 表示一段时间的长度(秒、毫秒、纳秒等)
- 用于计算时间间隔
use std::time::Duration;
use std::thread::sleep;
let one_second = Duration::from_secs(1);
let half_second = Duration::from_millis(500);
sleep(one_second + half_second); // 休眠1.5秒
2.7.4.2 Instant —— 时间点(单调时钟)
- 只能用于测量时间间隔,不表示实际时间
- 保证单调递增(不会因系统时间调整而回退)
use std::time::Instant;
let start = Instant::now();
// 执行一些操作...
let duration = start.elapsed(); // 获取经过的时间
println!("操作耗时: {:?}", duration);
2.7.4.3SystemTime —— 系统时间
- 表示实际的日历时间(可以转换为日期)
- 可能因系统时间调整而改变
use std::time::SystemTime;
let now = SystemTime::now();
println!("当前系统时间: {:?}", now);
// 转换为UNIX时间戳
match now.duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => println!("UNIX时间戳: {}秒", duration.as_secs()),
Err(e) => println!("时间计算错误: {:?}", e),
}
2.8自定义数据类型
2.8.1结构体
// 具名结构体
struct Point {
x: i32,
y: i32,
}
// 元组结构体
struct Color(i32, i32, i32);
// 单元结构体
struct Marker;
// 内存大小 = 各字段大小之和(考虑对齐)
泛型结构体
struct Point<T> {
x: T,
y: T,
}
let integer = Point { x: 5, y: 10 }; // Point<i32>
let float = Point { x: 1.0, y: 4.0 }; // Point<f64>
2.8.2枚举(Enum)
enum Message {
Quit, // 0字节
Move { x: i32, y: i32 }, // 8字节
Write(String), // 24字节
ChangeColor(i32, i32, i32), // 12字节
}
// 枚举大小 = 最大变体大小 + 判别式(通常1字节)
2.8.3特征对象(Trait Object)
let trait_obj: &dyn std::fmt::Debug = &42;
let boxed: Box<dyn std::error::Error> = Box::new(io::Error::new(...));
2.9 特殊类型
2.9.1Never 类型
什么是 ! 类型?
- 表示永远不会返回的计算
- 大小为 0 字节(不占内存)
- 可以转换为任何其他类型(因为它永远不会实际产生值)
// 1. panic!() - 程序崩溃,不会返回
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("除数不能为0"); // 这里返回 ! 类型
}
a / b
}
// 2. 无限循环 - 永远不会结束
fn forever() -> ! {
loop {
println!("永远运行...");
}
}
// 3. 退出进程
fn exit_program() -> ! {
std::process::exit(0);
}
// 4. match 分支中的 continue、return、break 等
let result: Result<i32, &str> = Err("错误");
let value = match result {
Ok(v) => v,
Err(_) => return, // 这里也是 ! 类型
};
2.9.2单元类型 () (空元组)
什么是 () 类型?
- 表示没有有意义的值
- 大小为 0 字节
- Rust 中所有表达式都必须有类型,即使没有返回值。
// 1. 没有返回值的函数(默认返回 ())
fn say_hello() { // 等同于 fn say_hello() -> ()
println!("Hello!");
}
// 2. 不需要返回值的操作
let _ = println!("这行代码返回 ()");
// 3. 表示"完成"状态(如异步编程)
async fn fetch_data() {
// 异步操作...
// 完成后返回 ()
}
// 4. 占位符(当语法要求但不需要值时)
let mut v = vec![1, 2, 3];
v.clear(); // clear() 返回 ()
// 5. 实现某些trait但不需要状态
struct Counter;
impl Iterator for Counter {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
Some(42) // 这里不返回 (),但很多trait方法需要返回 ()
}
}
2.9.3函数指针 fn(...) -> ...
什么是函数指针?
- 指向函数的指针,不是闭包
- 大小通常为 8 字节(64位系统)
- 只能指向定义明确的函数,不能捕获环境变量
// 1. panic!() - 程序崩溃,不会返回
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("除数不能为0"); // 这里返回 ! 类型
}
a / b
}
// 2. 无限循环 - 永远不会结束
fn forever() -> ! {
loop {
println!("永远运行...");
}
}
// 3. 退出进程
fn exit_program() -> ! {
std::process::exit(0);
}
// 4. match 分支中的 continue、return、break 等
let result: Result<i32, &str> = Err("错误");
let value = match result {
Ok(v) => v,
Err(_) => return, // 这里也是 ! 类型
};
3.控制流
3.1 if
形式1:
形式2:
3.2match 表达式(模式匹配)
// 1. 基本匹配
let number = 3;
match number {
1 => println!("一"),
2 => println!("二"),
3 => println!("三"),
_ => println!("其他"), // 通配符,必须!
}
// 2. 匹配范围
match number {
1..=5 => println!("1到5之间"), // 包含边界
6..=10 => println!("6到10之间"),
_ => println!("其他"),
}
// 3. 作为表达式返回值
let description = match number {
1 => "壹",
2 => "贰",
3 => "叁",
_ => "其他数字",
};
// 4. 多条件匹配
match number {
1 | 2 | 3 => println!("小数字"), // 或条件
4 | 5 | 6 => println!("中数字"),
_ => println!("大数字"),
}
// 5. 匹配守卫(额外条件)
match number {
x if x < 0 => println!("负数"),
x if x == 0 => println!("零"),
x if x > 0 => println!("正数"),
_ => unreachable!(), // 不可能到达
}