- 所有权系统在编译期保证内存安全,无需垃圾回收
- 借用规则:同一时间只能有一个可变引用或多个不可变引用
- 模式匹配是穷举的,编译器强制处理所有情况
- Trait 提供零成本抽象的多态,泛型在编译期单态化
- 使用 Result + ? 运算符处理可恢复错误,避免生产代码中使用 unwrap()
- 智能指针:Box 堆分配、Rc/Arc 共享所有权、RefCell/Mutex 内部可变性
- Send 和 Sync trait 在编译期保证线程安全
- Cargo 管理依赖、构建、测试和发布,是 Rust 生态的核心
为什么选择 Rust
Rust 是一门系统编程语言,专注于安全、并发和性能。它在编译期消除了内存安全问题(空指针、悬垂指针、数据竞争、缓冲区溢出),同时不依赖垃圾回收器,因此能达到与 C/C++ 相当的运行时性能。Rust 连续多年被 Stack Overflow 评为最受开发者喜爱的编程语言。
Rust 适用于以下场景:系统编程(操作系统、驱动程序、嵌入式)、高性能网络服务(Web 服务器、代理、数据库)、命令行工具、WebAssembly 应用、游戏引擎、区块链和密码学。如果你的项目需要可预测的低延迟、内存安全保证或高并发处理能力,Rust 是一个优秀的选择。
1. 所有权与借用
Rust 的所有权系统是其最独特的特性。每个值有且仅有一个所有者,当所有者离开作用域时值被自动释放。赋值操作默认移动所有权,使原变量失效。引用允许借用值而不获取所有权,分为不可变引用(&T)和可变引用(&mut T)。生命周期注解确保引用在其指向的数据有效期内使用。
实现了 Copy trait 的类型(如 i32、f64、bool、char)在赋值时自动复制而非移动。堆分配类型如 String 和 Vec 不实现 Copy,必须显式调用 .clone() 来创建深拷贝。理解移动语义和 Copy 的区别是避免常见编译错误的关键。
fn main() {
let s1 = String::from("hello"); // s1 owns the String
let s2 = s1; // ownership moves to s2, s1 is invalid
// println!("{}", s1); // compile error: value used after move
let s3 = s2.clone(); // deep copy, both s3 and s2 are valid
// Immutable borrowing: multiple readers allowed
let len = calculate_length(&s3);
println!("len of {} is {}", s3, len);
// Mutable borrowing: exclusive access
let mut s4 = String::from("hello");
change(&mut s4);
}
fn calculate_length(s: &String) -> usize { s.len() }
fn change(s: &mut String) { s.push_str(", world"); }字符串类型:String vs &str
Rust 有两种主要字符串类型:String 是堆分配的、可变的、拥有所有权的字符串;&str 是字符串切片引用,通常指向 String 的内容或字面量。函数参数一般接收 &str(更通用),返回值或需要拥有数据时使用 String。使用 .to_string()、String::from() 或 .into() 从 &str 创建 String。
2. 结构体与枚举
结构体(struct)用于将相关数据组合在一起,枚举(enum)用于定义一组可能的变体。impl 块为类型添加方法和关联函数。Rust 标准库中的 Option<T> 和 Result<T, E> 是最常用的枚举类型,分别表示可选值和可能失败的操作结果。
Rust 有三种结构体形式:命名字段结构体(最常用)、元组结构体(如 struct Meters(f64))和单元结构体(如 struct Marker)。枚举的每个变体可以包含不同类型和数量的数据,使其非常适合建模状态机和领域类型。
struct User {
name: String,
email: String,
active: bool,
}
impl User {
fn new(name: &str, email: &str) -> Self {
Self { name: name.into(), email: email.into(), active: true }
}
fn deactivate(&mut self) { self.active = false; }
}
enum Shape {
Circle(f64),
Rectangle { width: f64, height: f64 },
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle { width, height } => width * height,
}
}
}3. 模式匹配
模式匹配是 Rust 最强大的控制流工具之一。match 表达式要求穷举所有可能情况。if let 用于只关心一种模式的场景,while let 用于循环中的模式匹配。解构可以深入提取嵌套数据结构中的值,守卫条件(guard)可以为匹配臂添加额外逻辑。
穷举性检查是 Rust 模式匹配的一大优势。当你向枚举添加新变体时,编译器会报告所有未处理该变体的 match 表达式,防止遗漏。使用 _ 通配符匹配所有剩余情况,使用 @ 绑定匹配到的值到一个变量。
fn describe_number(n: i32) -> &'static str {
match n {
0 => "zero",
1..=9 => "single digit",
10 | 20 | 30 => "round tens",
x if x < 0 => "negative",
_ => "other positive",
}
}
// if let for single-pattern matching
let config: Option<&str> = Some("debug");
if let Some(level) = config {
println!("Log level: {}", level);
}
// Destructuring nested structs
let point = (3, -5);
let (x, y) = point;
match (x, y) {
(0, 0) => println!("origin"),
(x, 0) => println!("on x-axis at {}", x),
(0, y) => println!("on y-axis at {}", y),
(x, y) => println!("point at ({}, {})", x, y),
}4. Trait 与泛型
Trait 定义共享行为的方法集合,类似于其他语言中的接口但更强大。泛型通过 trait bound 约束类型参数,编译器在编译期将泛型单态化为具体类型,实现零成本抽象。impl Trait 语法可用于参数和返回类型的简写。dyn Trait 用于动态分派,通过虚表(vtable)在运行时确定调用的具体方法。
常用标准库 trait 包括:Display(格式化输出)、Debug(调试输出)、Clone(深拷贝)、PartialEq/Eq(相等比较)、PartialOrd/Ord(排序比较)、Hash(哈希计算)、From/Into(类型转换)、Iterator(迭代器)、Drop(析构函数)。使用 #[derive] 属性可以自动实现大部分常用 trait。
trait Summary {
fn summarize(&self) -> String; // required method
fn preview(&self) -> String { // default implementation
format!("Read more: {}...", &self.summarize()[..20])
}
}
struct Article { title: String, content: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, &self.content[..50])
}
}
// Static dispatch (monomorphized, zero-cost)
fn notify(item: &impl Summary) { println!("{}", item.summarize()); }
// Trait bound syntax (equivalent)
fn notify_bound<T: Summary + Clone>(item: &T) { println!("{}", item.summarize()); }
// Dynamic dispatch (vtable, runtime cost)
fn print_all(items: &[&dyn Summary]) {
for item in items { println!("{}", item.summarize()); }
}5. 错误处理
Rust 区分可恢复错误(Result<T, E>)和不可恢复错误(panic!)。? 运算符简化了错误传播,自动将 Err 值从函数返回。thiserror 库用于定义带有自动 From 实现的自定义错误类型,适合库开发。anyhow 库提供通用错误类型和上下文链,适合应用程序开发。
Rust 没有异常(exception)机制。所有可能失败的操作都通过返回 Result 或 Option 来表示。这种设计强制调用者处理错误情况,消除了未处理异常导致的运行时崩溃。unwrap() 和 expect() 在遇到 Err/None 时会 panic,只应在原型开发或确定不会失败的场景中使用。生产代码应始终显式处理错误或使用 ? 传播。
use std::fs;
use std::io;
// The ? operator propagates errors automatically
fn read_config(path: &str) -> Result<String, io::Error> {
let content = fs::read_to_string(path)?; // returns Err if fails
Ok(content.trim().to_string())
}
// Custom error with thiserror
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("Config key missing: {0}")]
MissingKey(String),
}
// anyhow for application code
fn load_port() -> anyhow::Result<u16> {
let cfg = fs::read_to_string("config.txt")
.context("failed to read config")?;
let port: u16 = cfg.parse().context("invalid port number")?;
Ok(port)
}6. 集合类型
Rust 标准库提供三种核心集合:Vec<T>(动态数组)、HashMap<K, V>(哈希映射)和 HashSet<T>(哈希集合)。迭代器是 Rust 集合操作的核心,提供惰性求值的链式操作,编译器会将其优化为与手写循环同等效率的代码。
Vec 是最常用的集合,支持 push、pop、索引访问和切片操作。HashMap 的 entry API 是处理插入或更新操作的惯用方式,避免了重复查找。HashSet 基于 HashMap 实现,提供集合运算(并集、交集、差集)。所有集合都实现了 IntoIterator trait,可以在 for 循环中直接使用。
use std::collections::{HashMap, HashSet};
fn main() {
// Vec: dynamic array
let mut nums = vec![1, 2, 3, 4, 5];
nums.push(6);
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
// HashMap: key-value store
let mut scores: HashMap<&str, i32> = HashMap::new();
scores.insert("Alice", 95);
scores.entry("Bob").or_insert(80);
// Count word frequencies
let text = "hello world hello rust";
let mut freq = HashMap::new();
for word in text.split_whitespace() {
*freq.entry(word).or_insert(0) += 1;
}
// HashSet: unique values
let a: HashSet<i32> = [1, 2, 3].into();
let b: HashSet<i32> = [2, 3, 4].into();
let union: HashSet<_> = a.union(&b).collect();
}7. 闭包与迭代器
闭包是可以捕获环境变量的匿名函数。Rust 根据闭包如何使用捕获的变量,自动推断其实现的 trait:Fn(不可变借用)、FnMut(可变借用)或 FnOnce(获取所有权)。迭代器适配器(map、filter、fold 等)是惰性的,只有在调用消费方法(如 collect、sum)时才执行。
迭代器是 Rust 中处理序列数据的惯用方式,通常比手写的 for 循环更简洁且同样高效。编译器会将迭代器链优化为等效的循环代码(零成本抽象)。move 关键字强制闭包获取所有捕获变量的所有权,这在将闭包传递给线程或返回闭包时尤为重要。
fn main() {
let multiplier = 3;
let multiply = |x: i32| x * multiplier; // Fn: borrows multiplier
let mut count = 0;
let mut increment = || { count += 1; }; // FnMut: mutably borrows
increment();
let name = String::from("Alice");
let greet = move || println!("Hi, {}", name); // FnOnce: takes ownership
// Iterator chain: lazy evaluation
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result: Vec<i32> = data.iter()
.filter(|&&x| x % 2 == 0) // keep even numbers
.map(|&x| x * x) // square them
.collect(); // [4, 16, 36, 64, 100]
let sum: i32 = data.iter().fold(0, |acc, &x| acc + x); // 55
let max = data.iter().max(); // Some(&10)
}8. 智能指针
智能指针是实现了 Deref 和 Drop trait 的结构体,不仅持有指向数据的指针,还拥有额外的元数据和能力。Box<T> 在堆上分配数据;Rc<T> 允许单线程中的多所有权;Arc<T> 是线程安全版本的 Rc;RefCell<T> 提供运行时借用检查的内部可变性;Mutex<T> 提供多线程互斥访问。
选择智能指针的经验法则:需要堆分配或递归类型用 Box;需要单线程共享所有权用 Rc;需要跨线程共享所有权用 Arc;需要单线程内部可变性用 RefCell;需要跨线程内部可变性用 Mutex 或 RwLock。常见组合模式是 Rc<RefCell<T>>(单线程共享可变状态)和 Arc<Mutex<T>>(多线程共享可变状态)。
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
fn main() {
// Box: heap allocation, single owner
let boxed: Box<i32> = Box::new(42);
// Rc: multiple owners (single-threaded)
let shared = Rc::new(String::from("shared data"));
let clone1 = Rc::clone(&shared); // reference count: 2
println!("refs: {}", Rc::strong_count(&shared)); // 2
// RefCell: interior mutability (runtime borrow check)
let cell = RefCell::new(vec![1, 2, 3]);
cell.borrow_mut().push(4); // mutable borrow at runtime
// Arc + Mutex: shared mutable state across threads
let counter = Arc::new(Mutex::new(0));
let c = Arc::clone(&counter);
std::thread::spawn(move || {
*c.lock().unwrap() += 1;
}).join().unwrap();
println!("count: {}", *counter.lock().unwrap()); // 1
}9. 并发编程
Rust 的所有权系统使并发编程安全可靠。标准库提供 OS 线程和通道(channel)进行消息传递。对于共享状态,使用 Arc<Mutex<T>>。对于异步 I/O 密集型工作负载,使用 async/await 语法配合 tokio 运行时。Send trait 标记类型可以安全地在线程间转移,Sync trait 标记类型可以安全地在线程间共享引用。
Rust 的"无畏并发"意味着数据竞争在编译期被消除。如果代码编译通过,就不会有数据竞争。选择并发模型的经验法则:CPU 密集型计算用 rayon 进行数据并行;I/O 密集型任务用 tokio 的 async/await;简单的线程间通信用标准库的 channel;需要共享可变状态时用 Arc<Mutex<T>>。
use std::sync::mpsc;
use std::thread;
fn main() {
// Message passing with channels
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || { tx.send("from thread 1").unwrap(); });
thread::spawn(move || { tx2.send("from thread 2").unwrap(); });
for msg in rx.iter().take(2) {
println!("received: {}", msg);
}
}
// Async/await with tokio
#[tokio::main]
async fn main() {
let result = tokio::spawn(async {
reqwest::get("https://api.example.com/data")
.await.unwrap().text().await.unwrap()
}).await.unwrap();
println!("{}", result);
}选择并发模型
- 标准库线程 — 适用于 CPU 密集型任务,每个线程有独立的栈空间(默认 8MB)
- mpsc::channel — 多生产者单消费者通道,适合简单的线程间通信
- Arc + Mutex — 适合需要多线程共享和修改的状态,注意避免死锁
- tokio async/await — 适合 I/O 密集型(HTTP、数据库、文件),单线程可处理数千并发连接
- rayon — 数据并行,将 .iter() 替换为 .par_iter() 即可自动并行化
10. 模块与包
Rust 的模块系统由 crate(包)和 module(模块)组成。crate 是编译的最小单元,分为二进制 crate 和库 crate。mod 关键字声明模块,pub 控制可见性,use 引入路径。Cargo.toml 是项目的清单文件,管理依赖、版本和构建配置。工作区(workspace)可以管理多个相关的 crate。
模块可以内联定义,也可以放在单独的文件中(src/module_name.rs 或 src/module_name/mod.rs)。pub(crate) 限制可见性为当前 crate 内部,pub(super) 限制为父模块。Cargo.toml 中的 [dependencies] 区域引入外部 crate,crates.io 是 Rust 官方的包注册中心。
// src/lib.rs
pub mod auth {
pub struct Token(String);
impl Token {
pub fn new(secret: &str) -> Self {
Token(format!("tok_{}", secret))
}
}
mod internal { // private module
pub fn validate(token: &str) -> bool {
token.starts_with("tok_")
}
}
pub fn is_valid(t: &Token) -> bool {
internal::validate(&t.0)
}
}
// src/main.rs
use mylib::auth::Token;
fn main() {
let t = Token::new("abc123");
}Cargo.toml 示例
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
tracing = "0.1"
[dev-dependencies]
criterion = "0.5" # benchmarking
[[bin]]
name = "my-project"
path = "src/main.rs"11. 宏
Rust 的宏系统允许在编译期生成代码。声明式宏(macro_rules!)使用模式匹配对 token 树进行转换,类似于高级模板。过程宏(procedural macros)可以操作 Rust 的抽象语法树(AST),包括派生宏(#[derive])、属性宏和函数式宏。标准库大量使用宏,如 vec!、println!、derive 等。
宏与函数的关键区别:宏在编译期展开,可以接受可变数量的参数,可以生成新的类型和 trait 实现。声明式宏适合简单的代码生成(如创建集合字面量),过程宏适合复杂的代码生成(如序列化框架)。使用 cargo expand 可以查看宏展开后的代码。
// Declarative macro: create a HashMap literal
macro_rules! hashmap {
($($key:expr => $val:expr),* $(,)?) => {{
let mut map = std::collections::HashMap::new();
$(map.insert($key, $val);)*
map
}};
}
let scores = hashmap! {
"Alice" => 95,
"Bob" => 87,
"Charlie" => 92,
};
// Derive macro usage (most common procedural macro)
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
struct Config {
host: String,
port: u16,
debug: bool,
}12. 测试
Rust 内置了完善的测试框架。单元测试放在同一文件的 #[cfg(test)] 模块中,集成测试放在项目根目录的 tests/ 目录下,文档测试是 /// 注释中的代码示例会被自动编译运行。使用 assert!、assert_eq! 和 assert_ne! 宏进行断言。#[should_panic] 测试预期的 panic,cargo test 运行所有测试。
#[cfg(test)] 模块只在运行 cargo test 时编译,不会出现在发布构建中。集成测试中每个文件都是独立的 crate,只能访问公有 API。文档测试特别有用,因为它们既是文档又是测试,保证代码示例始终与实际行为一致。使用 cargo test -- --nocapture 可以在测试中看到 println! 输出。
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err("division by zero".into()) }
else { Ok(a / b) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() { assert_eq!(add(2, 3), 5); }
#[test]
fn test_divide_ok() {
assert!((divide(10.0, 3.0).unwrap() - 3.333).abs() < 0.01);
}
#[test]
fn test_divide_by_zero() {
assert!(divide(1.0, 0.0).is_err());
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_panic() { let v = vec![1]; let _ = v[5]; }
}13. 常见设计模式
Rust 的类型系统和所有权模型催生了独特的设计模式。Builder 模式用于构造复杂对象;Newtype 模式通过包装类型添加语义和类型安全;Typestate 模式利用类型系统在编译期强制状态转换的正确性;RAII(资源获取即初始化)确保资源在离开作用域时自动释放。
Builder 模式在 Rust 中特别流行,因为 Rust 没有函数重载和默认参数。Newtype 模式的零成本抽象意味着包装类型在运行时与底层类型完全相同。RAII 是 Rust 资源管理的基石——文件句柄、网络连接和锁都在 Drop trait 中自动释放,确保不会泄漏。
// Builder pattern
struct ServerBuilder {
host: String,
port: u16,
max_connections: usize,
}
impl ServerBuilder {
fn new() -> Self {
Self { host: "127.0.0.1".into(), port: 8080, max_connections: 100 }
}
fn host(mut self, h: &str) -> Self { self.host = h.into(); self }
fn port(mut self, p: u16) -> Self { self.port = p; self }
fn build(self) -> String {
format!("{}:{} (max: {})", self.host, self.port, self.max_connections)
}
}
// Newtype pattern: type-safe wrapper
struct Meters(f64);
struct Kilometers(f64);
impl From<Kilometers> for Meters {
fn from(km: Kilometers) -> Self { Meters(km.0 * 1000.0) }
}常见编译错误及解决方案
Rust 编译器的错误信息非常详细且有帮助。以下是初学者最常遇到的编译错误及其解决方案。理解这些错误是掌握 Rust 的关键,因为编译器实际上是你最好的学习伙伴。
// E0382: use of moved value
let s = String::from("hello");
let s2 = s; // s is moved
// println!("{}", s); // error! Fix: use s2, or call s.clone()
// E0502: cannot borrow as mutable, already borrowed as immutable
let mut v = vec![1, 2, 3];
let first = &v[0]; // immutable borrow
// v.push(4); // error! Fix: use first before push
println!("{}", first); // use immutable borrow here
v.push(4); // now mutable borrow is ok
// E0106: missing lifetime specifier
// fn longest(a: &str, b: &str) -> &str // error!
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}生命周期错误(E0106)是初学者最困惑的问题。核心规则:如果函数返回一个引用,编译器需要知道返回值的生命周期与哪个输入参数相关联。大多数情况下,编译器可以自动推断(生命周期省略规则),但当有多个引用参数时需要手动标注。如果觉得生命周期太复杂,可以先使用拥有所有权的类型(如 String 代替 &str)来简化。
编译器诊断技巧
- 仔细阅读错误信息,Rust 编译器通常会建议具体的修复方案
- 使用 rustc --explain E0382 查看错误的详细解释和示例
- 使用 cargo clippy 获取更多代码改进建议
- 使用 rust-analyzer IDE 插件获取实时错误提示和自动修复
- 遇到生命周期问题时,考虑是否可以用 clone() 或 owned 类型(String 代替 &str)简化
总结速查表
所有权速查
- 每个值有且仅有一个所有者
- 赋值默认移动所有权(实现 Copy 的类型除外)
- 同一时间:一个 &mut T 或任意数量 &T,但不能同时存在
- 引用的生命周期不能超过被引用数据的生命周期
错误处理速查
- 可恢复错误用 Result<T, E>,不可恢复用 panic!
- 用 ? 传播错误,避免生产代码中使用 unwrap()
- 库用 thiserror,应用用 anyhow
并发速查
- 消息传递:mpsc::channel
- 共享状态:Arc<Mutex<T>> 或 Arc<RwLock<T>>
- 异步 I/O:async/await + tokio
- Send = 可跨线程转移,Sync = 可跨线程共享引用
Cargo 常用命令
cargo new myproject— 创建新项目cargo build --release— 优化构建cargo test— 运行所有测试cargo clippy— 代码静态分析cargo doc --open— 生成并打开文档cargo fmt— 自动格式化代码cargo add serde— 添加依赖
常用 trait 速查
- Display — 用户友好的格式化输出(用于 println! 的 {} 格式)
- Debug — 调试输出(用于 println! 的 {:?} 格式)
- Clone / Copy — Clone 用于显式深拷贝,Copy 用于隐式按位复制
- From / Into — 类型转换,实现 From 自动获得 Into
- Iterator — 只需实现 next() 方法即可获得 70+ 个适配器方法
- Deref / DerefMut — 智能指针自动解引用
- Send / Sync — 编译期线程安全标记
推荐 Crate
- serde + serde_json — 序列化与反序列化框架
- tokio — 异步运行时(async/await)
- reqwest — HTTP 客户端
- clap — 命令行参数解析
- thiserror / anyhow — 错误处理
- tracing — 结构化日志与分布式追踪
- sqlx — 编译期检查的异步数据库驱动
- axum — 基于 tokio 的 Web 框架
- rayon — 数据并行计算
Rust vs 其他语言
- Rust vs C/C++ — 同等性能,但 Rust 在编译期消除内存安全问题,无需手动管理内存
- Rust vs Go — Rust 性能更高且无 GC 停顿,Go 学习曲线更平缓且编译更快
- Rust vs Java/C# — Rust 无运行时开销和 GC,但学习曲线更陡;Java/C# 生态更成熟
- Rust vs Python — Rust 性能快 10-100 倍,适合性能关键路径;Python 适合快速原型和脚本
- Rust vs TypeScript — Rust 编译为原生代码或 WASM,TypeScript 运行在 JS 引擎上;两者都有强类型系统
学习路径建议
建议的 Rust 学习顺序:(1) 掌握所有权、借用和生命周期——这是 Rust 的核心概念;(2) 学习结构体、枚举和模式匹配——建立数据建模能力;(3) 理解 trait 和泛型——掌握抽象能力;(4) 学习错误处理和集合——写出实用代码;(5) 深入智能指针和并发——构建复杂系统;(6) 探索宏和高级模式——提升代码复用能力。每个阶段都配合实际项目练习,推荐从命令行工具开始,逐步过渡到 Web 服务。
推荐学习资源
- The Rust Book — 官方教程(doc.rust-lang.org/book),最全面的入门资源
- Rust by Example — 通过代码示例学习(doc.rust-lang.org/rust-by-example)
- Rustlings — 交互式练习,通过修复编译错误学习
- Exercism Rust Track — 有导师指导的编程练习
- std docs — 标准库文档(doc.rust-lang.org/std),每个 API 都有示例