跳到主要内容

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-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
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);
}

解释

  • isizeusize 的大小取决于计算机架构(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示例
+Adda + b
-Suba - b
*Mula * b
/Diva / b
%Rema % b
&BitAnda & b
``BitOr
^BitXora ^ b
<<Shla << b
>>Shra >> b
- (负号)Neg-a
!Not!a
==PartialEqa == b
<PartialOrda < b
* (解引用)Deref*a
[]Indexa[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 Edition 匹配变更

Rust 2024 对模式匹配进行了改进(称为 match ergonomics reservations):

  1. 更严格的模式检查:某些可能导致混淆的模式组合现在会产生警告或错误
  2. 改进的推断:编译器能更好地推断绑定变量的类型

这些改进旨在为未来的模式匹配增强铺平道路,同时避免破坏现有代码。如果你的代码在迁移到 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),即返回 ! 类型,如 returnbreakpanic!

对比传统写法

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-elseif 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() {}

条件编译属性

cfgcfg_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 中用于编写"编写代码的代码"的机制,属于元编程的一种形式。与函数不同,宏在编译时展开,可以接受可变数量的参数,并且可以生成代码。

宏与函数的区别

特性函数
参数数量固定可变
执行时机运行时编译时
返回类型固定可生成任意代码
定义位置任意调用前必须定义
复杂度简单复杂
用途运行逻辑代码生成

为什么需要宏?

  1. 可变参数:如 println!("hello")println!("hello {}", name) 参数数量不同
  2. 编译时代码生成:如 derive 宏可以在编译时自动实现 trait
  3. 消除重复代码:当函数无法胜任的代码模式

声明式宏(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 + 2func()
ident标识符fooMyStruct
ty类型i32String
literal字面量42"hello"
stmt语句let x = 5;
pat模式Some(x)
path路径std::mem
tt标记树任何标记
block代码块{ ... }
itemfn 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)

过程式宏更像函数,接收代码作为输入,处理后输出新代码。有三种类型:

  1. 派生宏#[derive(MacroName)]
  2. 属性宏#[macro_name]
  3. 函数式宏macro_name!(...)

过程式宏必须在单独的 crate 中定义(crate 类型为 proc-macro),通常使用 synquote 库来解析和生成代码。

派生宏示例

// 在 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
  • 需要消除大量重复代码
  • 需要在编译时进行检查

不适合使用宏的场景

  • 普通函数可以解决的问题
  • 代码可读性要求高的场景
  • 调试困难的复杂逻辑

最佳实践

  1. 优先使用函数和泛型
  2. 宏命名使用 snake_case 并以 ! 结尾调用
  3. 添加文档注释说明宏的用法
  4. 避免过度复杂的宏

小结

本章我们学习了:

  1. 变量letmut、常量、变量遮蔽
  2. 数据类型:整数、浮点数、布尔、字符、元组、数组
  3. 函数:定义、参数、返回值、表达式
  4. 运算符:算术、比较、逻辑、位运算、赋值、类型转换
  5. 控制流ifloopwhileformatchif let
  6. 属性:条件编译、派生、诊断、函数优化
  7. :声明式宏、过程式宏、元变量、重复模式

练习

  1. 声明一个可变变量并修改它的值
  2. 创建一个包含不同类型元素的元组并解构
  3. 编写一个函数,计算两个数的最大公约数
  4. 使用 for 循环打印斐波那契数列的前 10 个数
  5. 使用 match 表达式实现一个简单的计算器
  6. 使用位运算实现一个简单的权限系统(读、写、执行)
  7. 为自定义类型实现 AddMul trait