Rust速查手册
Rust所有权/借用/trait/错误处理速查
🦀 cargo 工程管理
--bin 可执行程序,--lib 库;cargo 自动生成 git 与目录骨架。
cargo new myapp --bin # 二进制程序 src/main.rs
cargo new mylib --lib # 库 src/lib.rs
cargo init # 在当前目录初始化
开发构建带调试信息,--release 启用优化。
cargo run
cargo run --release
cargo build
cargo build --release
cargo run -- --flag # -- 之后是传给程序的参数
./target/debug/myapp # 直接运行产物
cargo add 自动选版本并写入 Cargo.toml。
cargo add serde
cargo add serde --features derive
cargo add tokio --features full
cargo add anyhow@1
cargo remove serde
cargo tree # 查看依赖树
check 只做类型检查不生成代码,反馈最快。
cargo check
cargo test
cargo test it_works # 按名过滤
cargo doc --open # 生成并打开文档
cargo bench
rustfmt 统一风格,clippy 给出惯用法建议。
rustup component add rustfmt clippy
cargo fmt
cargo fmt --check
cargo clippy
cargo clippy --fix --allow-dirty
[dependencies] 写三方库,[dev-dependencies] 仅测试/示例可用。
[package]
name = "myapp"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
[profile.release]
opt-level = 3
🔒 变量与所有权
变量默认不可变,需要修改必须声明 mut;同名遮蔽(shadow)可换类型。
let x = 5;
// x = 6; // 编译错误:不可变
let mut y = 1;
y += 1;
let spaces = " "; // &str
let spaces = spaces.len(); // shadow:变成 usize
const MAX_POINTS: u32 = 100_000;
有符号 i8~i64/isize、无符号 u8~u64/usize、浮点 f32/f64。
let i: i32 = -42;
let u: u64 = 100;
let f: f64 = 3.14;
let b: bool = true;
let c: char = '中'; // char 是 Unicode 标量值,4 字节
let dec = 1_000;
let hex = 0xff;
let bin = 0b1010;
let byte: u8 = b'A';
let n = "123".parse::<i32>().unwrap();
堆数据赋值即转移所有权,原变量失效;需要深拷贝用 clone()。
let s1 = String::from("hello");
let s2 = s1; // 所有权移动到 s2
// println!("{}", s1); // 编译错误:s1 已被移动
let s3 = s2.clone(); // 深拷贝,s2 仍可用
println!("{} {}", s2, s3);
// 函数传参同样会转移所有权
takes_ownership(s3);
// s3 此后不可用
固定大小的栈类型(整数、bool、char、元组等)实现 Copy,赋值自动复制。
let x = 5;
let y = x; // i32 实现 Copy,x 仍可用
println!("{} {}", x, y);
let t1 = (1, 2.0); // 元素都 Copy,元组也 Copy
let t2 = t1;
fn calc(s: String) -> String { // 进函数失去所有权,返回重新移交
s
}
🔗 借用与生命周期
同一时刻:多个不可变引用,或唯一一个可变引用,二者互斥。
fn str_len(s: &String) -> usize { s.len() }
fn push_world(s: &mut String) {
s.push_str(" world");
}
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s; // 任意多个不可变借用 OK
println!("{} {}", r1, r2);
let r3 = &mut s; // r1/r2 此后不再使用才能借用
r3.push('!');
生命周期不改变存活时间,只是告诉编译器引用之间的关系。
// 返回的引用与 x、y 中较短者存活一致
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 结构体持有引用必须标注生命周期
struct Excerpt<'a> {
part: &'a str,
}
let e = Excerpt { part: "hello world" };
字符串切片类型是 &str;数组/ Vec 也可切片。切片越界会 panic。
let s = String::from("hello world");
let hello: &str = &s[0..5];
let world = &s[6..];
let all = &s[..];
let arr = [10, 20, 30, 40];
let sl: &[i32] = &arr[1..3]; // [20, 30]
fn first_word(s: &str) -> &str { // 参数用 &str 最通用
s.split_whitespace().next().unwrap_or("")
}
🏷 struct / enum / trait
new 只是约定函数;&self 读、&mut self 改、self 获取所有权。
struct User {
name: String,
age: u32,
active: bool,
}
impl User {
fn new(name: &str) -> Self {
Self { name: name.to_string(), age: 0, active: true }
}
fn greet(&self) -> String {
format!("hi, {}", self.name)
}
fn birthday(&mut self) {
self.age += 1;
}
}
let mut u = User::new("Tom");
适合命名字段无意义的轻量包装;单元结构体无字段。
struct Color(u8, u8, u8);
struct Meter(f64);
let red = Color(255, 0, 0);
let Color(r, g, b) = red;
println!("{} {} {}", r, g, b);
fn area(Meter(w): Meter) -> f64 { w * w } // 解构作参数
每个变体可携带不同类型数据;match 必须穷尽所有情况。
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let ip = IpAddr::V4(127, 0, 0, 1);
match ip {
IpAddr::V4(a, b, ..) => println!("IPv4 {}.{}.x.x", a, b),
IpAddr::V6(s) => println!("IPv6 {}", s),
}
match n {
1 => println!("一"),
2 | 3 | 5 => println!("质数"),
6..=10 => println!("六到十"),
_ => println!("其他"),
}
Rust 没有 null,可能缺失一律用 Option<T>。
fn find_id(name: &str) -> Option<u32> {
if name == "Tom" { Some(1) } else { None }
}
match find_id("Amy") {
Some(id) => println!("id = {}", id),
None => println!("未找到"),
}
// 只关心一种情况时用 if let
if let Some(id) = find_id("Tom") {
println!("{}", id);
}
let len = find_id("x").unwrap_or(0);
类似接口;可带默认实现,可作为参数/返回类型。
trait Greet {
fn greet(&self) -> String;
fn hello(&self) -> String { // 默认实现
format!("(默认) {}", self.greet())
}
}
impl Greet for User {
fn greet(&self) -> String {
format!("我是 {}", self.name)
}
}
// trait 作参数:impl Trait 语法糖
fn print_greet(g: &impl Greet) {
println!("{}", g.greet());
}
❗ 错误处理 Result / ?
可恢复错误用 Result<T, E>,没有异常机制。
use std::fs::File;
let f = match File::open("a.txt") {
Ok(file) => file,
Err(e) => panic!("打开失败: {e}"),
};
let n: Result<i32, _> = "42".parse::<i32>();
match n {
Ok(v) => println!("{v}"),
Err(e) => println!("解析失败: {e}"),
}
成功取值继续,失败立即 return Err;自动做可转换的错误类型提升。
use std::fs::File;
use std::io::{self, Read};
fn read_username() -> io::Result<String> {
let mut f = File::open("user.txt")?; // 失败直接返回 Err
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s.trim().to_string())
}
fn main() -> io::Result<()> {
let name = read_username()?;
println!("{name}");
Ok(())
}
原型或确定不会错时使用;生产代码中 expect 要写明原因。
let v = "42".parse::<i32>().unwrap();
let v = "42".parse::<i32>().expect("配置必须是整数");
let home = std::env::var("HOME").unwrap_or_default();
let port: u16 = std::env::var("PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(8080);
应用程序可用 anyhow::Error 统一各种错误;库建议定义自己的错误枚举。
// Cargo.toml: anyhow = "1", thiserror = "1"
use anyhow::{Context, Result};
fn run() -> Result<()> {
let cfg = std::fs::read_to_string("config.toml")
.context("读取配置文件失败")?;
println!("{cfg}");
Ok(())
}
fn main() -> Result<()> {
run()
}
📚 集合类型
vec! 宏快速创建;get 返回 Option,不会因越界 panic。
let mut v: Vec<i32> = vec![1, 2, 3];
v.push(4);
v.pop();
v.insert(0, 9);
v.remove(1);
v.extend([5, 6]);
println!("{}", v[0]);
if let Some(x) = v.get(100) {
println!("{x}");
}
let arr: Vec<i32> = (1..=5).collect();
entry API 是“存在则改、不存在则插”的惯用法。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("Blue", 10);
scores.insert("Red", 50);
if let Some(v) = scores.get("Blue") {
println!("{v}");
}
// 统计词频
let text = "a b a c b a";
let mut count = HashMap::new();
for w in text.split_whitespace() {
*count.entry(w).or_insert(0) += 1;
}
scores.remove("Red");
String 可增长有所有权,&str 是字符串切片(借用)。
let mut s = String::from("foo");
s.push_str("bar");
s.push('!');
let a = String::from("Hello, ");
let b = String::from("Rust");
let c = a + &b; // a 被移动,&b 自动解引用为 &str
let c = format!("{}-{}", a, b);
let lit: &str = "静态字符串"; // 编译期内嵌,生命周期 'static
let owned = lit.to_string();
🔁 迭代器 Iterator
迭代器是惰性的,collect 等消费方法才真正执行。
let v = vec![1, 2, 3, 4];
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect();
let strings: Vec<String> = v.iter().map(|x| x.to_string()).collect();
// 原地修改用 iter_mut
let mut v = v;
v.iter_mut().for_each(|x| *x += 10);
fold 是通用聚合;sum 需标注结果类型。
let sum: i32 = v.iter().sum();
let product: i32 = v.iter().product();
let s = v.iter().fold(0, |acc, x| acc + x);
let max = v.iter().fold(i32::MIN, |a, x| a.max(*x));
// 求累计和:1, 3, 6, 10
let acc: Vec<i32> = v.iter()
.scan(0, |state, x| { *state += x; Some(*state) })
.collect();
带索引遍历、双迭代器对齐、首尾相接。
for (i, x) in v.iter().enumerate() {
println!("{i}: {x}");
}
let names = ["a", "b"];
let ages = [1, 2];
for (n, a) in names.iter().zip(ages.iter()) {
println!("{n} = {a}");
}
let merged: Vec<i32> = [1, 2].into_iter()
.chain([3, 4])
.collect();
v.iter().take(2); // 前两个
v.iter().skip(1); // 跳过一个
v.iter().rev(); // 反转
v.windows(2); // 相邻窗口
v.chunks(2); // 分块
iter() 借用、iter_mut() 可变借用、into_iter() 获取所有权。
let v = vec![String::from("a"), String::from("b")];
for s in v.iter() { } // s: &String,v 仍可用
for s in &v { } // 同上(语法糖)
let mut v2 = vec![1, 2];
for x in v2.iter_mut() { *x += 1; }
for s in v.into_iter() { // s: String,循环后 v 被消费
println!("{s}");
}
📄 文件与字符串处理
小文件用 read_to_string/write 一步到位。
use std::fs;
let content = fs::read_to_string("a.txt")?;
fs::write("b.txt", "hello")?;
let bytes = fs::read("a.bin")?;
fs::create_dir_all("out/logs")?;
fs::rename("old.txt", "new.txt")?;
fs::remove_file("tmp.txt")?;
fs::copy("src.txt", "dst.txt")?;
Path 借用、PathBuf 有所有权,类似 str/String 关系。
use std::path::PathBuf;
let p = PathBuf::from("dir").join("sub").join("a.txt");
p.exists();
p.is_file();
p.is_dir();
p.file_name(); // Some("a.txt")
p.extension(); // Some("txt")
p.parent(); // dir/sub
for entry in fs::read_dir(".")? {
println!("{}", entry?.path().display());
}
split 返回迭代器;parse 需在 Result 上指定目标类型。
let s = " Hello, Rust ";
s.trim();
s.to_lowercase();
s.to_uppercase();
s.replace("Rust", "World");
s.contains("Hello");
s.starts_with(" ");
s.split(", ")
.map(|x| x.trim())
.collect::<Vec<_>>();
let n: i32 = "42".parse()?;
let s = 42.to_string();
let parts: Vec<&str> = "a,b,c".split(',').collect();
"2026".chars().count(); // 字符数(按 Unicode)
"abc".len(); // 字节长度
😶 没有匹配的条目,换个关键词试试
📖 使用说明
全程在浏览器本地运行。
操作步骤:
- 搜索或浏览分类条目;
- 查看语法与示例代码;
- 点击复制代码块。
💬 用户评论 (0)
还没有评论,快来抢沙发!