Rust 基础语法
本章将介绍 Rust 的基础语法,包括变量、数据类型和函数。
变量和可变性
变量声明
在 Rust 中,使用 let 关键字声明变量:
fn main() {
// 声明变量(默认不可变)
let x = 5;
println!("x 的值是: {}", x);
// 重新绑定变量(变量遮蔽)
let x = x + 1;
println!("x 的值是: {}", x);
// 可以改变类型
let x = "现在 x 是字符串";
println!("{}", x);
}
解释:
- Rust 变量默认是不可变的(immutable)
- 变量遮蔽(shadowing)允许用同一个名字声明新变量
- 遮蔽时可以改变类型
可变变量
使用 mut 关键字声明可变变量:
fn main() {
let mut x = 5;
println!("x 的值是: {}", x);
x = 6; // 修改值
println!("x 的值是: {}", x);
// x = "string"; // 错误!不能改变类型
}
常量
使用 const 声明常量:
// 常量必须注明类型,且必须在编译时就能确定值
const MAX_POINTS: u32 = 100_000;
fn main() {
println!("最大分数: {}", MAX_POINTS);
}
常量与变量的区别:
| 特性 | 常量 (const) | 不可变变量 (let) |
|---|---|---|
| 可变性 | 永远不可变 | 可以遮蔽 |
| 类型注解 | 必须 | 可推断 |
| 作用域 | 可以是全局 | 块级作用域 |
| 值的来源 | 编译时确定 | 运行时确定 |
变量遮蔽 vs 可变变量
fn main() {
// 使用 mut
let mut spaces = " ";
// spaces = spaces.len(); // 错误!不能改变类型
// 使用遮蔽
let spaces = " ";
let spaces = spaces.len(); // 正确!可以改变类型
println!("空格数量: {}", spaces);
}
数据类型
Rust 是静态类型语言,编译时必须知道所有变量的类型。
标量类型
整数类型
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
fn main() {
// 十进制
let decimal = 98_222;
// 十六进制
let hex = 0xff;
// 八进制
let octal = 0o77;
// 二进制
let binary = 0b1111_0000;
// 字节(仅 u8)
let byte = b'A';
println!("十进制: {}, 十六进制: {}, 八进制: {}, 二进制: {}, 字节: {}",
decimal, hex, octal, binary, byte);
}
解释:
isize和usize的大小取决于计算机架构(64位系统上是64位)- 使用
_作为分隔符提高可读性 - 整数溢出在 debug 模式会导致 panic,在 release 模式会回绕
浮点类型
fn main() {
let x = 2.0; // f64(默认)
let y: f32 = 3.0; // f32
// 浮点数运算
let sum = x + 2.5;
let product = x * 3.0;
println!("和: {}, 积: {}", sum, product);
}
布尔类型
fn main() {
let t = true;
let f: bool = false;
// 布尔值主要用于条件判断
if t {
println!("t 是 true");
}
}
字符类型
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
let chinese = '中';
// char 占 4 字节,代表一个 Unicode 标量值
println!("字符: {} {} {} {}", c, z, heart_eyed_cat, chinese);
}
复合类型
元组
fn main() {
// 创建元组
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 解构
let (x, y, z) = tup;
println!("x: {}, y: {}, z: {}", x, y, z);
// 索引访问
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
println!("值: {}, {}, {}", five_hundred, six_point_four, one);
// 单元素元组需要逗号
let single = (5,);
// 空元组(单元类型)
let unit = ();
}
数组
fn main() {
// 创建数组
let a = [1, 2, 3, 4, 5];
// 指定类型和长度
let b: [i32; 5] = [1, 2, 3, 4, 5];
// 重复元素初始化
let c = [3; 5]; // 等同于 [3, 3, 3, 3, 3]
// 访问元素
let first = a[0];
let second = a[1];
println!("第一个: {}, 第二个: {}", first, second);
// 越界访问会导致 panic
// let invalid = a[10]; // 运行时错误
// 数组是固定大小的
println!("数组长度: {}", a.len());
// 数组是栈分配的
println!("数组占用空间: {} 字节", std::mem::size_of_val(&a));
}
解释:
- 数组大小固定,在栈上分配
- 越界访问会在运行时导致 panic
- 使用
std::mem::size_of_val可以查看大小
函数
函数定义
fn main() {
println!("Hello, world!");
another_function();
print_labeled_measurement(5, 'h');
}
// 无参数函数
fn another_function() {
println!("另一个函数");
}
// 带参数函数
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("测量值: {}{}", value, unit_label);
}
语句和表达式
Rust 是基于表达式的语言,区分语句和表达式:
fn main() {
// 语句:执行操作但不返回值
let x = 5; // 这是一个语句
// 表达式:计算并返回值
// 代码块是表达式
let y = {
let x = 3;
x + 1 // 表达式,没有分号
};
println!("y 的值是: {}", y); // y = 4
// 表达式可以返回值
let sum = add(5, 10);
println!("和: {}", sum);
}
fn add(a: i32, b: i32) -> i32 {
a + b // 返回值,没有分号
// 等同于 return a + b;
}
解释:
- 语句执行操作但不返回值,以分号结尾
- 表达式计算并返回值,不以分号结尾
- 代码块
{}是表达式 - 函数返回值可以用
return或直接写表达式
返回值
fn main() {
let x = plus_five(5);
println!("x = {}", x);
// 提前返回
let result = check_number(-1);
println!("结果: {}", result);
}
// 返回 i32 类型
fn plus_five(x: i32) -> i32 {
x + 5 // 隐式返回
}
// 使用 return 提前返回
fn check_number(x: i32) -> &'static str {
if x < 0 {
return "负数"; // 提前返回
}
"正数或零" // 隐式返回
}
// 返回单元类型 ()
fn do_nothing() {
// 没有返回值,相当于返回 ()
}
注释
fn main() {
// 单行注释
/*
多行注释
可以跨越多行
*/
/// 文档注释(支持 Markdown)
/// 用于生成文档
///
/// # Examples
/// ```
/// let x = add(1, 2);
/// ```
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 代码中也可以有注释
let x = 5; // 行尾注释
}
运算符
运算符是编程语言中最基本的工具之一,用于执行各种操作。Rust 提供了丰富的运算符,大多数与其他语言类似,但有一些独特的特性。
算术运算符
算术运算符用于执行基本的数学运算:
fn main() {
let a = 10;
let b = 3;
// 加法
let sum = a + b; // 13
println!("加法: {} + {} = {}", a, b, sum);
// 减法
let diff = a - b; // 7
println!("减法: {} - {} = {}", a, b, diff);
// 乘法
let product = a * b; // 30
println!("乘法: {} * {} = {}", a, b, product);
// 除法(整数除法会向下取整)
let quotient = a / b; // 3
println!("除法: {} / {} = {}", a, b, quotient);
// 取余
let remainder = a % b; // 1
println!("取余: {} % {} = {}", a, b, remainder);
// 负号
let neg = -a; // -10
println!("负号: -{} = {}", a, neg);
}
整数除法注意事项:
fn main() {
// 整数除法会向下取整(向零截断)
println!("7 / 3 = {}", 7 / 3); // 2
println!("7 / -3 = {}", 7 / -3); // -2
println!("-7 / 3 = {}", -7 / 3); // -2
println!("-7 / -3 = {}", -7 / -3); // 2
// 浮点数除法
println!("7.0 / 3.0 = {}", 7.0 / 3.0); // 2.3333333333333335
}
整数溢出:
fn main() {
// 在 debug 模式下,溢出会导致 panic
// 在 release 模式下,溢出会回绕(wrap around)
let mut x: u8 = 255;
// x += 1; // debug 模式:panic! release 模式:x 变成 0
// 显式处理溢出
x = x.wrapping_add(1); // 显式回绕:0
x = x.saturating_add(1); // 饱和:保持最大值 255
x = x.checked_add(1); // 返回 Option,溢出时返回 None
x = x.overflowing_add(1); // 返回 (结果, 是否溢出)
println!("x = {}", x);
}
比较运算符
比较运算符用于比较两个值,返回布尔值:
fn main() {
let a = 5;
let b = 10;
// 相等
println!("{} == {}: {}", a, b, a == b); // false
// 不相等
println!("{} != {}: {}", a, b, a != b); // true
// 小于
println!("{} < {}: {}", a, b, a < b); // true
// 小于等于
println!("{} <= {}: {}", a, b, a <= b); // true
// 大于
println!("{} > {}: {}", a, b, a > b); // false
// 大于等于
println!("{} >= {}: {}", a, b, a >= b); // false
}
比较浮点数:
fn main() {
let x = 0.1 + 0.2;
let y = 0.3;
// 由于浮点数精度问题,直接比较可能不准确
println!("0.1 + 0.2 == 0.3: {}", x == y); // false!
// 推荐方法:使用近似比较
let epsilon = 1e-10;
let is_equal = (x - y).abs() < epsilon;
println!("近似相等: {}", is_equal); // true
// 浮点数的特殊值
let inf = f64::INFINITY;
let neg_inf = f64::NEG_INFINITY;
let nan = f64::NAN;
println!("无穷大: {}", inf);
println!("负无穷: {}", neg_inf);
println!("NaN: {}", nan);
// NaN 的比较结果总是 false
println!("NaN == NaN: {}", nan == nan); // false
println!("NaN != NaN: {}", nan != nan); // true
// 使用 is_nan() 检查
println!("是否是 NaN: {}", nan.is_nan()); // true
}
逻辑运算符
逻辑运算符用于布尔值操作:
fn main() {
let a = true;
let b = false;
// 逻辑与(短路求值)
println!("{} && {}: {}", a, b, a && b); // false
// 逻辑或(短路求值)
println!("{} || {}: {}", a, b, a || b); // true
// 逻辑非
println!("!{}: {}", a, !a); // false
// 短路求值示例
let x = 5;
let result = x > 0 && x / 2 > 0; // 先计算左边,如果为 false,不计算右边
println!("结果: {}", result);
// 利用短路求值避免错误
let divisor = 0;
// if divisor != 0 && 10 / divisor > 1 { // 安全:先检查除数
// println!("计算结果有效");
// }
}
短路求值详解:
fn main() {
// && 短路:如果左边为 false,右边不会执行
let result = false && panic!("不会执行");
println!("结果: {}", result); // false
// || 短路:如果左边为 true,右边不会执行
let result = true || panic!("不会执行");
println!("结果: {}", result); // true
}
位运算符
位运算符用于对整数的二进制位进行操作:
fn main() {
let a: u8 = 0b1100; // 12
let b: u8 = 0b1010; // 10
// 按位与
println!("{:04b} & {:04b} = {:04b}", a, b, a & b); // 1000 (8)
// 按位或
println!("{:04b} | {:04b} = {:04b}", a, b, a | b); // 1110 (14)
// 按位异或
println!("{:04b} ^ {:04b} = {:04b}", a, b, a ^ b); // 0110 (6)
// 按位取反
println!("~{:04b} = {:08b}", a, !a); // 11110011 (243)
// 左移
println!("{:04b} << 2 = {:08b}", a, a << 2); // 00110000 (48)
// 右移
println!("{:04b} >> 2 = {:04b}", a, a >> 2); // 0011 (3)
}
位运算的实际应用:
fn main() {
// 设置标志位
const READ: u8 = 0b0001;
const WRITE: u8 = 0b0010;
const EXECUTE: u8 = 0b0100;
let mut permissions = READ | WRITE; // 可读可写
// 检查权限
if permissions & READ != 0 {
println!("有读权限");
}
// 添加权限
permissions |= EXECUTE;
println!("权限: {:04b}", permissions); // 0111
// 移除权限
permissions &= !WRITE;
println!("权限: {:04b}", permissions); // 0101
// 切换权限
permissions ^= EXECUTE;
println!("权限: {:04b}", permissions); // 0001
// 快速乘除法(位移)
let n = 8;
println!("{} * 2 = {}", n, n << 1); // 16
println!("{} / 2 = {}", n, n >> 1); // 4
}
赋值运算符
复合赋值运算符结合了运算和赋值:
fn main() {
let mut a = 10;
// 基本赋值
a = 5;
println!("a = {}", a);
// 加法赋值
a += 3; // a = a + 3
println!("a += 3: {}", a); // 8
// 减法赋值
a -= 2; // a = a - 2
println!("a -= 2: {}", a); // 6
// 乘法赋值
a *= 2; // a = a * 2
println!("a *= 2: {}", a); // 12
// 除法赋值
a /= 3; // a = a / 3
println!("a /= 3: {}", a); // 4
// 取余赋值
a %= 3; // a = a % 3
println!("a %= 3: {}", a); // 1
// 位运算赋值
a = 0b1100;
a &= 0b1010; // 按位与赋值
println!("a &= 0b1010: {:04b}", a); // 1000
a |= 0b0011; // 按位或赋值
println!("a |= 0b0011: {:04b}", a); // 1011
a ^= 0b1111; // 按位异或赋值
println!("a ^= 0b1111: {:04b}", a); // 0100
a <<= 2; // 左移赋值
println!("a <<= 2: {:08b}", a); // 00010000
a >>= 2; // 右移赋值
println!("a >>= 2: {:04b}", a); // 0100
}
类型转换运算符
Rust 是强类型语言,不同类型之间不能隐式转换,必须显式转换。
as 运算符
fn main() {
// 数值类型转换
let x: i32 = 42;
let y: f64 = x as f64; // 整数转浮点
println!("i32 -> f64: {} -> {}", x, y);
let a: f64 = 3.7;
let b: i32 = a as i32; // 浮点转整数(截断)
println!("f64 -> i32: {} -> {}", a, b); // 3
// 整数类型之间的转换
let small: u8 = 255;
let big: u16 = small as u16;
println!("u8 -> u16: {} -> {}", small, big); // 255
// 大类型转小类型(可能溢出)
let large: u16 = 300;
let truncated: u8 = large as u8;
println!("u16 -> u8: {} -> {}", large, truncated); // 44 (300 % 256)
// 字符与整数转换
let c: char = 'A';
let code: u32 = c as u32;
println!("char -> u32: '{}' -> {}", c, code); // 65
let code = 20013; // '中'
let c: char = char::from_u32(code).unwrap();
println!("u32 -> char: {} -> '{}'", code, c);
// 布尔转整数
let t = true;
let f = false;
println!("true as i32: {}", t as i32); // 1
println!("false as i32: {}", f as i32); // 0
}
转换规则总结:
| 源类型 | 目标类型 | 说明 |
|---|---|---|
| 整数 → 整数 | 较大类型 | 符号扩展或零扩展 |
| 整数 → 整数 | 较小类型 | 截断高位 |
| 浮点 → 整数 | 整数 | 向零截断 |
| 整数 → 浮点 | 浮点 | 精确转换 |
| 浮点 → 浮点 | 较大类型 | 精确转换 |
| 浮点 → 浮点 | 较小类型 | 舍入 |
From 和 Into trait
更安全的类型转换方式:
fn main() {
// From trait:提供 into() 方法
let s = String::from("hello");
// Into trait:自动实现
let s: String = "hello".into();
// 数值转换
let x: i32 = 42;
let y: i64 = i64::from(x); // 使用 From
let z: i64 = x.into(); // 使用 Into
println!("转换结果: {}, {}, {}", s, y, z);
// 自定义类型转换
#[derive(Debug)]
struct MyInt(i32);
impl From<i32> for MyInt {
fn from(value: i32) -> Self {
MyInt(value)
}
}
let n: MyInt = 42.into();
println!("自定义转换: {:?}", n);
}
TryFrom 和 TryInto
可能失败的类型转换:
use std::convert::TryFrom;
use std::convert::TryInto;
fn main() {
// 可能失败的转换
let large: i32 = 300;
// 使用 TryFrom
match u8::try_from(large) {
Ok(v) => println!("转换成功: {}", v),
Err(e) => println!("转换失败: {}", e), // out of range integral type conversion attempted
}
// 使用 TryInto
let result: Result<u8, _> = large.try_into();
match result {
Ok(v) => println!("转换成功: {}", v),
Err(e) => println!("转换失败: {}", e),
}
// 安全的范围检查
let small: i32 = 100;
if let Ok(v) = u8::try_from(small) {
println!("安全转换: {}", v); // 100
}
}
解引用运算符
fn main() {
// 解引用
let x = 5;
let ref_x = &x;
println!("引用: {}, 解引用: {}", ref_x, *ref_x);
// 可变引用
let mut y = 10;
let ref_y = &mut y;
*ref_y = 20; // 通过解引用修改值
println!("修改后: {}", y); // 20
// 智能指针解引用
let boxed = Box::new(42);
println!("Box 中的值: {}", *boxed); // 42
// 自动解引用
let s = String::from("hello");
let len = s.len(); // 自动解引用,调用 str 的方法
println!("长度: {}", len);
}
范围运算符
Rust 提供了方便的范围语法:
fn main() {
// 开区间范围 (不包含结束值)
for i in 1..5 {
print!("{} ", i); // 1 2 3 4
}
println!();
// 闭区间范围 (包含结束值)
for i in 1..=5 {
print!("{} ", i); // 1 2 3 4 5
}
println!();
// 从某个值开始
let nums: Vec<i32> = (3..).take(5).collect();
println!("从3开始: {:?}", nums); // [3, 4, 5, 6, 7]
// 到某个值结束
let nums: Vec<i32> = (..5).collect();
println!("到5结束: {:?}", nums); // [0, 1, 2, 3, 4]
// 完整范围
let arr = [1, 2, 3, 4, 5];
let slice = &arr[..]; // 整个数组
println!("完整切片: {:?}", slice);
// 切片操作
let slice = &arr[1..4]; // [2, 3, 4]
println!("部分切片: {:?}", slice);
let slice = &arr[2..=4]; // [3, 4, 5]
println!("闭区间切片: {:?}", slice);
}
运算符优先级
了解运算符优先级有助于写出正确的表达式:
fn main() {
// 优先级从高到低:
// 1. 一元运算符: !, -, *, & (解引用和借用)
// 2. 乘除模: *, /, %
// 3. 加减: +, -
// 4. 位移: <<, >>
// 5. 位与: &
// 6. 位异或: ^
// 7. 位或: |
// 8. 比较: ==, !=, <, >, <=, >=
// 9. 逻辑与: &&
// 10. 逻辑或: ||
// 11. 范围: .., ..=
// 12. 赋值: =, +=, -=, etc.
// 示例
let a = 2 + 3 * 4; // 14, 不是 20
println!("2 + 3 * 4 = {}", a);
let b = 10 > 5 && 3 < 4; // true
println!("10 > 5 && 3 < 4 = {}", b);
let c = 1 | 2 & 3; // 1 | (2 & 3) = 1 | 2 = 3
println!("1 | 2 & 3 = {}", c);
// 使用括号明确优先级
let d = (2 + 3) * 4; // 20
println!("(2 + 3) * 4 = {}", d);
}
建议:当不确定优先级时,使用括号明确表达式含义,这也能提高代码可读性。
运算符重载
Rust 允许通过实现特定的 trait 来重载运算符:
use std::ops::{Add, Sub, Mul};
#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
// 重载 + 运算符
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
// 重载 - 运算符
impl Sub for Point {
type Output = Point;
fn sub(self, other: Point) -> Point {
Point {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
// 重载 * 运算符(与标量相乘)
impl Mul<i32> for Point {
type Output = Point;
fn mul(self, scalar: i32) -> Point {
Point {
x: self.x * scalar,
y: self.y * scalar,
}
}
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 3, y: 4 };
let sum = p1 + p2;
println!("p1 + p2 = {:?}", sum); // Point { x: 4, y: 6 }
let diff = p1 - p2;
println!("p1 - p2 = {:?}", diff); // Point { x: -2, y: -2 }
let scaled = p1 * 3;
println!("p1 * 3 = {:?}", scaled); // Point { x: 3, y: 6 }
}
可重载的运算符及对应 Trait:
| 运算符 | Trait | 示例 |
|---|---|---|
+ | Add | a + b |
- | Sub | a - b |
* | Mul | a * b |
/ | Div | a / b |
% | Rem | a % b |
& | BitAnd | a & b |
| ` | ` | BitOr |
^ | BitXor | a ^ b |
<< | Shl | a << b |
>> | Shr | a >> b |
- (负号) | Neg | -a |
! | Not | !a |
== | PartialEq | a == b |
< | PartialOrd | a < b |
* (解引用) | Deref | *a |
[] | Index | a[b] |
控制流
if 表达式
fn main() {
let number = 6;
// 基本用法
if number % 4 == 0 {
println!("number 能被 4 整除");
} else if number % 3 == 0 {
println!("number 能被 3 整除");
} else {
println!("number 不能被 4 或 3 整除");
}
// if 是表达式,可以用于 let 语句
let condition = true;
let number = if condition { 5 } else { 6 };
println!("number 的值是: {}", number);
// 分支必须返回相同类型
// let number = if condition { 5 } else { "six" }; // 错误!
}
循环
loop 循环
fn main() {
let mut count = 0;
// 无限循环
let result = loop {
count += 1;
if count == 10 {
break count * 2; // break 可以返回值
}
};
println!("结果: {}", result);
// 循环标签
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;
}
}
while 循环
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("发射!");
// 遍历数组
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("值: {}", a[index]);
index += 1;
}
}
for 循环
fn main() {
// 遍历数组(推荐方式)
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("值: {}", element);
}
// Range
for number in (1..4).rev() {
println!("{}!", number);
}
println!("发射!");
// 遍历索引
for i in 0..a.len() {
println!("索引 {}: {}", i, a[i]);
}
}
match 表达式
fn main() {
let number = 13;
// 基本匹配
match number {
1 => println!("一"),
2 | 3 | 5 | 7 | 11 => println!("这是质数"), // or-patterns:多个值共享同一分支
13..=19 => println!("十几"),
_ => println!("其他数字"), // 默认分支
}
// match 是表达式
let boolean = true;
let binary = match boolean {
false => 0,
true => 1,
};
println!("{} => {}", boolean, binary);
// 匹配元组
let pair = (2, -2);
match pair {
(0, y) => println!("x 是零,y 是{}", y),
(x, 0) => println!("x 是{},y 是零", x),
_ => println!("其他情况"),
}
}
or-patterns:组合多个模式
使用 | 可以将多个模式组合在一起,共享相同的处理逻辑:
fn main() {
let command = "--help";
match command {
// 多个字符串字面量
"-h" | "--help" => println!("显示帮助信息"),
"-v" | "--version" => println!("显示版本"),
// 组合范围和值
"0" | "1" | "2" => println!("小数字"),
_ => println!("未知命令"),
}
// 在复杂模式中使用 or-patterns
let point = (0, 5);
match point {
(0, _) | (_, 0) => println!("在坐标轴上"),
(x, y) if x > 0 && y > 0 => println!("第一象限"),
_ => println!("其他位置"),
}
}
Rust 2024 对模式匹配进行了改进(称为 match ergonomics reservations):
- 更严格的模式检查:某些可能导致混淆的模式组合现在会产生警告或错误
- 改进的推断:编译器能更好地推断绑定变量的类型
这些改进旨在为未来的模式匹配增强铺平道路,同时避免破坏现有代码。如果你的代码在迁移到 Rust 2024 后出现模式匹配相关的警告,请根据编译器建议进行修改。
let-else 模式
let-else 是 Rust 1.65 引入的语法,用于在模式匹配失败时提前返回或跳出。它结合了 let 的简洁性和 match 的灵活性,让代码更加清晰。
基本语法
fn main() {
let option = Some(42);
// 使用 let-else 解构
let Some(value) = option else {
println!("没有值");
return;
};
println!("值是: {}", value);
}
语法说明:
let PATTERN = expression else { ... }- 如果模式匹配成功,绑定的变量可以在后续代码中使用
- 如果模式匹配失败,执行
else块,该块必须发散(diverge),即返回!类型,如return、break、panic!等
对比传统写法
fn process(value: Option<i32>) {
// 传统写法:使用 match
match value {
Some(v) => {
println!("处理值: {}", v);
// 更多代码...
}
None => {
println!("没有值");
return;
}
}
}
fn process_better(value: Option<i32>) {
// 使用 let-else:更简洁
let Some(v) = value else {
println!("没有值");
return;
};
println!("处理值: {}", v);
// 更多代码...
}
常见应用场景
处理 Option 类型:
fn get_user_name(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn greet(id: u32) {
let Some(name) = get_user_name(id) else {
println!("用户不存在");
return;
};
println!("你好, {}!", name);
}
fn main() {
greet(1); // 输出: 你好, Alice!
greet(2); // 输出: 用户不存在
}
处理 Result 类型:
fn parse_number(s: &str) -> Result<i32, String> {
s.parse().map_err(|_| format!("无法解析 '{}' 为数字", s))
}
fn process_number(s: &str) {
let Ok(num) = parse_number(s) else {
println!("输入无效");
return;
};
println!("数字的平方是: {}", num * num);
}
fn main() {
process_number("42"); // 输出: 数字的平方是: 1764
process_number("abc"); // 输出: 输入无效
}
解构复杂类型:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn handle_move(message: Message) {
let Message::Move { x, y } = message else {
println!("不是移动消息");
return;
};
println!("移动到 ({}, {})", x, y);
}
fn main() {
handle_move(Message::Move { x: 10, y: 20 });
handle_move(Message::Quit);
}
结合守卫条件:
fn process_positive(value: Option<i32>) {
let Some(n) = value else {
println!("没有值");
return;
};
// 额外的条件检查
if n <= 0 {
println!("值必须为正数");
return;
}
println!("正数: {}", n);
}
// 更简洁的写法:在循环中使用
fn find_first_positive(numbers: &[i32]) {
for &n in numbers {
if n > 0 {
println!("找到正数: {}", n);
return;
}
}
println!("没有找到正数");
}
在循环中使用:
fn find_item(items: &[Option<i32>]) {
for item in items {
let Some(value) = item else {
continue; // 跳过 None
};
if value > 10 {
println!("找到大于 10 的值: {}", value);
break;
}
}
}
fn main() {
let items = [None, Some(5), Some(15), Some(20)];
find_item(&items);
}
let-else vs if let
| 特性 | let-else | if let |
|---|---|---|
| 失败时处理 | 必须发散(return/break/panic) | 可以执行任意代码 |
| 作用域 | 变量在外部作用域可用 | 变量仅在其块内可用 |
| 适用场景 | 提前返回、验证前置条件 | 条件分支处理 |
| 代码风格 | 守卫模式(Guard Pattern) | 条件处理 |
fn example_let_else(value: Option<i32>) -> i32 {
// let-else:变量在外部可用
let Some(n) = value else {
return 0; // 必须发散
};
n * 2 // n 在这里可用
}
fn example_if_let(value: Option<i32>) -> i32 {
// if let:变量在块内可用
if let Some(n) = value {
n * 2
} else {
0
}
}
实际应用示例
配置验证:
struct Config {
host: Option<String>,
port: Option<u16>,
}
fn start_server(config: Config) {
let Some(host) = config.host else {
eprintln!("错误: 未配置主机地址");
return;
};
let Some(port) = config.port else {
eprintln!("错误: 未配置端口");
return;
};
println!("服务器启动在 {}:{}", host, port);
}
fn main() {
let config = Config {
host: Some("127.0.0.1".to_string()),
port: Some(8080),
};
start_server(config);
}
链式验证:
fn validate_user(id: Option<u32>, name: Option<String>) {
let Some(id) = id else {
println!("缺少用户ID");
return;
};
let Some(name) = name else {
println!("缺少用户名");
return;
};
if name.is_empty() {
println!("用户名不能为空");
return;
}
println!("用户验证通过: {} ({})", name, id);
}
fn main() {
validate_user(Some(1), Some("Alice".to_string()));
validate_user(None, Some("Bob".to_string()));
validate_user(Some(2), None);
}
if let 语法
fn main() {
let some_value = Some(3);
// 使用 match
match some_value {
Some(3) => println!("三"),
_ => (),
}
// 使用 if let(更简洁)
if let Some(3) = some_value {
println!("三");
}
// 可以带 else
if let Some(x) = some_value {
println!("值是 {}", x);
} else {
println!("没有值");
}
}
while let 语法
fn main() {
let mut optional = Some(0);
// 当匹配成功时继续循环
while let Some(i) = optional {
if i > 5 {
println!("大于 5,退出");
optional = None;
} else {
println!("i 是 {}", i);
optional = Some(i + 1);
}
}
}
属性(Attributes)
属性是 Rust 中用于向编译器、代码检查工具或其他工具添加元数据的语法结构。它们可以附加到各种程序元素上,如函数、结构体、枚举、模块等,用于控制编译行为、条件编译、文档生成等。
属性语法
Rust 提供两种属性语法:
// 外部属性:应用于其后的项
#[attribute]
#[attribute(arg1, arg2)]
#[attribute = "value"]
// 内部属性:应用于其所在的项(使用 !)
#![attribute]
#![attribute(arg1, arg2)]
外部属性 vs 内部属性:
| 类型 | 语法 | 应用对象 |
|---|---|---|
| 外部属性 | #[...] | 紧随其后的项 |
| 内部属性 | #![...] | 其所在的项(通常是模块或 crate) |
// 外部属性:应用于函数
#[inline]
fn quick_function() {}
// 内部属性:应用于整个模块
#![allow(unused_variables)]
mod my_module {
// 这里的代码...
}
属性语法形式
属性支持多种参数形式:
// 无参数
#[inline]
fn function() {}
// 键值对
#[doc = "这是文档注释"]
struct MyStruct;
// 列表形式
#[allow(unused, dead_code)]
fn unused_function() {}
// 嵌套键值对
#[cfg(target_os = "linux")]
fn linux_only() {}
条件编译属性
cfg 和 cfg_attr 用于条件编译:
// 仅在调试模式下编译
#[cfg(debug_assertions)]
fn debug_only() {
println!("调试模式");
}
// 仅在特定平台编译
#[cfg(target_os = "linux")]
fn linux_specific() {
println!("Linux 特定代码");
}
// 多条件组合
#[cfg(all(unix, target_pointer_width = "64"))]
fn on_64bit_unix() {
println!("64位 Unix 系统");
}
// 任一条件满足
#[cfg(any(windows, target_os = "macos"))]
fn on_windows_or_mac() {
println!("Windows 或 macOS");
}
// cfg_attr:条件满足时应用属性
#[cfg_attr(debug_assertions, derive(Debug))]
struct Config {
value: i32,
}
fn main() {
debug_only();
linux_specific();
}
常用条件选项:
| 条件 | 说明 |
|---|---|
debug_assertions | 调试模式 |
target_os = "linux" | 目标操作系统 |
target_arch = "x86_64" | 目标架构 |
feature = "name" | 特性标志 |
test | 测试模式 |
派生属性
derive 属性可以自动实现特定 trait:
// 自动派生多个 trait
#[derive(Debug, Clone, PartialEq, Eq)]
struct Person {
name: String,
age: u32,
}
fn main() {
let p1 = Person {
name: String::from("Alice"),
age: 30,
};
// Clone
let p2 = p1.clone();
// PartialEq
assert_eq!(p1, p2);
// Debug
println!("{:?}", p1);
}
常用可派生 Trait:
| Trait | 功能 |
|---|---|
Debug | 调试格式输出 |
Clone | 显式克隆 |
Copy | 隐式复制 |
PartialEq | 相等比较 |
Eq | 完全相等 |
PartialOrd | 部分排序 |
Ord | 完全排序 |
Hash | 哈希计算 |
Default | 默认值 |
诊断属性
控制编译器警告和错误:
// 允许特定警告
#[allow(unused_variables)]
fn allow_unused() {
let x = 5; // 不警告未使用
}
// 拒绝特定警告(编译错误)
#[deny(unused_variables)]
fn deny_unused() {
let x = 5; // 编译错误!
}
// 警告特定情况
#[warn(unused_variables)]
fn warn_unused() {
let x = 5; // 警告
}
// 禁止特定警告(比 deny 更严格)
#[forbid(unsafe_code)]
fn no_unsafe() {
// unsafe { } // 编译错误!
}
// 标记为已弃用
#[deprecated(since = "1.0.0", note = "请使用 new_function")]
fn old_function() {}
// 标记返回值必须使用
#[must_use = "函数结果必须使用"]
fn important_value() -> i32 {
42
}
fn main() {
allow_unused();
important_value(); // 警告:未使用返回值
}
函数和代码生成属性
// 内联提示
#[inline] // 建议内联
#[inline(always)] // 强制内联
#[inline(never)] // 永不内联
fn optimized() {}
// 冷门函数(很少调用)
#[cold]
fn error_handler() {}
// 禁止命名修饰
#[no_mangle]
pub extern "C" fn c_callable() {}
// 导出名称
#[export_name = "my_custom_name"]
pub fn exported() {}
// 链接段
#[link_section = ".my_section"]
static MY_DATA: [u8; 4] = [1, 2, 3, 4];
fn main() {
optimized();
error_handler();
}
文档属性
/// 这是文档注释
///
/// # Examples
///
/// ```
/// let x = my_function();
/// ```
#[doc = "也可以使用属性"]
fn my_function() {}
// 隐藏文档中的内容
#[doc(hidden)]
fn internal_api() {}
// 文档别名
#[doc(alias = "别名")]
fn documented() {}
测试属性
// 标记测试函数
#[test]
fn test_something() {
assert_eq!(2 + 2, 4);
}
// 忽略测试
#[test]
#[ignore]
fn slow_test() {}
// 期望 panic
#[test]
#[should_panic(expected = "错误信息")]
fn test_panic() {
panic!("错误信息");
}
常用属性速查
| 属性 | 用途 |
|---|---|
#[derive(Trait)] | 自动实现 trait |
#[cfg(condition)] | 条件编译 |
#[allow/warn/deny/forbid] | 诊断控制 |
#[inline] | 内联提示 |
#[test] | 测试标记 |
#[doc] | 文档 |
#[deprecated] | 弃用标记 |
#[must_use] | 必须使用返回值 |
宏(Macros)
宏是 Rust 中用于编写"编写代码的代码"的机制,属于元编程的一种形式。与函数不同,宏在编译时展开,可以接受可变数量的参数,并且可以生成代码。
宏与函数的区别
| 特性 | 函数 | 宏 |
|---|---|---|
| 参数数量 | 固定 | 可变 |
| 执行时机 | 运行时 | 编译时 |
| 返回类型 | 固定 | 可生成任意代码 |
| 定义位置 | 任意 | 调用前必须定义 |
| 复杂度 | 简单 | 复杂 |
| 用途 | 运行逻辑 | 代码生成 |
为什么需要宏?
- 可变参数:如
println!("hello")和println!("hello {}", name)参数数量不同 - 编译时代码生成:如
derive宏可以在编译时自动实现 trait - 消除重复代码:当函数无法胜任的代码模式
声明式宏(macro_rules!)
声明式宏是最常用的宏形式,使用 macro_rules! 定义,语法类似于模式匹配。
基本语法
// 定义宏
macro_rules! say_hello {
// 匹配规则
() => {
println!("Hello!");
};
}
fn main() {
// 调用宏
say_hello!();
}
带参数的宏
macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("函数 {:?} 被调用", stringify!($func_name));
}
};
}
fn main() {
create_function!(foo);
create_function!(bar);
foo();
bar();
}
元变量类型
宏可以匹配不同类型的语法元素,通过片段分类符指定:
macro_rules! demo {
// expr: 表达式
($e:expr) => {
println!("表达式: {}", $e);
};
// ident: 标识符
($i:ident) => {
println!("标识符: {}", stringify!($i));
};
// ty: 类型
($t:ty) => {
println!("类型: {}", stringify!($t));
};
// literal: 字面量
($l:literal) => {
println!("字面量: {}", $l);
};
// stmt: 语句
($s:stmt) => {
println!("语句: {}", stringify!($s));
};
// pat: 模式
($p:pat) => {
println!("模式: {}", stringify!($p));
};
// path: 路径
($p:path) => {
println!("路径: {}", stringify!($p));
};
// tt: 标记树(任何标记)
($t:tt) => {
println!("标记: {}", stringify!($t));
};
// block: 代码块
($b:block) => {
println!("代码块");
};
// item: 项(函数、结构体等)
($i:item) => {
println!("项: {}", stringify!($i));
};
// vis: 可见性修饰符
($v:vis) => {
println!("可见性: {}", stringify!($v));
};
}
fn main() {
demo!(1 + 2); // expr
demo!(my_var); // ident
demo!(i32); // ty
demo!(42); // literal
demo!(let x = 5;); // stmt
demo!(Some(x)); // pat
demo!(std::collections::HashMap); // path
}
常用片段分类符:
| 分类符 | 匹配内容 | 示例 |
|---|---|---|
expr | 表达式 | 1 + 2、func() |
ident | 标识符 | foo、MyStruct |
ty | 类型 | i32、String |
literal | 字面量 | 42、"hello" |
stmt | 语句 | let x = 5; |
pat | 模式 | Some(x) |
path | 路径 | std::mem |
tt | 标记树 | 任何标记 |
block | 代码块 | { ... } |
item | 项 | fn foo() {} |
重复模式
宏支持重复匹配,使用 $() 包裹重复部分,后跟重复操作符:
// * 表示零次或多次
macro_rules! vec_macro {
($($x:expr),*) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
// + 表示一次或多次
macro_rules! require_at_least_one {
($($x:expr),+) => {
$($x)+
};
}
// ? 表示零次或一次
macro_rules! maybe_expr {
($($x:expr)?) => {
$($( $x )?)?
};
}
fn main() {
let v = vec_macro![1, 2, 3, 4, 5];
println!("向量: {:?}", v);
let empty = vec_macro![];
println!("空向量: {:?}", empty);
}
多分支宏
宏可以有多个匹配分支:
macro_rules! calculate {
// 单个值
($val:expr) => {
$val
};
// 加法
($left:expr + $right:expr) => {
$left + $right
};
// 乘法
($left:expr * $right:expr) => {
$left * $right
};
// 多参数加法
($($val:expr),+ $(,)?) => {
$($val)+
};
}
fn main() {
println!("单个值: {}", calculate!(42));
println!("加法: {}", calculate!(1 + 2));
println!("乘法: {}", calculate!(3 * 4));
}
实用宏示例
计算哈希值:
macro_rules! hash {
// 空情况
() => { 0 };
// 单个值
($key:expr) => {
$key
};
// 多个值递归
($first:expr, $($rest:expr),+) => {
{
let h = $first;
$(
let h = h.wrapping_mul(31).wrapping_add($rest);
)+
h
}
};
}
fn main() {
println!("hash!: {}", hash!());
println!("hash!(1): {}", hash!(1));
println!("hash!(1, 2, 3): {}", hash!(1, 2, 3));
}
结构体构建器:
macro_rules! struct_builder {
($name:ident { $($field:ident: $type:ty),* $(,)? }) => {
pub struct $name {
$(pub $field: $type,)*
}
impl $name {
pub fn new() -> Self {
Self {
$($field: Default::default(),)*
}
}
$(
pub fn $field(mut self, value: $type) -> Self {
self.$field = value;
self
}
)*
}
};
}
// 使用宏生成结构体
struct_builder!(Person {
name: String,
age: u32,
});
fn main() {
let person = Person::new()
.name(String::from("Alice"))
.age(30);
println!("{}: {}", person.name, person.age);
}
过程式宏(Procedural Macros)
过程式宏更像函数,接收代码作为输入,处理后输出新代码。有三种类型:
- 派生宏:
#[derive(MacroName)] - 属性宏:
#[macro_name] - 函数式宏:
macro_name!(...)
过程式宏必须在单独的 crate 中定义(crate 类型为 proc-macro),通常使用 syn 和 quote 库来解析和生成代码。
派生宏示例
// 在 proc-macro crate 中
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let expanded = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}
// 使用
// #[derive(HelloMacro)]
// struct Pancakes;
//
// Pancakes::hello_macro(); // 输出: Hello, Macro! My name is Pancakes
属性宏示例
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// attr: 属性参数
// item: 被标注的项
item
}
// 使用
// #[route(GET, "/")]
// fn index() {}
函数式宏示例
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
// 处理 SQL 语法
input
}
// 使用
// let query = sql!(SELECT * FROM users);
宏的卫生性(Hygiene)
Rust 宏是"卫生"的,宏内定义的变量不会与调用处的变量冲突:
macro_rules! hygienic {
() => {
let x = 1; // 宏内的 x
println!("宏内的 x: {}", x);
};
}
fn main() {
let x = 2; // 函数内的 x
hygienic!();
println!("函数内的 x: {}", x); // 仍然是 2
}
常用内置宏
fn main() {
// 打印宏
println!("Hello, {}!", "world");
print!("不换行");
eprintln!("错误输出");
// 格式化宏
let s = format!("值: {}", 42);
// 调试宏
let x = 42;
dbg!(x); // 输出到 stderr
// 编译时断言
const _: () = assert!(std::mem::size_of::<usize>() == 8);
// 环境变量
let path = env!("PATH");
// 字符串化
let name = stringify!(hello_world);
println!("{}", name); // "hello_world"
// 连接标识符
let paste_test = 42;
println!("{}", paste_test);
// 包含文件
// let content = include_str!("data.txt");
// 代码行和文件
println!("文件: {}, 行: {}", file!(), line!());
}
宏使用建议
适合使用宏的场景:
- 需要可变参数(如
println!、vec!) - 需要编译时代码生成(如
derive) - 需要消除大量重复代码
- 需要在编译时进行检查
不适合使用宏的场景:
- 普通函数可以解决的问题
- 代码可读性要求高的场景
- 调试困难的复杂逻辑
最佳实践:
- 优先使用函数和泛型
- 宏命名使用
snake_case并以!结尾调用 - 添加文档注释说明宏的用法
- 避免过度复杂的宏
小结
本章我们学习了:
- 变量:
let、mut、常量、变量遮蔽 - 数据类型:整数、浮点数、布尔、字符、元组、数组
- 函数:定义、参数、返回值、表达式
- 运算符:算术、比较、逻辑、位运算、赋值、类型转换
- 控制流:
if、loop、while、for、match、if let - 属性:条件编译、派生、诊断、函数优化
- 宏:声明式宏、过程式宏、元变量、重复模式
练习
- 声明一个可变变量并修改它的值
- 创建一个包含不同类型元素的元组并解构
- 编写一个函数,计算两个数的最大公约数
- 使用 for 循环打印斐波那契数列的前 10 个数
- 使用 match 表达式实现一个简单的计算器
- 使用位运算实现一个简单的权限系统(读、写、执行)
- 为自定义类型实现
Add和Multrait