面向有经验的开发者——从 Python / Go / Java 平滑过渡到 Rust
Rust 是一门系统级编程语言,2015 年发布 1.0,由 Mozilla 研发,现在由独立的 Rust Foundation 维护。它的核心承诺可以用三句话概括:
通过所有权(Ownership)+ 借用检查(Borrow Checker)在编译期保证内存安全。没有空指针、没有悬垂引用、没有 double free。不需要垃圾回收器,没有 STW 停顿。
泛型单态化、内联、迭代器链——你写高层抽象,编译器帮你展开成底层代码。抽象的代价为零,性能等于手写。
Send/Sync trait 在编译期标记类型是否可以跨线程共享。数据竞争是编译错误,不是运行时 bug。并发安全不需要靠纪律。
| 特性 | Rust | Go | Python | Java |
|---|---|---|---|---|
| 内存管理 | 所有权(编译期) | GC | GC + 引用计数 | GC |
| 空指针 | ❌ 编译期消灭 | ✅ nil panic | ✅ None / AttributeError | ✅ NullPointerException |
| 数据竞争 | ❌ 编译期消灭 | ⚠️ race condition 可能 | ⚠️ GIL 掩盖问题 | ⚠️ 需要自己加锁 |
| 运行时开销 | 几乎为零 | 轻量 runtime | 解释器开销 | JVM 开销 |
| 泛型 | 单态化(零成本) | 无泛型(1.18 前) | 鸭子类型 | 类型擦除 |
| 包管理 | Cargo(内置) | go mod | pip + venv | Maven / Gradle |
Cargo 是 Rust 的构建工具 + 包管理器 + 测试运行器 + 文档生成器,一站式解决。对比:
cargo new myapp # 创建项目 cargo build # 编译 cargo run # 编译+运行 cargo test # 跑测试 cargo add serde # 加依赖 cargo doc --open # 生成文档
cmake -B build ... make -C build apt install libfoo-dev # 系统级依赖 valgrind ./myapp # 内存检查 doxygen # 文档 vcpkg / conan ... # 包管理各自为战
💡 一句话总结:Rust 的价值不在于"比 C++ 安全"或"比 Go 快",而在于让安全的代码也是高性能的代码。在 Rust 里,安全不是运行时开销,而是编译器帮你检查的契约。
# Linux / macOS curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 验证 rustc --version # e.g. rustc 1.78.0 cargo --version # e.g. cargo 1.78.0
rustup 管理 Rust 工具链的多个版本(stable / nightly / beta),类似 Python 的 pyenv 或 Go 的多版本管理。
cargo new hello_rust cd hello_rust # 生成的目录结构 # hello_rust/ # ├── Cargo.toml # 项目配置(类似 package.json / go.mod) # └── src/ # └── main.rs # 入口文件 cargo run # → Hello, world!
fn main() {
println!("Hello, world!");
}
⚠️ println! 是宏,不是函数——注意感叹号。Rust 的宏在编译期展开,支持格式化参数检查。日常使用中你不需要区分宏和函数,只需要知道带 ! 的是宏。
fn main() {
let name = "Rust";
let year = 2015;
// println! 格式化
println!("Hello, {}!", name); // 位置参数
println!("{0} was released in {1}, {0}!", name, year); // 命名索引
println!("{name} {year}", name=name, year=year); // 命名参数
println!("{:?}", (1, "two", 3.0)); // Debug 格式
println!("{:#?}", vec![1, 2, 3]); // 美化 Debug
// 对比 Python: f"Hello, {name}!"
// 对比 Go: fmt.Sprintf("Hello, %s!", name)
}
[package] name = "hello_rust" version = "0.1.0" edition = "2021" # Rust 版本(edition),不是编译器版本 [dependencies] # serde = "1.0" # 取消注释即可添加依赖
Edition 是 Rust 的语法版本(类似 C++ 的 C++17/C++20),每 3 年一个。目前最新是 2024 edition。你的编译器可以编译任何 edition 的代码。
fn main() {
let x = 5;
// x = 6; // ❌ 编译错误!cannot assign twice to immutable variable
let mut y = 5;
y = 6; // ✅ 可变变量可以重新赋值
}
这可能是 Rust 和其他语言最大的习惯差异。变量默认不可变,你需要显式声明 mut 才能修改。
💡 为什么默认不可变?不可变数据天然线程安全、推理简单、编译器优化空间更大。Go 和 Java 的 final / const 是"可选的不可变",Rust 是"默认不可变,显式可变"——心理学上,这让你在写 mut 时多想一秒"真的需要变吗?"
// 常量必须标注类型,命名全大写,编译期求值
const MAX_POINTS: u32 = 100_000; // 下划线提高可读性
fn main() {
println!("Max: {}", MAX_POINTS);
}
let | let mut | const | |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 不可变 |
| 类型标注 | 可选(推导) | 可选(推导) | 必须 |
| 求值时机 | 运行时 | 运行时 | 编译期 |
| 作用域 | 块作用域 | 块作用域 | 可跨模块 |
| 赋值内容 | 任意表达式 | 任意表达式 | 常量表达式 |
fn main() {
let x = 5;
let x = x + 1; // shadowing:用同一个名字声明新变量
let x = x * 2; // 再 shadow 一次
println!("x = {}", x); // x = 12
// shadowing 可以改变类型!
let spaces = " "; // &str
let spaces = spaces.len(); // usize — 类型变了
// mut 不行:
// let mut y = " ";
// y = y.len(); // ❌ 类型不匹配!
}
🚨 Shadowing ≠ Mutation——Shadowing 创建了一个新变量,只是碰巧用了同一个名字。原变量还在内存里(直到作用域结束)。这在类型转换场景很有用,但也容易让初学者困惑。记住:let 每次都是新绑定。
# 一切可变,没有 const x = 5 x = "hello" # 类型也变了 # "常量"只是约定 MAX = 100 # 其实还是能改
x := 5 // 短变量声明 x = 6 // 默认可变 const Max = 100 // 常量 // 没有 shadowing 语法糖
Rust 是静态强类型语言,编译器通常能推导类型,但有时需要你显式标注。
| 类型 | 大小 | 范围 | 说明 |
|---|---|---|---|
i8 / u8 | 1 字节 | -128~127 / 0~255 | u8 常用于字节 |
i32 / u32 | 4 字节 | ±21亿 / 0~42亿 | 整数默认类型 |
i64 / u64 | 8 字节 | ±9.2×10¹⁸ | 大整数 |
isize / usize | 指针大小 | 取决于平台 | 索引/长度用 usize |
f32 | 4 字节 | IEEE 754 单精度 | 一般不用 |
f64 | 8 字节 | IEEE 754 双精度 | 浮点默认类型 |
bool | 1 字节 | true / false | — |
char | 4 字节 | Unicode 标量值 | 不是 byte! |
// 整数字面量 let decimal = 98_222; // 下划线分隔 let hex = 0xff; let octal = 0o77; let binary = 0b1111_0000; let byte = b'A'; // u8 only // 浮点 let x = 2.0; // f64(默认) let y: f32 = 3.0; // f64 // 布尔 let t = true; let f: bool = false; // 字符 — Unicode,不是 ASCII byte let c = 'z'; let z = 'ℤ'; let heart = '❤'; let emoji = '🦀'; // char 是 4 字节 Unicode 标量值
⚠️ 整数溢出——debug 模式下溢出会 panic,release 模式下会 wrap(回绕)。如果你需要 wrap 行为,显式调用 wrapping_add 等方法。
fn main() {
let tup: (i32, f64, &str) = (500, 6.4, "hello");
// 解构
let (x, y, z) = tup;
println!("y = {}", y); // 6.4
// 索引访问(用 . 不是 [])
let first = tup.0; // 500
let second = tup.1; // 6.4
// 单元素元组
let single = (42,); // 注意逗号
let not_tuple = (42); // 这只是 42,括号表达式
}
fn main() {
// 固定长度,栈上分配
let arr = [1, 2, 3, 4, 5];
let arr: [i32; 5] = [1, 2, 3, 4, 5]; // 显式类型
let arr = [3; 5]; // [3, 3, 3, 3, 3]
// 访问
let first = arr[0];
// let out = arr[10]; // ❌ 编译错误 / 运行时 panic
// 数组是固定大小的!
// 需要动态大小?用 Vec
}
fn main() {
let arr = [1, 2, 3, 4, 5];
// 切片是对数组/Vec的引用视图
let slice: &[i32] = &arr[1..3]; // [2, 3]
let full: &[i32] = &arr[..]; // [1, 2, 3, 4, 5]
let from_two: &[i32] = &arr[2..]; // [3, 4, 5]
// 切片是胖指针 = (数据指针, 长度)
// Python: arr[1:3]
// Go: arr[1:3]
}
fn main() {
// &str — 字符串切片,借用的视图
let s1: &str = "hello"; // 字符串字面量,存在二进制里
let s2: &str = &String::from("world")[..];
// String — 堆分配的可增长字符串
let mut s3: String = String::from("hello");
s3.push_str(", world");
s3.push('!');
// &str → String
let s4 = s1.to_string(); // 方法1
let s5 = String::from(s1); // 方法2
// String → &str(自动解引用)
let s6: &str = &s3; // Deref coercion
take_str(s3.as_str()); // 显式转换
// 函数参数:优先用 &str(更通用)
fn take_str(s: &str) {
println!("{}", s);
}
take_str("literal"); // ✅ &str
take_str(&s3); // ✅ &String → &str (deref)
}
🚨 常见陷阱——&str 不是 &String!&str 是胖指针(指针+长度),String 是(指针+长度+容量)的三元组。函数参数用 &str 更灵活,因为它同时接受字面量和 &String(通过 deref coercion)。
// string 是不可变字节切片 s := "hello" b := []byte(s) // 可变
# str 是不可变 Unicode s = "hello" b = s.encode() # bytes # 没有"可变字符串"
// 参数必须声明类型
fn add(x: i32, y: i32) -> i32 {
x + y // 没有分号!最后一个表达式就是返回值
}
fn main() {
let result = add(3, 4);
println!("3 + 4 = {}", result);
}
这是 Rust 和 C/Go/Java 的核心区别:几乎所有东西都是表达式。
fn main() {
// 语句(statement)— 执行操作,不返回值
let x = 5; // let 是语句
// 表达式(expression)— 求值并返回结果
let y = {
let x = 3;
x + 1 // 没有分号 → 表达式,返回 4
// x + 1; // 加了分号 → 语句,返回 ()
};
println!("y = {}", y); // y = 4
// if 也是表达式!
let condition = true;
let number = if condition { 5 } else { 6 };
println!("number = {}", number); // 5
}
🚨 分号杀死返回值——在 Rust 中,x + 1 是表达式(返回值),x + 1; 是语句(返回 ())。这是新手最常犯的错误:在函数末尾加了分号,导致返回 () 而不是期望的值。编译器会提示 "mismatched types",注意检查分号。
// 返回单元类型 () — 可以省略
fn say_hello() {
println!("Hello!");
}
// 等价于
fn say_hello() -> () {
println!("Hello!");
}
// 提前返回用 return(通常只在提前返回时用)
fn abs(x: i32) -> i32 {
if x < 0 {
return -x; // 提前返回
}
x // 最后一行用表达式,不用 return
}
func add(x, y int) int {
return x + y // 必须写 return
}
// if 不是表达式
def add(x: int, y: int) -> int:
return x + y # 必须写 return
# 三元表达式: x if cond else y
fn main() {
let number = 6;
// 不需要括号!
if number % 4 == 0 {
println!("divisible by 4");
} else if number % 3 == 0 {
println!("divisible by 3");
} else if number % 2 == 0 {
println!("divisible by 2");
} else {
println!("not divisible by 4, 3, or 2");
}
// if 作为表达式 — 两个分支类型必须一致
let condition = true;
let x = if condition { 5 } else { 6 };
// let x = if condition { 5 } else { "six" }; // ❌ 类型不匹配
// 对比 Python: x = 5 if condition else 6
// 对比 Go: 没有 ternary,必须 if-else
}
fn main() {
// 基本用法
let mut count = 0;
loop {
count += 1;
if count == 5 {
break; // 退出循环
}
}
// loop 带返回值!
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break 带值 → loop 表达式的值
}
};
println!("result = {}", result); // 20
// 循环标签 — 嵌套循环中指定 break/continue 哪一层
'outer: loop {
println!("outer");
loop {
println!("inner");
break 'outer; // 跳出外层循环
}
}
}
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
// while 不带返回值(永远是 ())
// 需要返回值?用 loop + break
}
fn main() {
// 遍历数组
let arr = [10, 20, 30, 40, 50];
for element in arr {
println!("value: {}", element);
}
// Range
for i in 0..5 { // 0, 1, 2, 3, 4(不含5)
println!("{}", i);
}
for i in 0..=5 { // 0, 1, 2, 3, 4, 5(含5)
println!("{}", i);
}
// 反向
for i in (1..4).rev() {
println!("{}!", i); // 3! 2! 1!
}
// 步长
for i in (0..10).step_by(2) {
println!("{}", i); // 0 2 4 6 8
}
// enumerate(类似 Python)
for (i, val) in arr.iter().enumerate() {
println!("{}: {}", i, val);
}
}
✅ 优先用 for 遍历——Rust 的 for 遍历的是迭代器,不是索引。这比 while 更安全(不会越界)、更快(编译器能优化边界检查)。只有需要索引或复杂控制流时才用 while。
fn main() {
for i in 0..10 {
if i == 3 {
continue; // 跳过 3
}
if i == 7 {
break; // 到 7 停止
}
println!("{}", i); // 0 1 2 4 5 6
}
}
Rust 的模式匹配是语言级特性,不是 switch-case 的语法糖。编译器会检查穷举性(exhaustiveness)——漏掉一个分支就是编译错误。
fn main() {
let number = 13;
match number {
1 => println!("one"),
2 | 3 | 5 => println!("prime under 5"),
13..=19 => println!("a teen"), // 范围模式
_ => println!("something else"), // _ 是通配符
}
// match 是表达式
let text = match number {
1 => "one",
2 => "two",
3..=9 => "single digit",
_ => "big number",
};
println!("{}", text);
}
fn main() {
// 解构元组
let point = (3, -5);
match point {
(0, 0) => println!("origin"),
(x, 0) => println!("on x-axis at {}", x),
(0, y) => println!("on y-axis at {}", y),
(x, y) => println!("at ({}, {})", x, y),
}
// 解构结构体
struct Color { r: u8, g: u8, b: u8 }
let c = Color { r: 255, g: 0, b: 128 };
match c {
Color { r: 255, g: 0, b } => println!("red-ish, blue={}", b),
Color { r, g, b } => println!("r={}, g={}, b={}", r, g, b),
}
// 解构枚举
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
let msg = Message::Move { x: 10, y: 20 };
match msg {
Message::Quit => println!("quit"),
Message::Move { x, y } => println!("move to ({}, {})", x, y),
Message::Write(text) => println!("write: {}", text),
}
}
fn main() {
let pair = (2, -2);
match pair {
(x, y) if x == y => println!("twin"),
(x, y) if x + y == 0 => println!("opposites"),
(x, _) if x > 0 => println!("positive x"),
_ => println!("no match"),
}
}
fn main() {
let value = Some(7);
// 用 match 有点啰嗦
match value {
Some(v) => println!("got {}", v),
_ => (), // 不关心 None
}
// if let 更简洁
if let Some(v) = value {
println!("got {}", v);
}
// 也可以加 else
if let Some(v) = value {
println!("some: {}", v);
} else {
println!("none");
}
// while let — 持续匹配直到 None
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top); // 3 2 1
}
}
⚠️ match vs if let——match 要求穷举所有分支,编译器帮你检查遗漏。if let 只处理一个模式,其他静默忽略。当你确实需要处理所有分支时,用 match;当你只关心一种情况时,用 if let。
// 没有 pattern matching
// 用 type switch(interface)
switch v := x.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("str:", v)
default:
fmt.Println("unknown")
}
// 不穷举检查,没有解构
match value:
case Some(v):
print(f"got {v}")
case None:
print("none")
# 结构化模式匹配
# 有穷举提示但不强制
Rust 没有 exception。错误处理分两类:可恢复错误用 Result,不可恢复错误用 panic!。
fn main() {
// panic 会展开栈并终止线程
panic!("something went terribly wrong!");
// 数组越界也会 panic
let v = vec![1, 2, 3];
// v[99]; // panic: index out of bounds
}
use std::fs; use std::io; // Result 的定义 // enum Result{ // Ok(T), // 成功,包含值 // Err(E), // 失败,包含错误 // } fn read_file(path: &str) -> Result { fs::read_to_string(path) // 返回 Result } fn main() { // 处理 Result:match match read_file("hello.txt") { Ok(content) => println!("content: {}", content), Err(e) => println!("error: {}", e), } // 处理 Result:if let if let Ok(content) = read_file("hello.txt") { println!("content: {}", content); } }
use std::fs; use std::io; // 没有 ? 的写法 fn read_username_manual(path: &str) -> Result{ let content = match fs::read_to_string(path) { Ok(c) => c, Err(e) => return Err(e), // 手动传播 }; Ok(content.trim().to_string()) } // 用 ? 的写法 — 遇到 Err 自动提前返回 fn read_username(path: &str) -> Result { let content = fs::read_to_string(path)?; // Ok → 解包;Err → return Err Ok(content.trim().to_string()) } // ? 可以链式调用! fn read_and_parse(path: &str) -> Result > { let num: i32 = fs::read_to_string(path)?.trim().parse()?; Ok(num) // 两种不同的错误类型都自动转换成 Box } fn main() -> Result<(), Box > { let name = read_username("user.txt")?; println!("username: {}", name); Ok(()) }
// Option 的定义 // enum Option{ // Some(T), // 有值 // None, // 无值 // } fn find_user(id: u32) -> Option { if id == 1 { Some("Alice".to_string()) } else { None } } fn main() { // match 处理 match find_user(1) { Some(name) => println!("found: {}", name), None => println!("not found"), } // ? 也能用于 Option! fn get_name(id: u32) -> Option { let name = find_user(id)?; // None → return None Some(format!("User: {}", name)) } // 常用方法 let x: Option = Some(42); x.unwrap(); // 42 — None 时 panic x.expect("must have value"); // 同上但可自定义消息 x.unwrap_or(0); // 42 — None 时返回默认值 0 x.unwrap_or_else(|| expensive_default()); // 惰性默认值 x.map(|v| v * 2); // Some(84) — 转换内部值 x.and_then(|v| Some(v + 1)); // Some(43) — 链式操作 x.filter(|v| *v > 40); // Some(42) — 过滤 }
🚨 unwrap() 是双刃剑——unwrap() 遇到 None 或 Err 会 panic。原型开发可以随便用,生产代码应该用 ?、unwrap_or、expect("reason") 或显式 match。expect 比 unwrap 好,因为你的错误消息会出现在 panic 信息里。
// 多返回值
val, err := strconv.Atoi("42")
if err != nil {
return err
}
// 没有 Option,用 nil/零值
// 没有 ? 运算符
# 异常
try:
val = int("42")
except ValueError as e:
raise MyError(e)
# Optional 类型提示
def find(id: int) -> str | None:
...
Rust 的模块系统用来组织代码、控制可见性。和 Go 的 package / Java 的 package 不同,Rust 模块更像命名空间。
// 默认私有,pub 才能外部访问
mod network {
pub fn connect() {
println!("connecting...");
}
fn check_status() { // 私有,外部不可见
println!("checking...");
}
pub mod server { // 嵌套模块
pub fn start() {
println!("server started");
super::check_status(); // super 访问父模块
}
}
}
fn main() {
network::connect(); // ✅ pub
// network::check_status(); // ❌ 私有
network::server::start(); // ✅ pub → pub
}
use std::collections::HashMap; // 引入类型
use std::fs::{self, File}; // 同时引入模块和类型
use std::io::Result as IoResult; // 别名
fn main() {
let mut map = HashMap::new(); // 不用写全路径
map.insert("key", "value");
let _result: IoResult<()> = Ok(());
}
项目稍微大一点就要拆文件。Cargo 项目的标准结构:
myapp/
├── Cargo.toml
└── src/
├── main.rs # binary crate 入口
├── lib.rs # library crate 入口(可选)
├── network.rs # mod network → 文件名=模块名
│ # 或 network/mod.rs(旧风格)
└── network/
├── mod.rs # 或 network.rs(新风格二选一)
└── server.rs
// src/main.rs
mod network; // 声明模块,编译器找 network.rs 或 network/mod.rs
use network::connect; // 使用模块中的项
fn main() {
connect();
}
// src/network.rs(或 src/network/mod.rs)
pub fn connect() {
println!("connected!");
}
pub mod server; // 声明子模块 → 编译器找 network/server.rs
// src/network/server.rs
pub fn start() {
println!("server started");
}
⚠️ 模块可见性 vs 文件结构——文件结构只是组织方式,真正的访问控制靠 pub。即使文件拆开了,没有 pub 标记的项依然外部不可见。mod 声明在哪个文件里,那个文件就是模块的"拥有者"。
// 一个目录 = 一个 package // 首字母大写 = public // 首字母小写 = private // 无嵌套 package
# 目录 = package # __init__.py 标记 # _ 前缀 = 约定私有 # from x import y # 没有 true private
学以致用——写一个完整的文件搜索工具 fsearch,支持按文件名模式搜索、按内容搜索、颜色高亮。
cargo new fsearch cd fsearch cargo add clap --features derive
use clap::Parser;
use std::fs;
use std::path::{Path, PathBuf};
/// 🦀 fsearch — 一个简单的文件搜索工具
#[derive(Parser, Debug)]
#[command(name = "fsearch")]
#[command(author, version, about)]
struct Args {
/// 搜索目录
#[arg(default_value = ".")]
directory: String,
/// 按文件名模式搜索(支持 * 和 ? 通配符)
#[arg(short, long)]
name: Option,
/// 按文件内容搜索
#[arg(short, long)]
content: Option,
/// 最大搜索深度
#[arg(short, long, default_value_t = 10)]
depth: usize,
/// 显示文件大小
#[arg(long)]
size: bool,
}
fn main() {
let args = Args::parse();
let root = Path::new(&args.directory);
if !root.exists() {
eprintln!("❌ 目录不存在: {}", args.directory);
std::process::exit(1);
}
println!("🔍 搜索目录: {}", root.display());
let mut count = 0u64;
let mut total_size = 0u64;
if let Err(e) = walk_dir(root, &args, 0, &mut count, &mut total_size) {
eprintln!("❌ 错误: {}", e);
std::process::exit(1);
}
println!("\n📊 统计: 找到 {} 个文件", count);
if args.size {
println!("📦 总大小: {} bytes", total_size);
}
}
fn walk_dir(
dir: &Path,
args: &Args,
current_depth: usize,
count: &mut u64,
total_size: &mut u64,
) -> Result<(), Box> {
if current_depth > args.depth {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let metadata = entry.metadata()?;
if metadata.is_dir() {
walk_dir(&path, args, current_depth + 1, count, total_size)?;
continue;
}
// 文件名过滤
if let Some(pattern) = &args.name {
let file_name = path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
if !match_pattern(file_name, pattern) {
continue;
}
}
// 内容过滤
if let Some(search_content) = &args.content {
match fs::read_to_string(&path) {
Ok(content) => {
if !content.contains(search_content) {
continue;
}
}
Err(_) => continue, // 跳过二进制文件等
}
}
// 输出匹配文件
*count += 1;
*total_size += metadata.len();
let size_info = if args.size {
format!(" ({} B)", metadata.len())
} else {
String::new()
};
println!(" 📄 {}{}", path.display(), size_info);
// 如果是内容搜索,显示匹配行
if let Some(search_content) = &args.content {
if let Ok(content) = fs::read_to_string(&path) {
for (i, line) in content.lines().enumerate() {
if line.contains(search_content) {
let trimmed = if line.len() > 80 {
&line[..80]
} else {
line
};
println!(" {}: {}", i + 1, trimmed);
}
}
}
}
}
Ok(())
}
/// 简单的通配符匹配(支持 * 和 ?)
fn match_pattern(text: &str, pattern: &str) -> bool {
let t: Vec = text.chars().collect();
let p: Vec = pattern.chars().collect();
match_inner(&t, &p, 0, 0)
}
fn match_inner(text: &[char], pattern: &[char], ti: usize, pi: usize) -> bool {
if pi == pattern.len() {
return ti == text.len();
}
if pattern[pi] == '*' {
// * 匹配 0 个或多个字符
for i in ti..=text.len() {
if match_inner(text, pattern, i, pi + 1) {
return true;
}
}
return false;
}
if ti >= text.len() {
return false;
}
if pattern[pi] == '?' || pattern[pi] == text[ti] {
return match_inner(text, pattern, ti + 1, pi + 1);
}
false
}
# 基本用法 — 列出当前目录所有文件 cargo run -- . # 按文件名搜索 cargo run -- /tmp --name "*.rs" # 按内容搜索 cargo run -- src --content "fn main" # 组合搜索 + 显示大小 cargo run -- . --name "*.toml" --size # 查看帮助 cargo run -- --help
$ cargo run -- src --name "*.rs" --content "fn main"
🔍 搜索目录: src
📄 src/main.rs
36: fn main() {
📊 统计: 找到 1 个文件
用 #[derive(Parser)] 把结构体字段变成 CLI 参数。#[arg] 注解控制短选项、默认值、帮助文本。比手动解析 argv 简洁太多。
fs::read_dir 和 entry.metadata() 都可能失败(权限、文件消失)。用 ? 传播错误,Box<dyn Error> 统一不同错误类型。
手动递归 walk_dir,带深度限制。生产代码用 walkdir 或 ignore crate(支持 .gitignore、符号链接处理等)。
💡 进阶方向——这个工具可以扩展:加 --type 按扩展名过滤、用 colored crate 高亮匹配内容、用 rayon 并行搜索、加 --json 输出格式。Rust 的 CLI 工具生态非常成熟,clap + anyhow + colored 是标准组合。
十节入门,覆盖了 Rust 最核心的概念。回顾一下你刚学到的:
| 概念 | Rust 的方式 | 关键记忆点 |
|---|---|---|
| 变量 | let 不可变,let mut 可变 | 默认不可变,shadowing ≠ mutation |
| 类型 | 静态强类型 + 推导 | String vs &str 是第一大坑 |
| 函数 | 表达式返回,无分号 | 分号杀死返回值 |
| 控制流 | if 是表达式,loop 可带值 | 优先 for 遍历 |
| 模式匹配 | match 穷举检查 | 编译器帮你找遗漏 |
| 错误处理 | Result + ? | 没有 exception,错误是值 |
| 模块 | mod + pub + use | 默认私有,显式公开 |
✅ 下一步——入门之后,你需要攻克的核心概念是所有权(Ownership)+ 借用(Borrowing)+ 生命周期(Lifetime)。这三者构成 Rust 内存安全的基石,也是从其他语言转 Rust 最大的心智转变。我们下一章见。