Rust 编程语言从入门到实战完全教程
关键词:Rust、系统编程、高性能、并发、编程语言
适用读者:有一定编程基础(熟悉 C/C++/Python/Go 等任意语言),希望系统学习 Rust 的开发者。
目录
- 为什么选择 Rust
- 环境搭建与第一个程序
- Cargo 包管理与项目结构
- 核心基础:变量、类型与控制流
- 所有权与借用机制
- 生命周期(Lifetime)
- 模式匹配与枚举
- 错误处理:Result 与 Option
- Trait 与泛型
- 并发编程与 async/await
- 常用 Crate 介绍
- Web 开发实战:Axum 与 Actix-web
- CLI 工具开发
- FFI 与跨语言调用
- 学习资源与进阶路径
1. 为什么选择 Rust
Rust 是由 Mozilla 发起、目前由 Rust Foundation 维护的系统级编程语言。它的核心设计目标是 "零成本抽象" 和 "内存安全无需垃圾回收"。
1.1 Rust 的三大卖点
- 内存安全:编译期通过所有权系统杜绝空指针、悬垂引用、数据竞争等内存 bug,无需 GC。
- 高性能:与 C/C++ 同一量级的运行速度,适合系统编程、游戏引擎、嵌入式等场景。
- 开发体验:优秀的编译器错误提示、内置测试框架、Cargo 包管理器,让开发效率极高。
1.2 谁在用 Rust
- Mozilla:Firefox 浏览器引擎 Servo
- Amazon:Firecracker 虚拟化引擎
- Microsoft:Windows 内核部分模块
- Google:Android 操作系统底层组件
- Discord:从 Go 迁移到 Rust 后性能提升显著
- 字节跳动、蚂蚁集团:大量微服务和基础设施
2. 环境搭建与第一个程序
2.1 安装 Rust
推荐通过 rustup 安装:
# Linux / macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 安装完成后重启终端,或执行:
source $HOME/.cargo/env
# 验证安装
rustc --version
cargo --version
Windows 用户可下载 rustup-init.exe 直接安装。
2.2 Hello World
创建文件 main.rs:
fn main() {
println!("Hello, world!");
}
编译并运行:
rustc main.rs
./main
但实际开发中我们几乎不直接使用 rustc,而是通过 Cargo 管理项目。
3. Cargo 包管理与项目结构
3.1 创建项目
cargo new hello_rust
cd hello_rust
生成的目录结构:
hello_rust/
├── Cargo.toml # 项目元数据与依赖声明
├── src/
│ └── main.rs # 入口文件
└── .gitignore
3.2 Cargo.toml 详解
[package]
name = "hello_rust"
version = "0.1.0"
edition = "2021" # Rust 版本:2015 / 2018 / 2021 / 2024
authors = ["Your Name <you@example.com>"]
description = "A demo project"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
3.3 常用 Cargo 命令
cargo build # 编译(debug 模式)
cargo build --release # 编译(release 模式,优化级别更高)
cargo run # 编译并运行
cargo test # 运行测试
cargo doc --open # 生成并打开文档
cargo clippy # Lint 检查
cargo fmt # 代码格式化
cargo add serde # 添加依赖(Cargo 1.62+)
4. 核心基础:变量、类型与控制流
4.1 变量与可变性
Rust 中变量默认 不可变,这是其安全哲学的体现:
fn main() {
let x = 5;
// x = 6; // 编译错误!x 不可变
let mut y = 10;
y = 20; // 合法,mut 声明了可变性
// Shadowing(遮蔽):重新 let 同名变量,可改变类型
let spaces = " ";
let spaces = spaces.len(); // 从 &str 变为 usize
}
4.2 基本类型
| 类别 | 类型 | 示例 |
|---|---|---|
| 整数 | i8, i16, i32, i64, i128, isize |
let a: i32 = 42; |
| 无符号整数 | u8, u16, u32, u64, u128, usize |
let b: u64 = 100; |
| 浮点数 | f32, f64 |
let pi: f64 = 3.14159; |
| 布尔 | bool |
let flag = true; |
| 字符 | char(4 字节 Unicode) |
let c = '🦀'; |
| 元组 | (T1, T2, ...) |
let tup: (i32, f64) = (5, 3.14); |
| 数组 | [T; N] |
let arr = [1, 2, 3]; |
4.3 函数
fn add(a: i32, b: i32) -> i32 {
a + b // 没有分号 = 返回值(表达式)
}
// 表达式 vs 语句
fn example() -> i32 {
let x = {
let y = 3;
y + 1 // 表达式,作为块的返回值
}; // x = 4
x * 2 // 返回 8
}
4.4 控制流
fn main() {
let number = 7;
// if 表达式(可以赋值)
let desc = if number > 0 { "positive" } else { "non-positive" };
// loop 可以返回值
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 返回 20
}
};
// for 循环
for i in 0..5 {
println!("{}", i);
}
// 遍历迭代器
let names = vec!["Alice", "Bob", "Charlie"];
for name in names.iter() {
println!("Hello, {}!", name);
}
}
5. 所有权与借用机制
这是 Rust 最核心、最具特色的设计,也是新手最大的学习曲线。
5.1 所有权规则
三条铁律:
- 每个值都有且仅有一个所有者
- 同一时刻只能有一个所有者
- 所有者离开作用域时,值被自动释放(Drop)
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权转移给 s2(Move)
// println!("{}", s1); // 编译错误!s1 已失效
let s3 = s2.clone(); // 深拷贝,s2 和 s3 各自独立
println!("{} {}", s2, s3); // 合法
}
5.2 引用与借用
为了避免所有权转移带来的不便,Rust 提供 引用(Reference) 机制:
fn main() {
let s = String::from("hello");
// 不可变引用:可以有多个
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
// 可变引用:同一时刻只能有一个
let mut s2 = String::from("world");
change(&mut s2);
println!("{}", s2); // "world!"
}
fn change(s: &mut String) {
s.push('!');
}
借用规则:
- 可以有 任意数量 的不可变引用
&T - 或 恰好一个 可变引用
&mut T - 两者不能同时存在
这条规则在编译期就消除了数据竞争。
5.3 切片(Slice)
切片是对集合中部分元素的引用:
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
// 字符串字面量就是切片
let literal: &str = "I am a slice";
// 数组切片
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // [2, 3]
}
6. 生命周期(Lifetime)
6.1 为什么需要生命周期
当函数返回引用时,编译器需要确保返回的引用不会指向已被释放的数据:
// 错误示例:编译器无法推断生命周期
// fn longest(x: &str, y: &str) -> &str {
// if x.len() > y.len() { x } else { y }
// }
// 正确:显式标注生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("Longest: {}", result); // 合法:s2 仍存活
}
// println!("{}", result); // 编译错误!s2 已被释放
}
6.2 结构体中的生命周期
当结构体持有引用时,必须标注生命周期:
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.text // 返回的生命周期与 self 绑定
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence: &str = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first_sentence };
println!("{}", excerpt.text);
}
6.3 生命周期省略规则
编译器有三条推断规则,大多数情况无需手动标注:
- 每个引用参数获得各自的生命周期
- 如果只有一个输入生命周期,它被赋给所有输出引用
- 如果是方法(
&self),self的生命周期被赋给所有输出引用
7. 模式匹配与枚举
7.1 枚举定义
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
Color(i32, i32, i32),
}
7.2 match 表达式
fn describe(msg: &Message) -> String {
match msg {
Message::Quit => String::from("Quit"),
Message::Move { x, y } => format!("Move to ({}, {})", x, y),
Message::Write(text) => format!("Write: {}", text),
Message::Color(r, g, b) => format!("Color #{:02x}{:02x}{:02x}", r, g, b),
}
}
// match 必须穷尽所有模式,_ 是通配符
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
7.3 if let 简写
当你只关心一种情况时:
fn main() {
let config_max = Some(3u8);
// 完整 match
match config_max {
Some(max) => println!("Maximum: {}", max),
_ => (),
}
// if let 简写
if let Some(max) = config_max {
println!("Maximum: {}", max);
}
// while let
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
8. 错误处理:Result 与 Option
8.1 不可恢复错误:panic!
fn main() {
// panic!("crash and burn"); // 程序直接终止
// 数组越界也会 panic
let v = vec![1, 2, 3];
// v[99]; // panic: index out of bounds
}
8.2 可恢复错误:Result
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("username.txt")?; // ? 运算符:出错则提前返回
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
// 更优雅的链式写法
fn read_username_short() -> Result<String, io::Error> {
let mut username = String::new();
File::open("username.txt")?.read_to_string(&mut username)?;
Ok(username)
}
// 最简写法
fn read_username_shortest() -> Result<String, io::Error> {
std::fs::read_to_string("username.txt")
}
8.3 自定义错误类型
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Parse(ParseIntError),
Io(std::io::Error),
Custom(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Parse(e) => write!(f, "Parse error: {}", e),
AppError::Io(e) => write!(f, "IO error: {}", e),
AppError::Custom(msg) => write!(f, "Custom error: {}", msg),
}
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::Parse(e)
}
}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self {
AppError::Io(e)
}
}
fn process() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")?; // 自动转换
let num: i32 = content.trim().parse()?; // 自动转换
Ok(num * 2)
}
在实际项目中,推荐使用 thiserror 或 anyhow crate 简化错误处理:
[dependencies]
thiserror = "1"
anyhow = "1"
9. Trait 与泛型
9.1 定义 Trait
trait Summary {
// 必须实现的方法
fn summarize(&self) -> String;
// 默认实现
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..20])
}
}
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, &self.content[..50])
}
}
9.2 泛型函数
// 泛型 + Trait Bound
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest {
largest = item;
}
}
largest
}
// where 子句(复杂约束时更清晰)
fn process<T, U>(t: T, u: U) -> String
where
T: std::fmt::Display + Clone,
U: std::fmt::Debug + Summary,
{
format!("{} - {:?}", t, u.summarize())
}
9.3 泛型结构体与枚举
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// 仅为特定类型实现方法
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// Option 和 Result 就是泛型枚举
// enum Option<T> { Some(T), None }
// enum Result<T, E> { Ok(T), Err(E) }
9.4 Trait 对象(动态分发)
trait Drawable {
fn draw(&self);
}
struct Circle { radius: f64 }
struct Square { side: f64 }
impl Drawable for Circle {
fn draw(&self) { println!("Drawing circle r={}", self.radius); }
}
impl Drawable for Square {
fn draw(&self) { println!("Drawing square s={}", self.side); }
}
// dyn Trait 实现动态多态
fn draw_all(shapes: &[Box<dyn Drawable>]) {
for shape in shapes {
shape.draw();
}
}
fn main() {
let shapes: Vec<Box<dyn Drawable>> = vec![
Box::new(Circle { radius: 5.0 }),
Box::new(Square { side: 3.0 }),
];
draw_all(&shapes);
}
10. 并发编程与 async/await
10.1 线程基础
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("Spawned thread: {}", i);
thread::sleep(Duration::from_millis(100));
}
});
for i in 1..=3 {
println!("Main thread: {}", i);
thread::sleep(Duration::from_millis(150));
}
handle.join().unwrap(); // 等待子线程完成
}
10.2 消息传递(Channel)
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let messages = vec!["hello", "from", "the", "other", "side"];
for msg in messages {
tx.send(msg).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
for received in rx {
println!("Got: {}", received);
}
}
10.3 共享状态:Mutex 与 Arc
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap()); // 10
}
10.4 async/await 异步编程
use tokio::time::{sleep, Duration};
async fn fetch_data(id: u32) -> String {
sleep(Duration::from_millis(100)).await;
format!("Data from task {}", id)
}
#[tokio::main]
async fn main() {
// 并发执行多个异步任务
let (r1, r2, r3) = tokio::join!(
fetch_data(1),
fetch_data(2),
fetch_data(3),
);
println!("{}\n{}\n{}", r1, r2, r3);
// 异步任务生成
let handle = tokio::spawn(async {
fetch_data(4).await
});
let result = handle.await.unwrap();
println!("{}", result);
}
11. 常用 Crate 介绍
| Crate | 用途 | 版本建议 |
|---|---|---|
serde / serde_json |
序列化/反序列化 | 1.0 |
tokio |
异步运行时 | 1.x |
reqwest |
HTTP 客户端 | 0.12 |
axum |
Web 框架(Tokio 生态) | 0.7 |
actix-web |
Web 框架(高性能) | 4.x |
sqlx |
异步数据库(编译期校验 SQL) | 0.8 |
clap |
CLI 参数解析 | 4.x |
tracing |
结构化日志 | 0.1 |
anyhow / thiserror |
错误处理 | 1.x |
chrono |
日期时间 | 0.4 |
regex |
正则表达式 | 1.x |
rand |
随机数 | 0.8 |
12. Web 开发实战:Axum 与 Actix-web
12.1 Axum 入门
# Cargo.toml
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
use axum::{
routing::{get, post},
Json, Router,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct Message {
message: String,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
}
async fn hello() -> Json<Message> {
Json(Message {
message: "Hello, Axum!".to_string(),
})
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
Json(User {
id: 1,
name: payload.name,
email: payload.email,
})
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(hello))
.route("/users", post(create_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Server running on http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
12.2 Axum 中间件与层
use axum::{
middleware::{self, Next},
response::Response,
routing::get,
Router,
};
use std::time::Instant;
use tower_http::cors::CorsLayer;
async fn logging_middleware(
req: axum::http::Request<axum::body::Body>,
next: Next,
) -> Response {
let method = req.method().clone();
let uri = req.uri().clone();
let start = Instant::now();
let response = next.run(req).await;
println!("{} {} - {} - {:?}",
method, uri, response.status(), start.elapsed());
response
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello!" }))
.layer(middleware::map_fn(logging_middleware))
.layer(CorsLayer::permissive());
// ...
}
12.3 Actix-web 对比
use actix_web::{web, App, HttpServer, HttpResponse, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
name: String,
}
async fn greet(info: web::Path<Info>) -> impl Responder {
format!("Hello {}!", info.name)
}
async fn index() -> HttpResponse {
HttpResponse::Ok().body("Welcome!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
.route("/hello/{name}", web::get().to(greet))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Axum vs Actix-web 选择建议:
- Axum:Tokio 官方生态,设计现代,与 Tower 中间件生态深度集成,社区活跃。
- Actix-web:老牌高性能框架,benchmark 表现优秀,API 更紧凑。
13. CLI 工具开发
使用 clap 构建命令行工具:
[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;
/// A simple file word counter
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// Input file path
#[arg(short, long)]
file: String,
/// Show line count instead
#[arg(short, long, default_value_t = false)]
lines: bool,
/// Minimum word length to count
#[arg(short, long, default_value_t = 1)]
min_length: usize,
}
fn main() {
let args = Args::parse();
let content = std::fs::read_to_string(&args.file)
.expect("Failed to read file");
if args.lines {
let count = content.lines().count();
println!("Lines: {}", count);
} else {
let count = content.split_whitespace()
.filter(|w| w.len() >= args.min_length)
.count();
println!("Words (min length {}): {}", args.min_length, count);
}
}
运行:
cargo run -- --file README.md
cargo run -- --file README.md --lines
cargo run -- --file README.md --min-length 5
14. FFI 与跨语言调用
14.1 Rust 调用 C 函数
// 声明外部 C 函数
extern "C" {
fn abs(input: i32) -> i32;
fn sqrt(input: f64) -> f64;
}
fn main() {
unsafe {
println!("abs(-5) = {}", abs(-5));
println!("sqrt(144) = {}", sqrt(144.0));
}
}
14.2 Rust 导出给 C/Python 调用
// lib.rs
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn greet(name: *const std::ffi::c_char) -> *mut std::ffi::c_char {
let c_str = unsafe { std::ffi::CStr::from_ptr(name) };
let greeting = format!("Hello, {}!", c_str.to_str().unwrap_or("World"));
std::ffi::CString::new(greeting).unwrap().into_raw()
}
Cargo.toml 中配置生成动态库:
[lib]
crate-type = ["cdylib", "staticlib"]
14.3 Python 调用 Rust(PyO3)
[dependencies]
pyo3 = { version = "0.22", features = ["extension-module"] }
[lib]
name = "my_rust_lib"
crate-type = ["cdylib"]
use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
#[pymodule]
fn my_rust_lib(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
Python 端使用:
import my_rust_lib
result = my_rust_lib.sum_as_string(5, 20)
print(result) # "25"
15. 学习资源与进阶路径
15.1 推荐学习路线
- 入门:The Rust Programming Language(官方书籍,免费在线阅读)
- 练习:Rustlings(小练习)
- 进阶:Rust by Example
- 深入:The Rustonomicon(unsafe Rust)
- 异步:Asynchronous Programming in Rust
15.2 社区与生态
- crates.io — 官方包仓库
- lib.rs — 替代的 crate 搜索引擎
- This Week in Rust — 每周新闻
- Rust 中文社区 — 中文论坛
15.3 实战项目建议
| 难度 | 项目 | 技能点 |
|---|---|---|
| ⭐ | 命令行计算器 | 基础语法、模式匹配 |
| ⭐⭐ | 文件搜索工具(类似 grep) | 文件 I/O、正则、CLI |
| ⭐⭐⭐ | HTTP 服务器 | TCP、HTTP 协议、多线程 |
| ⭐⭐⭐⭐ | 数据库 ORM | 泛型、trait、宏 |
| ⭐⭐⭐⭐⭐ | 分布式 KV 存储 | 网络、序列化、Raft 协议 |
总结
Rust 的学习曲线确实陡峭,尤其是所有权和生命周期系统——但一旦掌握,你会发现编译器成为了你最可靠的搭档。它在编译期帮你捕获的 bug,比你在运行期花数小时调试的问题要少得多。
核心记住:
- 所有权系统是 Rust 的灵魂,理解它就理解了 Rust 的一半
Result+?是错误处理的标准范式trait+泛型实现零成本抽象async/await+ Tokio 是异步编程的标准方案- Cargo 是你最好的朋友
祝你在 Rust 的世界里写出让编译器微笑的代码。🦀