基础
变量与可变性
Rust 变量默认是不可变的——这是防止意外修改的核心安全特性。仅在你确实需要修改值时才使用 'mut'。'const' 需要显式类型并在编译时内联,而 'static' 具有固定的内存地址和 'static 生命周期。
let x = 5; // immutable by default
let mut y = 10; // mutable with 'mut'
y += 1; // OK, y is mutable
const MAX: u32 = 100; // compile-time constant
static GREETING: &str = "Hi"; // global static
let z: i32 = -5; // explicit type annotation遮蔽
遮蔽允许你重用变量名同时改变其类型或值。与 'mut' 不同,遮蔽会创建一个新的绑定——适用于在不发明新名称的情况下转换数据(例如,将字符串解析为整数)。旧值在新绑定后会被丢弃。
let x = 5;
let x = x * 2; // shadow previous x, now 10
let x = "text"; // can even change type!
println!("{}", x); // "text"
// shadowing vs mut: shadowing creates a NEW variable
// with the same name, allowing type changes注释与打印
使用 /// 编写条目文档(通过 'cargo doc' 显示),使用 //! 编写模块/crate 文档。println! 写入 stdout,eprintln! 写入 stderr。{} 占位符支持格式化规范:> 表示右对齐,< 表示左对齐,^ 表示居中,.N 表示精度,#b/#o/#x 表示二进制/八进制/十六进制。
// Line comment
/* Block comment */
/// Doc comment (renders in rustdoc)
//! Module-level doc comment
let name = "Alice";
println!("Hello, {}!", name); // format string
println!("{0} {1} {0}", "a", "b"); // positional args
println!("{:>5}", 42); // right-align, width 5
println!("{:.2}", 3.14159); // 2 decimal places: 3.14
println!("{:#b}", 0b1010); // binary: 0b1010
eprintln!("Error to stderr"); // error output数据类型概述
Rust 没有隐式类型转换——使用 'as' 进行转换。i32 是默认整数类型,f64 是默认浮点类型。usize 用于索引和大小。char 是完整的 Unicode 标量值(不是字节),所以 '🦀' 是单个 char。
// Scalar types
let a: i32 = 42; // signed 32-bit integer
let b: u64 = 100; // unsigned 64-bit
let c: f64 = 3.14; // 64-bit float (default)
let d: bool = true;
let e: char = '🦀'; // 4-byte Unicode scalar
// isize/usize: pointer-sized (32 or 64 bit depending on arch)
let len: usize = vec![1,2,3].len();
// Compound types
let pair: (i32, &str) = (1, "hello");
let arr: [i32; 3] = [0; 3]; // [0, 0, 0]类型转换与别名
'as' 转换是不检查的,可能会丢失数据(例如,300u16 as u8 会回绕到 44)。对于安全转换,使用 From/Into trait,它们为无损转换实现。类型别名可以提高可读性而不创建新类型——使用 'struct NewType(i32)' 实现独特的新类型模式。
// 'as' keyword for primitive casts (may truncate)
let x: i32 = 42;
let y: f64 = x as f64; // 42.0
let z: u8 = 300u16 as u8; // truncated: 44
// From/Into traits for safe conversion
let s = String::from("hi");
let owned: String = "hi".into();
// Type aliases
type Kilometers = i32;
let distance: Kilometers = 5;字符串
String 与 &str
String(拥有、堆)和 &str(借用切片)之间的区别在 Rust 中是基础性的。当你需要拥有/修改/增长文本时使用 String;当你只需要读取时使用 &str。&str 可以指向 String 的缓冲区或二进制文件中的字符串字面量。在函数参数中优先使用 &str 以获得灵活性。
// &str: immutable string slice (borrowed)
let s1: &str = "literal"; // stored in binary
let s2: &str = &s3[..2]; // slice of a String
// String: owned, growable, heap-allocated
let mut s3: String = String::from("hello");
s3.push_str(", world"); // append
s3.push('!'); // append char
s3.insert(0, '>'); // insert at index
println!("{}", s3); // >hello, world!
// Convert: &str -> String
let owned = "literal".to_string();
let owned2 = String::from("literal");字符串方法
字符串方法返回新的拥有 String 而不是原地修改(可变 String 上的 push_*/insert 方法除外)。split() 返回迭代器,所以使用 .collect() 来物化。注意,字符串上不允许索引 s[0],因为 UTF-8 边界与字节索引不对齐——改用 s.chars().nth(0)。
let s = String::from("Hello, World");
// inspection
println!("len: {}", s.len()); // 12
println!("is_empty: {}", s.is_empty()); // false
println!("contains 'World': {}", s.contains("World")); // true
println!("starts_with 'Hello': {}", s.starts_with("Hello")); // true
// transformation
let upper = s.to_uppercase(); // HELLO, WORLD
let replaced = s.replace("o", "0"); // Hell0, W0rld
let trimmed = " hi ".trim().to_string(); // hi
// splitting
for word in s.split(", ") {
println!("{}", word); // Hello / World
}
let parts: Vec<&str> = s.split_whitespace().collect();格式化与拼接
+ 运算符获取左侧 String 的所有权并借用右侧(&str)。这就是为什么 s1 + &s2 后 s1 变得无效。对于拼接多个字符串,优先使用 format!,它更易读且不会移动任何操作数。concat! 仅适用于字面量并产生 &'static str。
// format! macro creates a new String
let name = "Alice";
let msg = format!("Hi {}, you have {} messages", name, 5);
// concatenation
let s1 = String::from("Hello");
let s2 = String::from(" World");
let s3 = s1 + &s2; // s1 moved, s2 borrowed
// s1 is now invalid!
let s4 = format!("{}{}", s2, s3); // neither moved
// concat! macro for literals
let s5 = concat!("foo", "bar"); // "foobar" at compile time遍历字符与字节
Rust 字符串是 UTF-8 编码的,所以字节索引 != 字符索引。chars() 遍历 Unicode 标量值(O(n) 解码),bytes() 遍历原始字节。使用 [n..m] 切片时,如果 n 或 m 落在多字节字符中间会 panic。对于字节级访问,通过 as_bytes() 转换为 Vec<u8>。
let s = "héllo"; // é is 2 bytes in UTF-8
// iterate over chars (Unicode scalar values)
for c in s.chars() {
print!("{} ", c); // h é l l o
}
// iterate over bytes
for b in s.bytes() {
print!("{} ", b); // 104 195 169 108 108 111
}
// get char at index (O(n) — must walk UTF-8)
let third = s.chars().nth(2); // Some('l')
// string slices must be on char boundaries
let slice = &s[0..1]; // "h" — OK
// let bad = &s[0..2]; // PANIC if mid-é!解析与转换
parse() 返回 Result,因为字符串可能不是有效数字——始终处理错误。turbofish 语法 parse::<T>() 允许你内联指定类型。将 String 转换为 &str 是免费的(只是借用),但 &str 转换为 String 需要分配内存。collect() 可以从字符迭代器构建 String。
// String/str -> number
let n: i32 = "42".parse().unwrap();
let n2 = "42".parse::<i32>().unwrap(); // turbofish syntax
let f: f64 = "3.14".parse().unwrap();
// number -> String
let s = 42.to_string();
let s2 = format!("{}", 42);
// String -> &str (free, just dereference)
let owned = String::from("hi");
let borrowed: &str = &owned;
// collect chars into String
let upper: String = "hello".chars().map(|c| c.to_uppercase().next().unwrap()).collect();数据结构
数组与切片
数组 [T; N] 在编译时已知固定大小并存在于栈上。切片 &[T] 是胖指针(指针 + 长度),借用一段连续序列——它们允许函数接受任何数组或向量而不关心大小。在函数签名中使用切片以获得通用性。
// Fixed-size array (stack allocated)
let arr: [i32; 3] = [1, 2, 3];
let zeros = [0; 5]; // [0, 0, 0, 0, 0]
println!("first: {}", arr[0]);
println!("len: {}", arr.len());
// Slice: a view into an array/vector
let slice: &[i32] = &arr[1..3]; // [2, 3]
let full: &[i32] = &arr; // whole array
let first = &arr[..1]; // [1]
// iterating
for n in &arr {
println!("{}", n);
}向量(Vec<T>)
Vec<T> 是 Rust 的可增长数组,由堆内存支持,容量加倍。push/pop 是 O(1) 摊销;insert/remove 是 O(n),因为元素需要移动。当你想要安全访问时使用 .get(i) 而不是 v[i](返回 Option)。into_iter() 消费向量,产生拥有的值。
// growable heap array
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3]; // macro shorthand
v.push(4); // append
v.pop(); // remove last -> Option
v.insert(0, 0); // insert at index (O(n))
v.remove(0); // remove at index (O(n))
v.extend([5, 6]); // append multiple
// access
println!("{}", v[0]); // panics if out of bounds
println!("{:?}", v.get(0)); // Some(&4) — safe
// iterate by value, ref, or mut ref
for n in &v { print!("{}", n); }
for n in &mut v { *n *= 2; } // double each
let owned: Vec<i32> = v.into_iter().collect();HashMap 与 BTreeMap
HashMap 使用哈希实现 O(1) 平均访问但没有排序。BTreeMap 使用 B 树实现 O(log n) 访问但保持键排序。当你需要快速查找时使用 HashMap;当你需要有序迭代或范围查询时使用 BTreeMap。entry().or_insert() 是 'upsert' 的惯用方式——它返回值的可变引用,如果不存在则插入默认值。
use std::collections::HashMap;
use std::collections::BTreeMap;
// HashMap: O(1) average lookup, unordered
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 10);
scores.entry("Bob".into()).or_insert(0); // insert if absent
let alice = scores.get("Alice"); // Some(&10)
// iterate (unordered)
for (name, score) in &scores {
println!("{}: {}", name, score);
}
// BTreeMap: O(log n) lookup, sorted by key
let mut bt: BTreeMap<String, i32> = BTreeMap::new();
bt.insert("zebra".into(), 1);
bt.insert("apple".into(), 2);
// iterates in sorted order: apple, zebraHashSet 与 BTreeSet
HashSet 存储唯一值,O(1) 成员检查。集合操作(并集、交集、差集、对称差集)返回迭代器。使用集合进行去重、成员测试和数学集合操作。BTreeSet 是由 BTreeMap 支持的排序等价物。
use std::collections::HashSet;
let mut a: HashSet<i32> = [1, 2, 3].into_iter().collect();
let b: HashSet<i32> = [3, 4, 5].into_iter().collect();
a.insert(4);
a.remove(&1);
println!("contains 2: {}", a.contains(&2));
// set operations
let union: HashSet<_> = a.union(&b).copied().collect();
let inter: HashSet<_> = a.intersection(&b).copied().collect();
let diff: HashSet<_> = a.difference(&b).copied().collect();
let sym: HashSet<_> = a.symmetric_difference(&b).copied().collect();元组与解构
元组将固定数量的可能不同类型的值组合在一起。使用 .0、.1 等访问字段。使用 let 模式解构是惯用做法。单元类型 () 只有一个值 (),在其他语言中类似 void。元组通常用于从函数返回多个值。
// tuples can hold different types
let tup: (i32, f64, &str) = (42, 3.14, "hi");
// access by index
println!("{} {} {}", tup.0, tup.1, tup.2);
// destructuring
let (n, pi, s) = tup;
println!("{} {} {}", n, pi, s);
// nested
let ((a, b), c) = ((1, 2), 3);
// unit tuple (empty)
let unit: () = ();
// function returning multiple values
fn divmod(a: i32, b: i32) -> (i32, i32) {
(a / b, a % b)
}
let (q, r) = divmod(17, 5); // (3, 2)控制流
If / Else If / Else
与许多语言不同,Rust 中的 'if' 是一个返回值的表达式。这消除了对三元运算符(condition ? a : b)的需求——只需使用 if/else。两个分支必须返回相同的类型。条件不需要括号,但主体必须是块 { }。
let score = 85;
// if is an expression — returns a value
let grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else if score >= 70 {
"C"
} else {
"F"
};
println!("Grade: {}", grade);
// arms must return same type
// let bad = if true { 1 } else { "two" }; // ERROR!循环:loop、while、for
loop 创建无限循环——break 退出它并可以返回一个值(break value)。while 在每次迭代前检查条件。for 是最常见的循环,遍历范围、数组、向量、迭代器。使用 'continue' 跳到下一次迭代。范围:a..b 是排他的,a..=b 是包含的。
// loop: infinite, use break to exit
let mut count = 0;
let result = loop {
count += 1;
if count == 10 {
break count * 2; // break with value
}
};
println!("{}", result); // 20
// while: condition-checked loop
let mut n = 5;
while n > 0 {
println!("{}", n);
n -= 1;
}
// for: iterate over anything with IntoIterator
for i in 0..5 { print!("{}", i); } // 01234
for i in (1..=3).rev() { print!("{}", i); } // 321
for ch in "hello".chars() { print!("{}", ch); }Match(模式匹配)
match 是 Rust 强大的模式匹配构造。它必须是穷尽的(覆盖所有可能性)——使用 _ 作为通配符。模式支持字面量、范围(..=)、或模式(|)、绑定和守卫(if)。match 是一个表达式并返回一个值。它是处理 Option 和 Result 等枚举的惯用方式。
let coin = 25;
match coin {
25 => println!("quarter"),
10 => println!("dime"),
5 => println!("nickel"),
1 => println!("penny"),
_ => println!("unknown"), // catch-all (required)
}
// matching with bindings
let x = 3;
match x {
1 | 2 => println!("one or two"), // or-pattern
3..=9 => println!("single digit"), // range
n if n % 2 == 0 => println!("even: {}", n), // guard
_ => println!("other"),
}
// match is exhaustive — all cases must be coveredIf Let 与 While Let
if let 是当你只关心一个变体时 match 的语法糖。它比 match 更简洁但不够穷尽——在其他情况不重要时使用。while let 在模式匹配时循环,通常与迭代器一起使用(next() 返回 Option)。else 分支是可选的。
// if let: shorthand for matching one pattern
let maybe: Option<i32> = Some(5);
// verbose: match
match maybe {
Some(x) => println!("got {}", x),
None => println!("nothing"),
}
// concise: if let
if let Some(x) = maybe {
println!("got {}", x);
} else {
println!("nothing");
}
// while let: loop while pattern matches
let mut iter = vec![1, 2, 3].into_iter();
while let Some(n) = iter.next() {
println!("{}", n);
}Break、Continue 与标签
标签(单引号 + 名称)允许从嵌套循环中 break 或 continue 外层循环。当你需要一次退出多个循环层级时,这很重要。没有标签,break/continue 只影响最内层循环。标签是标志变量的干净替代方案。
// continue: skip to next iteration
for i in 0..10 {
if i % 2 == 0 { continue; }
println!("{}", i); // prints odd numbers
}
// break: exit loop
for i in 0..10 {
if i == 5 { break; }
println!("{}", i); // prints 0..4
}
// labeled loops (for nested break/continue)
'outer: for i in 0..3 {
for j in 0..3 {
if i == 1 && j == 1 {
break 'outer; // breaks the outer loop
}
println!("{} {}", i, j);
}
}函数与闭包
定义函数
函数使用 'fn' 关键字。没有分号的最后一个表达式是返回值(表达式)。添加分号会将其变成返回 () 的语句。仅在提前退出时使用显式 'return'。! 返回类型标记从不返回的发散函数(无限循环、panic、进程退出)。
// basic function with return type
fn add(a: i32, b: i32) -> i32 {
a + b // no semicolon = expression = return value
}
// statements (with semicolon) return ()
fn greet(name: &str) {
println!("Hi, {}", name);
// implicit return ()
}
// explicit return
fn abs(x: i32) -> i32 {
if x < 0 {
return -x; // early return needs 'return'
}
x // tail expression
}
// diverging function (never returns)
fn forever() -> ! {
loop {}
}参数与实参
Rust 没有函数重载或可选参数(改用泛型或构建器)。仔细选择参数类型:&T 用于读访问,&mut T 用于写访问,T 用于所有权转移。切片(&[T])是接受可变长度序列的惯用方式。不支持默认参数——使用构建器模式或 Option<T>。
// immutable borrow
fn len(s: &String) -> usize { s.len() }
// mutable borrow
fn push(v: &mut Vec<i32>) { v.push(42); }
// take ownership
fn consume(s: String) { println!("{}", s); }
// multiple return via tuple
fn swap(a: i32, b: i32) -> (i32, i32) { (b, a) }
// variadic-ish via slices
fn sum(nums: &[i32]) -> i32 {
nums.iter().sum()
}
println!("{}", sum(&[1, 2, 3, 4])); // 10闭包
闭包是可 以捕获其环境的匿名函数。它们由用法推断。闭包默认通过引用捕获;'move' 强制所有权转移(对线程很重要)。闭包实现 Fn(借用)、FnMut(可变借用)或 FnOnce(消费)trait,使它们可以作为函数参数传递。
// closure syntax: |params| body
let add = |a, b| a + b;
println!("{}", add(1, 2)); // 3
// type annotations (rarely needed)
let square = |x: i32| -> i32 { x * x };
// capturing environment
let multiplier = 3;
let multiply = |x| x * multiplier; // borrows multiplier
println!("{}", multiply(5)); // 15
// move closure: takes ownership of captured vars
let name = String::from("Alice");
let greet = move || println!("Hi {}", name);
// name is now moved into greet
greet();高阶函数与迭代器
Rust 迭代器是惰性的——操作在调用 .collect() 或其他消费方法之前不会执行。这允许零成本抽象:编译器可以将链式迭代器方法优化为高效循环。常用方法:map(转换)、filter(选择)、fold(累积)、take(限制)、skip、enumerate、zip、flat_map。
let nums = vec![1, 2, 3, 4, 5];
// map: transform each element
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
// filter: keep elements matching predicate
let evens: Vec<&i32> = nums.iter().filter(|&&x| x % 2 == 0).collect();
// fold/reduce: accumulate
let sum: i32 = nums.iter().sum(); // 15
let product: i32 = nums.iter().product(); // 120
let combined = nums.iter().fold(0, |acc, x| acc + x);
// chain multiple operations (lazy!)
let result: Vec<i32> = nums.iter()
.filter(|&&x| x > 1)
.map(|&x| x * x)
.collect(); // [4, 9, 16, 25]函数指针与 Trait
fn(小写)是函数指针类型——零成本,但不能捕获环境。对于捕获的闭包,使用泛型 <F: Fn(...)> 约束。Fn 借用,FnMut 可变借用,FnOnce 消费。函数指针适用于在结构体中存储函数或传递给 C。带 Fn 约束的泛型更灵活,在单态化时仍然是零成本的。
// function pointer type
type MathFn = fn(i32, i32) -> i32;
fn add(a: i32, b: i32) -> i32 { a + b }
fn mul(a: i32, b: i32) -> i32 { a * b }
fn apply(f: MathFn, a: i32, b: i32) -> i32 {
f(a, b)
}
println!("{}", apply(add, 3, 4)); // 7
println!("{}", apply(mul, 3, 4)); // 12
// generic over Fn trait (accepts closures too)
fn apply_fn<F: Fn(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
f(a, b)
}
let closure = |a, b| a - b;
println!("{}", apply_fn(closure, 10, 3)); // 7所有权与借用
所有权规则
所有权是 Rust 的核心内存管理系统——不需要垃圾回收器。当你赋值堆值(String、Vec)时,所有权移动,旧变量变得无效。栈类型(i32、f64、bool、char、Copy 类型的元组)实现 Copy 并被复制。这在编译时消除了 use-after-free 和 double-free 错误。
// Rule 1: Each value has ONE owner
let s1 = String::from("hello");
let s2 = s1; // s1's ownership MOVED to s2
// println!("{}", s1); // ERROR: s1 is invalid after move
// Rule 2: When owner goes out of scope, value is dropped
{
let s = String::from("temp");
// s is valid here
} // s is automatically dropped (memory freed)
// Rule 3: Copy types (i32, bool, char, etc.) are copied, not moved
let a = 5;
let b = a; // a is copied, both valid
println!("{} {}", a, b); // OK借用与引用
借用允许你使用值而不获取所有权。&T 创建不可变引用——你可以同时拥有多个。&mut T 创建可变引用——但只能有一个可变引用或任意数量的不可变引用,不能同时存在。这在编译时防止数据竞争。引用必须始终指向有效数据(没有悬空指针)。
// &T: immutable borrow (read-only, multiple allowed)
fn calc_len(s: &String) -> usize {
s.len()
// s goes out of scope but is NOT dropped (we don't own it)
}
let s = String::from("hello");
let len = calc_len(&s); // borrow s, don't move it
println!("'{}' has length {}", s, len); // s still valid
// &mut T: mutable borrow (exclusive, only ONE at a time)
fn push_world(s: &mut String) {
s.push_str(", world");
}
let mut s2 = String::from("hello");
push_world(&mut s2);
println!("{}", s2); // hello, world切片引用
切片是对集合连续部分的引用。它们是包含指针和长度的'胖指针'。字符串切片(&str)让函数接受 String 和字符串字面量。数组切片(&[T])适用于任何连续序列。切片借用底层数据,防止在切片存在时被修改或丢弃。
// string slice: &str
let s = String::from("hello world");
let hello: &str = &s[0..5]; // "hello"
let world: &str = &s[6..]; // "world"
let full: &str = &s[..]; // "hello world"
// array slice: &[T]
let arr = [1, 2, 3, 4, 5];
let mid: &[i32] = &arr[1..4]; // [2, 3, 4]
// function accepting slices (idiomatic)
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..]
}生命周期
生命周期告诉编译器引用有效多久。'a 注解不会改变生命周期——它描述关系。'static 是一个特殊的生命周期,持续整个程序(字符串字面量有它)。大多数代码使用生命周期省略(编译器推断)。当你需要显式生命周期时:函数返回引用,或结构体持有引用。
// explicit lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 'a means: the returned reference lives as long as
// the SHORTEST of x and y's lifetimes
let s1 = String::from("long string");
let s2 = String::from("hi");
let result = longest(s1.as_str(), s2.as_str());
println!("Longest: {}", result);
// struct holding references needs lifetime
struct Excerpt<'a> {
part: &'a str,
}
let novel = String::from("call me Ishmael. years ago...");
let first_sentence = novel.split('.').next().unwrap();
let ex = Excerpt { part: first_sentence };智能指针
Box<T> 将数据移到堆上(单一所有者)。Rc<T> 通过引用计数实现共享所有权(仅单线程)。Arc<T> 是使用原子操作的线程安全版本。RefCell<T> 将借用检查移到运行时,允许通过共享引用进行修改(内部可变性)。递归类型使用 Box,图状结构使用 Rc/Arc,需要修改共享数据时使用 RefCell。
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
// Box<T>: heap allocation, single owner
let b = Box::new(5); // 5 lives on heap
println!("{}", b); // dereferenced automatically
// Rc<T>: reference counting, multiple owners (single-threaded)
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a); // increments ref count
println!("count: {}", Rc::strong_count(&a)); // 2
// Arc<T>: atomic Rc, thread-safe
let arc = Arc::new(vec![1, 2, 3]);
// RefCell<T>: interior mutability (runtime borrow check)
let cell = RefCell::new(5);
*cell.borrow_mut() += 1; // mutable borrow checked at runtime结构体、枚举与 Trait
定义结构体
结构体将相关字段组合在一起。命名字段结构体最常见。元组结构体在字段名无意义时有用(Color、Point)。单元结构体没有数据,用于实现 trait。.. 语法从另一个实例复制未指定的字段。结构体在栈上分配,除非它们包含堆类型(String、Vec、Box)。
// named-field struct
struct User {
name: String,
age: u32,
active: bool,
}
let u = User {
name: String::from("Alice"),
age: 30,
active: true,
};
// field init shorthand
let name = String::from("Bob");
let u2 = User { name, age: 25, active: true };
// update syntax (copy remaining fields from another)
let u3 = User { age: 40, ..u2 };
// tuple struct
struct Color(u8, u8, u8);
let red = Color(255, 0, 0);
// unit struct (no fields, useful for traits)
struct AlwaysEqual;使用 impl 的方法
方法放在 impl 块中。&self 不可变借用,&mut self 可变借用,self 获取所有权(消费)。关联函数(没有 self 参数)类似静态方法——用 Type::function() 调用。Self 是类型的别名。允许多个 impl 块,适用于按关注点拆分方法或条件编译。
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// associated function (constructor, no &self)
fn new(w: f64, h: f64) -> Self {
Rectangle { width: w, height: h }
}
// method (borrows self)
fn area(&self) -> f64 {
self.width * self.height
}
// mutable method
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
// consuming method (takes ownership)
fn into_square(self) -> Rectangle {
let side = (self.width + self.height) / 2.0;
Rectangle { width: side, height: side }
}
}
let mut r = Rectangle::new(10.0, 5.0);
println!("Area: {}", r.area()); // 50
r.scale(2.0);
let sq = r.into_square(); // r consumed枚举与模式匹配
Rust 中的枚举是代数数据类型——每个变体可以携带不同的数据。这使它们比 C 枚举强大得多。使用 match 进行模式匹配可以解构变体并提取数据。当值可以是几种不同形式之一时使用枚举。matches! 宏是返回 bool 的单模式匹配简写。
// enum with data (algebraic data type)
enum Message {
Quit, // no data
Move { x: i32, y: i32 }, // named fields
Write(String), // tuple variant
ChangeColor(i32, i32, i32), // tuple variant
}
// pattern matching with destructuring
fn process(msg: Message) {
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => println!("RGB({}, {}, {})", r, g, b),
}
}
process(Message::Move { x: 10, y: 20 });
process(Message::Write(String::from("hello")));
// enums can have methods too
impl Message {
fn is_quit(&self) -> bool {
matches!(self, Message::Quit)
}
}Option 与 Result
Option<T> 替代 null——你必须显式处理 None 情况,消除了 NullPointerException 式的错误。Result<T, E> 用于可能失败的操作。两者都有丰富的方法:map(转换)、and_then(链式)、unwrap_or(默认值)、is_some/is_ok(检查)。Result 上的 ? 运算符自动传播错误。这两个类型是 Rust 错误处理的支柱。
// Option<T>: Some(value) or None (replaces null)
fn find_user(id: i32) -> Option<String> {
if id == 1 { Some(String::from("Alice")) }
else { None }
}
let user = find_user(1);
match user {
Some(name) => println!("Found: {}", name),
None => println!("Not found"),
}
// convenient methods
let name = find_user(1).unwrap_or("Anonymous".into());
let upper = find_user(1).map(|n| n.to_uppercase());
let len = find_user(1).and_then(|n| Some(n.len()));
// Result<T, E>: Ok(value) or Err(error)
fn parse_num(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}
match parse_num("42") {
Ok(n) => println!("Parsed: {}", n),
Err(e) => println!("Error: {}", e),
}Trait 与 Trait 约束
Trait 定义共享行为(类似其他语言的接口)。类型用 'impl Trait for Type' 实现 trait。Trait 可以有默认方法实现。Trait 约束(<T: Trait>)将泛型限制为实现特定 trait 的类型。'where' 子句提高了复杂约束的可读性。Trait 通过静态分发(泛型)和动态分发(trait 对象 &dyn Trait)实现多态。
// define a trait (interface)
trait Summary {
fn summarize(&self) -> String;
// default method
fn author(&self) -> String {
String::from("Unknown")
}
}
struct Article { title: String, content: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, self.content)
}
// author() uses default implementation
}
let a = Article {
title: "Rust".into(),
content: "Great".into(),
};
println!("{}", a.summarize());
println!("Author: {}", a.author()); // Unknown
// generic with trait bound
fn print_summary<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
// multiple bounds with +, or where clause
fn display<T: Summary + std::fmt::Display>(item: &T) {}
// or: fn display<T>(item: &T) where T: Summary + std::fmt::Display {}派生宏与 Trait 对象
#[derive(...)] 自动实现常见 trait:Debug(调试打印)、Clone(深拷贝)、PartialEq/Eq(== 比较)、Hash(用于 HashMap 键)、Copy(栈拷贝而非移动)。Trait 对象(&dyn Trait 或 Box<dyn Trait>)通过 vtable 实现运行时多态,有少量性能损失。尽可能使用泛型进行静态分发(零成本),需要异构集合时使用 trait 对象。
// derive common traits automatically
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone(); // Clone
println!("{:?}", p1); // Debug
println!("{}", p1 == p2); // PartialEq -> true
// trait object: dynamic dispatch
trait Animal {
fn sound(&self) -> String;
}
struct Dog;
struct Cat;
impl Animal for Dog { fn sound(&self) -> String { "Woof".into() } }
impl Animal for Cat { fn sound(&self) -> String { "Meow".into() } }
// Vec of trait objects (dynamic dispatch via vtable)
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
];
for a in &animals {
println!("{}", a.sound());
}错误处理
Result 与 ? 运算符
? 运算符是传播错误的惯用方式。对于 Ok(v),它解包为 v。对于 Err(e),它立即从函数返回 Err(e)。? 还通过 From trait 转换错误类型,所以返回 Box<dyn Error> 的函数可以对任何错误类型使用 ?。对于 Option,? 提前返回 None。这使得错误处理简洁而不牺牲安全性。
use std::fs;
use std::io;
use std::num::ParseIntError;
// ? propagates errors: if Err, return early; if Ok, unwrap
fn read_config(path: &str) -> Result<i32, io::Error> {
let content = fs::read_to_string(path)?; // ? on io::Result
Ok(content.len() as i32)
}
// ? converts error types via From
fn parse_and_read(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?; // io::Error -> Box
let n: i32 = content.trim().parse()?; // ParseIntError -> Box
Ok(n)
}
// ? on Option too
fn first_char(s: &str) -> Option<char> {
s.lines().next()?.chars().next()
}Panic 与 Result
对不可恢复的错误(bug、违反不变量)使用 panic!——它表示编程错误。对预期的、可恢复的失败(用户输入、文件 I/O、网络)使用 Result。unwrap()/expect() 在错误时 panic——在测试、原型或可以证明值有效时可以接受。在生产代码中,优先使用 ? 和 match 进行正确的错误处理。
// panic: unrecoverable error, crashes the program
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Division by zero!"); // unwinds stack
}
a / b
}
// Result: recoverable error, caller decides
fn safe_divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}
// unwrap/expect: panic on Err (use sparingly)
let n: i32 = "42".parse().unwrap(); // panics on error
let n2: i32 = "42".parse().expect("valid number"); // panic with msg
// when to panic vs Result:
// panic: bugs, invariant violations, impossible states
// Result: expected failures (file not found, parse error)自定义错误类型
自定义错误类型为你提供类型安全、结构化的错误处理。实现 Display(人类可读)和 Error(用于源链)。为每个底层错误实现 From,以便 ? 自动转换。像 thiserror(派生宏)或 anyhow(动态错误盒)这样的库可以减少样板代码。库使用 thiserror,应用程序使用 anyhow。
use std::fmt;
use std::error::Error;
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
NotFound(String),
}
// implement Display (required by Error)
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO error: {}", e),
AppError::Parse(e) => write!(f, "Parse error: {}", e),
AppError::NotFound(name) => write!(f, "Not found: {}", name),
}
}
}
// implement Error
impl Error for AppError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
AppError::Io(e) => Some(e),
AppError::Parse(e) => Some(e),
_ => None,
}
}
}
// From impls for ? to work automatically
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}匹配与组合 Result
Result 组合器允许函数式风格的错误处理而无需 match。map 转换 Ok 值,map_err 转换错误,and_then 链接可能失败的操作(flatMap)。unwrap_or 在错误时提供默认值。is_ok/is_err 检查而不消费。这些方法使错误处理链可读并避免深度嵌套。
// match on Result
let result: Result<i32, &str> = Ok(42);
match result {
Ok(n) => println!("Got: {}", n),
Err(e) => println!("Error: {}", e),
}
// combinators
let r1: Result<i32, &str> = Ok(5);
let doubled = r1.map(|n| n * 2); // Ok(10)
let mapped_err = r1.map_err(|e| format!("{}", e));
// and_then: chain fallible operations
let r2 = r1.and_then(|n| if n > 0 { Ok(n * 2) } else { Err("negative") });
// unwrap_or family
let val = r1.unwrap_or(0); // 5 or 0
let val2 = r1.unwrap_or_default(); // 5 or T::default()
let val3 = r1.unwrap_or_else(|_| 0); // lazy default
// check variants
println!("{}", r1.is_ok()); // true
println!("{}", r1.is_err()); // false使用 From 转换错误
? 运算符使用 From 转换错误。Box<dyn Error> 为所有标准错误实现 From,使其成为方便的通用类型。anyhow crate 提供 anyhow::Result,它添加上下文(例如,.context("failed to read config")?)。对于库,用 thiserror 定义特定的错误枚举;对于应用程序,使用 anyhow 以简化。