let n = 7;
let parity = if n % 2 == 0 { "even" } else { "odd" };
println!("{parity}");
// 分支类型必须一致;少写 else 编译器会按 unit 类型给你 ()
let _u = if n > 0 { /* () */ };
if 是表达式,会返回值。所有分支类型必须一致。漏掉 else 时返回值是 unit 类型 ()。
loop / while / for 区间循环
let mut i = 0;
let answer = loop {
i += 1;
if i == 10 { break i * i } // loop 也可返回值
};
while i < 20 { i += 1 }
for n in 0..5 { print!("{n} ") } // 0..5 不含 5
for n in 0..=5 { print!("{n} ") } // 0..=5 含 5
for c in "abc".chars() { print!("{c}") }
let opt: Option<i32> = Some(7);
// if let:只关心一个分支
if let Some(x) = opt {
println!("got {x}");
}
// let else:解构失败就走发散分支(return / break / panic)
fn parse(s: &str) -> i32 {
let Ok(n) = s.parse::<i32>() else {
return -1;
};
n
}
println!("{}", parse("42"));
if let 就是只关心一个分支的 match。let else(1.65 稳定)匹配成功就绑定,失败就走发散块(return / break / panic),写提前返回不用层层嵌套。
带标签循环与 break 返回值
let found = 'outer: loop {
for i in 0..10 {
for j in 0..10 {
if i * j == 12 {
break 'outer (i, j); // 跳出外层并带返回值
}
}
}
};
println!("{found:?}"); // (2, 6)
let x = 3.9_f64;
let y = x as i32; // 截断小数 → 3,不四舍五入
let b = 300_i32 as u8; // 溢出回绕 → 44 (300 % 256)
let c = 'A' as u32; // char → 码点 65
let back = 97_u8 as char; // u8 → char 'a'
println!("{y} {b} {c} {back}");
as 做廉价的原始数值转换:浮点转整型向零截断,缩窄会回绕(绝不 panic),char 转整型得到码点。可能失败的转换优先用 TryFrom / try_into。
块作为表达式
let area = {
let w = 3;
let h = 4;
w * h // 最后一行无分号 = 块的值
};
println!("{area}"); // 12
// 用块把临时变量限制在小作用域里
let cleaned = {
let raw = " hi ";
raw.trim().to_uppercase()
};
println!("{cleaned}");
'static 表示引用在整个程序运行期间都有效。字符串字面量就是 &'static str。thread::spawn 这种 API 需要 'static,是为了让派生的线程不会比它捕获的数据活得更久。
持有引用的结构体
struct Excerpt<'a> {
part: &'a str,
}
impl<'a> Excerpt<'a> {
fn part(&self) -> &str { self.part }
}
let book = String::from("Call me Ishmael. Some years ago...");
let first = book.split('.').next().unwrap();
let e = Excerpt { part: first };
println!("{}", e.part()); // e 不能比 book 活得久
#[derive(Debug, Clone, Copy)]
enum Status {
Active = 1,
Paused = 2,
Closed = 9,
}
let s = Status::Paused;
let code = s as i32; // 无字段的 enum 可 as 成整数
println!("{code}"); // 2
无字段(C 风格)的 enum 可以给每个变体指定整数判别值,并用 as 转成整数。序列化成状态码很方便。注意:只有无字段 enum 才支持 as 转换。
matches! 宏
#[derive(Debug)]
enum Token { Num(i32), Plus, Minus }
let t = Token::Num(5);
// 只想知道"是不是某种形状",不用写完整 match
let is_num = matches!(t, Token::Num(_));
let is_op = matches!(t, Token::Plus | Token::Minus);
println!("{is_num} {is_op}"); // true false
matches!(表达式, 模式) 返回 bool,表达式匹配模式(可带守卫)就是 true。只想判断"是不是某种形状"时,比写完整 match 干净得多。
Option 组合子 map / and_then / filter
let s = Some("42");
let n: Option<i32> = s
.and_then(|x| x.parse().ok()) // Option<i32>,解析失败 → None
.filter(|&n| n > 0) // 不满足 → None
.map(|n| n * 10); // 包在 Some 里变换
println!("{n:?}"); // Some(420)
println!("{:?}", None::<i32>.unwrap_or(0));
// 想给 Vec<i32> 实现自己的 trait
pub trait Sum {
fn sum(&self) -> i32;
}
impl Sum for Vec<i32> { // ✅ trait 是本 crate 定义的,所以可以
fn sum(&self) -> i32 { self.iter().copied().sum() }
}
let v = vec![1, 2, 3];
println!("{}", v.sum()); // 6
孤儿规则:实现 Trait for Type 时,Trait 或 Type 必须有一个是本 crate 定义的。这避免两个 crate 各自给同一对 (Trait, Type) 提供冲突实现。
泛型函数上的 trait 约束
use std::fmt::Display;
fn announce<T: Display>(item: T) {
println!("breaking news: {item}");
}
// where 子句让多个约束更好读
fn longest<T>(a: T, b: T) -> T
where
T: PartialOrd,
{
if a > b { a } else { b }
}
announce("rust 2024");
announce(42);
println!("{}", longest(3, 7));
一揽子实现 impl<T: Bound> Trait for T 一次性给所有满足约束的类型实现你的 trait。标准库 ToString 就是这样给所有 Display 类型实现的。很强,但可能引发一致性冲突。
超 trait (supertrait)
use std::fmt::Display;
// 实现 Named 的类型必须也实现 Display
trait Named: Display {
fn name(&self) -> String {
format!("name is {self}") // 可以直接用 Display 的能力
}
}
struct Cat;
impl Display for Cat {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Cat")
}
}
impl Named for Cat {}
println!("{}", Cat.name()); // name is Cat
trait Named: Display 把 Display 声明为超 trait,每个实现 Named 的类型都必须也实现 Display,且 Named 的默认方法可以依赖 Display 的能力。用于要求一个 trait 所依赖的能力。
泛型(9)
泛型函数
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut max = list[0];
for &x in list {
if x > max { max = x }
}
max
}
println!("{}", largest(&[10, 1, 50, 20])); // 50
println!("{}", largest(&[3.5, 1.0, 7.2])); // 7.2
泛型函数用尖括号 <T>。trait 约束(T: PartialOrd)限制函数体能对 T 做哪些操作。单态化在编译期为每个具体 T 生成专用版本,运行期零开销。
let v = vec![1, 2, 3];
for x in v.iter() { print!("{x} ") } // &i32 (v 还能用)
for x in &v { print!("{x} ") } // 同 .iter()
let mut w = v.clone();
for x in w.iter_mut() { *x *= 10 } // &mut i32 (原地修改)
for x in v.into_iter(){ print!("{x} ") } // i32 (v 被消费)
.iter() 返回 &T(集合还能用),.iter_mut() 返回 &mut T,.into_iter() 返回 T 并消费集合。for x in &v 等价于 .iter(),for x in v 等价于 .into_iter()。
HashMap<K, V>
use std::collections::HashMap;
let mut m: HashMap<String, u32> = HashMap::new();
m.insert("apple".into(), 3);
m.insert("pear".into(), 5);
// 取值
if let Some(n) = m.get("apple") { println!("{n}") }
// 不存在则插入默认值
*m.entry("apple".into()).or_insert(0) += 1;
// 遍历
for (k, v) in &m { println!("{k}: {v}") }
let mut s = String::from("hello");
s.push(' ');
s.push_str("world");
let n = s.len(); // 字节长度
let chars = s.chars().count(); // 字符数(多字节正确)
let upper = s.to_uppercase();
let parts: Vec<&str> = s.split(' ').collect();
let joined = parts.join(",");
println!("{n} {chars} {upper} {joined}");
use std::collections::HashMap;
let text = "a b a c b a";
let mut counts: HashMap<&str, u32> = HashMap::new();
for w in text.split_whitespace() {
*counts.entry(w).or_insert(0) += 1;
}
println!("{:?}", counts.get("a")); // Some(3)
// 值是 Vec 时用 or_insert_with 避免每次都构造空 Vec
let mut groups: HashMap<char, Vec<&str>> = HashMap::new();
for w in ["apple", "avocado", "berry"] {
groups.entry(w.chars().next().unwrap())
.or_insert_with(Vec::new)
.push(w);
}
println!("{:?}", groups.get(&'a'));
entry(k).or_insert(v) 返回 &mut V,不存在就插 v,做计数最合适。默认值构造代价大(如 Vec::new)时用 or_insert_with(|| ...),只在 miss 时才构造。
切片模式与 windows / chunks
let v = [1, 2, 3, 4, 5];
// 切片模式解构
if let [first, .., last] = v {
println!("{first} {last}"); // 1 5
}
// 滑动窗口与定长分块
for w in v.windows(2) { print!("{w:?} ") } // [1,2] [2,3] ...
println!();
for c in v.chunks(2) { print!("{c:?} ") } // [1,2] [3,4] [5]
use std::fs;
use std::io;
fn read_first_line(path: &str) -> io::Result<String> {
let s = fs::read_to_string(path)?; // Err -> 立刻 return Err
Ok(s.lines().next().unwrap_or("").to_string())
}
match read_first_line("/etc/hostname") {
Ok(l) => println!("first: {l}"),
Err(e) => println!("err: {e}"),
}
? 运算符 Ok 就解包,Err 就立刻返回给调用方。错误类型必须能 From 转换为函数声明的错误类型。一个字符替代 8 行 match 模板。
Box<dyn Error>,快速错误类型
use std::error::Error;
use std::fs;
fn main() -> Result<(), Box<dyn Error>> {
let s = fs::read_to_string("Cargo.toml")?;
let n: usize = s.lines().count();
println!("{n} lines");
Ok(())
}
use std::fs;
fn load(path: &str) -> Result<String, String> {
fs::read_to_string(path)
.map_err(|e| format!("failed to read {path}: {e}"))
}
println!("{:?}", load("/no/such/file").err());
// Some("failed to read /no/such/file: No such file or directory ...")
let words = vec!["rust", "is", "fast"];
let joined: String = words.iter().fold(String::new(), |mut acc, w| {
if !acc.is_empty() { acc.push(' ') }
acc.push_str(w);
acc
});
println!("{joined}"); // rust is fast
let sum = (1..=100).fold(0, |a, b| a + b);
println!("{sum}"); // 5050
let names = ["Ada", "Bob", "Cy"];
for (i, n) in names.iter().enumerate() {
println!("{i}: {n}");
}
let ages = [30, 25, 40];
for (n, a) in names.iter().zip(ages.iter()) {
println!("{n} is {a}");
}
// Cargo.toml: tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let h = tokio::spawn(async {
sleep(Duration::from_millis(50)).await;
"from task"
});
let val = h.await.unwrap(); // 等任务完成拿返回值
println!("{val}");
}
use std::rc::Rc;
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a); // 只复制计数,不复制字符串
let c = a.clone(); // 同上
println!("{} refs", Rc::strong_count(&a)); // 3
println!("{a} {b} {c}");
use std::sync::Arc;
use std::thread;
let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];
for _ in 0..3 {
let d = Arc::clone(&data);
handles.push(thread::spawn(move || {
println!("{:?}", d);
}));
}
for h in handles { h.join().unwrap() }
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let c = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut guard = c.lock().unwrap();
*guard += 1;
}));
}
for h in handles { h.join().unwrap() }
println!("{}", *counter.lock().unwrap()); // 5
use std::rc::Rc;
use std::cell::RefCell;
let shared = Rc::new(RefCell::new(vec![1, 2]));
let alias = Rc::clone(&shared);
shared.borrow_mut().push(3); // 通过任一别名改
alias.borrow_mut().push(4);
println!("{:?}", shared.borrow()); // [1,2,3,4]
use std::sync::{Arc, RwLock};
use std::thread;
let cache = Arc::new(RwLock::new(vec![1, 2, 3]));
// 多个读者并发
let r = Arc::clone(&cache);
let h = thread::spawn(move || {
let guard = r.read().unwrap(); // 共享读锁
println!("{:?}", *guard);
});
h.join().unwrap();
cache.write().unwrap().push(4); // 独占写锁
println!("{:?}", *cache.read().unwrap());
// internal::api 是实现细节,但 lib 想让用户用 mycrate::Client
mod internal {
pub mod api {
pub struct Client;
}
}
pub use internal::api::Client; // 对外重命名 / 提升路径
// 调用方: use mycrate::Client;
pub use 把一个项目以另一个(通常更浅的)路径重新导出。常用场景:内部模块结构可以随便重排,对外保持稳定、扁平的公开 API。
use 分组导入与 as 别名
use std::collections::{HashMap, HashSet, BTreeMap}; // 一行多个
use std::io::Result as IoResult; // 重名时取别名
use std::fmt::Result as FmtResult;
fn _read() -> IoResult<()> { Ok(()) }
let _m: HashMap<i32, i32> = HashMap::new();
let _s: HashSet<i32> = HashSet::new();
use a::{B, C, D} 一行从同一路径导入多个项。use a::Thing as Alias 在导入时重命名,两个路径导出同名(std::io::Result 和 std::fmt::Result)时必须用。
#[cfg(test)] mod tests 块只在测试时编译,里面的辅助函数和 fixture 不会给发布二进制增加任何体积。use super::* 引入父模块的项。把重复断言抽成 check() 辅助函数。
常见坑(13)
坑:多字节字符上切 &str
let s = "中文";
// let bad = &s[0..1]; // ❌ panic: not a char boundary
// ✅ 用 .chars() 按字符切
let first: String = s.chars().take(1).collect();
println!("{first}"); // 中
// ✅ 已知字节边界时可以 &s[..n]
let s2 = "abc";
println!("{}", &s2[..1]); // a
// ❌ 闭包要 'static → 不思考直接 clone 大对象
let big = vec![0_u8; 10_000_000];
let _bad = move || println!("{}", big.clone().len());
// ✅ 真正需要的是 Arc 共享所有权
use std::sync::Arc;
let big = Arc::new(vec![0_u8; 10_000_000]);
let b = Arc::clone(&big); // 只增加引用计数
let _good = move || println!("{}", b.len());
let v = vec![1, 2, 3];
let w = v;
// println!("{:?}", v); // ❌ borrow of moved value
// ✅ 想保留 v 就 clone 或借用
let v = vec![1, 2, 3];
let w = v.clone();
println!("{v:?} {w:?}");
let v = vec![1, 2, 3];
// let x = v[10]; // ❌ 运行期 panic: index out of bounds
// ✅ .get 返回 Option,安全
match v.get(10) {
Some(x) => println!("{x}"),
None => println!("out of range"),
}
let y = v.get(1).copied().unwrap_or(-1);
println!("{y}"); // 2