01-Rust环境搭建与所有权
Rust是Mozilla于2010年启动的系统编程语言,2015年发布1.0版本。核心特性:内存安全、零成本抽象、并发安全。
Rust简介
设计目标
内存安全:无GC的内存安全,编译期检查
并发安全:消除数据竞争,编译期保证
零成本抽象:高级特性无运行时开销
性能:接近C/C++
生产力:现代工具链、包管理
应用场景
系统编程:
操作系统内核(Redox OS)
嵌入式开发
驱动程序
Web后端:
高性能API服务(Actix、Rocket)
WebAssembly(Yew前端框架)
云原生工具
命令行工具:
ripgrep(文本搜索)
fd(文件查找)
bat(cat替代品)
exa(ls替代品)
区块链:
Polkadot、Solana、NEAR
游戏开发:
Bevy游戏引擎
语言特点
优势:
内存安全(无悬空指针、无数据竞争)
无GC(可预测性能)
强大类型系统(零成本抽象)
并发安全(Send/Sync trait)
现代工具链(Cargo、rustfmt、clippy)
劣势:
学习曲线陡峭(所有权、生命周期)
编译速度慢
生态尚不成熟(相比C++/Java)
开发速度慢(严格编译器)
环境安装
rustup(推荐)
# Linux/macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Windows
# 下载:https://rustup.rs
# 需要Visual Studio C++构建工具
# 验证
rustc --version
cargo --version
# 更新
rustup update
# 卸载
rustup self uninstall
工具链管理
# 查看已安装工具链
rustup show
# 安装稳定版
rustup install stable
# 安装nightly版
rustup install nightly
# 切换默认工具链
rustup default stable
rustup default nightly
# 项目级工具链
rustup override set nightly # 当前目录
# 组件管理
rustup component add rustfmt # 格式化工具
rustup component add clippy # lint工具
rustup component add rust-src # 标准库源码
rustup component add rust-analyzer # LSP服务器
# 目标平台
rustup target list
rustup target add x86_64-pc-windows-gnu # 交叉编译
Cargo包管理
基本命令
# 创建项目
cargo new myproject # 二进制项目
cargo new --lib mylib # 库项目
# 构建
cargo build # Debug构建
cargo build --release # Release构建(优化)
# 运行
cargo run
cargo run --release
# 测试
cargo test
# 检查(不生成二进制,快)
cargo check
# 格式化
cargo fmt
# Lint
cargo clippy
# 文档
cargo doc --open
# 清理
cargo clean
# 更新依赖
cargo update
# 发布到crates.io
cargo publish
Cargo.toml配置
[package]
name = "myproject"
version = "0.1.0"
edition = "2021" # Rust版本(2015/2018/2021)
authors = ["Your Name <you@example.com>"]
license = "MIT"
[dependencies]
serde = "1.0" # 最新1.x版本
tokio = { version = "1.0", features = ["full"] } # 启用特性
rand = "0.8"
[dev-dependencies] # 仅测试/bench时用
criterion = "0.4"
[build-dependencies] # 构建脚本依赖
cc = "1.0"
[[bin]] # 多个二进制
name = "myapp"
path = "src/main.rs"
[profile.release] # Release优化
opt-level = 3 # 优化级别
lto = true # 链接时优化
codegen-units = 1 # 单一代码生成单元
开发工具
VS Code
必装插件:
rust-analyzer(官方推荐,替代rls)
CodeLLDB(调试)
crates(依赖版本提示)
配置(settings.json):
{
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.cargo.features": "all",
"editor.formatOnSave": true
}
IntelliJ IDEA
插件:
IntelliJ Rust(JetBrains官方)
特性:
智能补全
重构工具
调试器
Cargo集成
Hello World
基本程序
// main.rs
fn main() {
println!("Hello, World!");
}
# 编译运行
rustc main.rs
./main
# 或使用Cargo
cargo new hello
cd hello
cargo run
项目结构
myproject/
├── Cargo.toml # 项目配置
├── Cargo.lock # 依赖锁定(自动生成)
├── src/
│ ├── main.rs # 二进制入口
│ ├── lib.rs # 库入口(如果是库)
│ └── bin/ # 多个二进制
│ └── another.rs
├── tests/ # 集成测试
│ └── integration_test.rs
├── benches/ # 性能测试
│ └── benchmark.rs
├── examples/ # 示例
│ └── example.rs
└── target/ # 构建产物
├── debug/
└── release/
所有权系统
Rust核心特性,编译期保证内存安全,无需GC。
三条规则
每个值都有一个所有者(owner)
同时只能有一个所有者
所有者离开作用域,值被丢弃(drop)
移动(Move)
// String是堆分配,所有权会转移
let s1 = String::from("hello");
let s2 = s1; // s1的所有权移动到s2
// println!("{}", s1); // ❌ 编译错误:s1已失效
// 整数等基本类型是Copy,不会移动
let x = 5;
let y = x; // x仍有效
println!("{}", x); // ✓ OK
克隆(Clone)
// 深拷贝
let s1 = String::from("hello");
let s2 = s1.clone(); // 显式克隆
println!("{}, {}", s1, s2); // ✓ 两者都有效
Copy trait
// 实现Copy的类型:整数、浮点、布尔、字符、元组(元素都是Copy)
// 不能实现Copy的类型:String、Vec、Box等堆分配
fn main() {
let x: i32 = 5; // Copy
let y = x; // 复制,x仍有效
let s1 = String::from("hello"); // 非Copy
let s2 = s1; // 移动,s1失效
}
函数和所有权
fn main() {
let s = String::from("hello");
takes_ownership(s); // s的所有权移入函数
// println!("{}", s); // ❌ 错误:s已失效
let x = 5;
makes_copy(x); // x是Copy,传的是副本
println!("{}", x); // ✓ x仍有效
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string离开作用域,被drop
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}
返回值和所有权
fn main() {
let s1 = gives_ownership(); // 所有权转移给s1
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2移入,返回值移给s3
// println!("{}", s2); // ❌ s2已失效
println!("{}", s3); // ✓ s3有效
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string // 返回,所有权转移
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回传入的值
}
引用和借用
不获取所有权,只借用。
不可变引用
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 借用
println!("'{}' 的长度是 {}", s1, len); // s1仍有效
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s是引用,不会drop
可变引用
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
借用规则
核心规则:
同一时间,要么一个可变引用,要么任意数量不可变引用
引用必须总是有效(无悬空引用)
// ❌ 错误:同时存在可变和不可变引用
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s; // 错误!
println!("{}, {}, {}", r1, r2, r3);
// ✓ 正确:不可变引用的作用域结束后可变引用
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
// r1和r2不再使用
let r3 = &mut s; // ✓ OK
println!("{}", r3);
// ❌ 错误:多个可变引用
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // 错误!
println!("{}, {}", r1, r2);
// ❌ 错误:悬空引用
fn dangle() -> &String { // 编译错误
let s = String::from("hello");
&s // s离开作用域被drop,引用无效
}
// ✓ 正确:返回所有权
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有权移出
}
切片(Slice)
引用集合的一部分。
字符串切片
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
// 简写
let hello = &s[..5]; // 从开始
let world = &s[6..]; // 到结尾
let whole = &s[..]; // 全部
// 字符串字面量是切片
let s: &str = "Hello, world!"; // 类型:&str
// 函数参数用&str更通用(接受String和&str)
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
let my_string = String::from("hello world");
let word = first_word(&my_string); // ✓ String引用
let my_string_literal = "hello world";
let word = first_word(my_string_literal); // ✓ 字符串字面量
数组切片
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // &[i32]类型
assert_eq!(slice, &[2, 3]);
所有权模式总结
何时使用
场景 |
方式 |
示例 |
|---|---|---|
转移所有权 |
值 |
|
只读访问 |
不可变引用 |
|
修改数据 |
可变引用 |
|
不确定生命周期 |
智能指针 |
|
常见错误
// ❌ 错误1:使用已移动的值
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // 错误
// ✓ 解决:克隆或借用
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}, {}", s1, s2);
// ❌ 错误2:可变引用冲突
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // 错误
println!("{}, {}", r1, r2);
// ✓ 解决:分开作用域
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1离开作用域
let r2 = &mut s;
// ❌ 错误3:悬空引用
let reference_to_nothing = dangle();
fn dangle() -> &String {
let s = String::from("hello");
&s // 错误:返回局部变量的引用
}
// ✓ 解决:返回所有权
fn no_dangle() -> String {
String::from("hello")
}
所有权的优势
内存安全
// C++可能的内存问题
// ❌ 悬空指针
int* ptr;
{
int x = 42;
ptr = &x;
} // x销毁
// *ptr; // 未定义行为
// Rust编译期阻止
// fn main() {
// let r;
// {
// let x = 5;
// r = &x; // 错误:x的生命周期不够长
// }
// println!("{}", r);
// }
并发安全
use std::thread;
fn main() {
let v = vec![1, 2, 3];
// ❌ 错误:不能在多线程间共享可变引用
let handle = thread::spawn(|| {
println!("{:?}", v); // 错误:v可能被主线程drop
});
// drop(v); // 如果允许,会导致数据竞争
handle.join().unwrap();
}
// ✓ 正确:转移所有权
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("{:?}", v); // v的所有权移入闭包
});
// println!("{:?}", v); // 错误:v已移走
handle.join().unwrap();
}
性能
零成本抽象
所有权和借用在编译期检查,运行时无开销。
// 高级抽象
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// 编译后等效于C的循环
// int sum = 0;
// for (int i = 0; i < len; i++) {
// sum += numbers[i];
// }
与C++对比
特性 |
Rust |
C++ |
|---|---|---|
内存安全 |
编译期保证 |
运行时易错 |
数据竞争 |
编译期阻止 |
运行时调试 |
悬空指针 |
不可能 |
容易出现 |
智能指针 |
内置所有权 |
需手动管理 |
性能 |
零成本抽象 |
零成本抽象 |
最佳实践
1. 优先借用
// ❌ 不必要的所有权转移
fn process(s: String) {
println!("{}", s);
}
// ✓ 只读用引用
fn process(s: &str) {
println!("{}", s);
}
2. 返回值优于可变引用
// ❌ 不够清晰
fn append(s: &mut String, suffix: &str) {
s.push_str(suffix);
}
// ✓ 更函数式
fn append(mut s: String, suffix: &str) -> String {
s.push_str(suffix);
s
}
3. 使用方法链
// ✓ 清晰的所有权转移
let result = String::from("hello")
.to_uppercase()
.replace("HELLO", "HI");
4. 避免克隆
// ❌ 不必要的克隆
let s1 = String::from("hello");
let s2 = s1.clone();
process(&s1, &s2);
// ✓ 直接借用
let s1 = String::from("hello");
let s2 = &s1;
process(&s1, s2);
所有权高级应用
零拷贝字符串处理
// ❌ 多次拷贝
fn process_data(data: String) -> String {
let trimmed = data.trim().to_string(); // 拷贝1
let uppercase = trimmed.to_uppercase(); // 拷贝2
uppercase
}
// ✅ 减少拷贝
fn process_data(data: &str) -> String {
data.trim().to_uppercase() // 仅最后结果分配
}
// ✅ 原地修改
fn process_data_inplace(mut data: String) -> String {
data = data.trim().to_string();
data.make_ascii_uppercase(); // 原地修改
data
}
借用检查器工作原理
// 编译器分析示例
fn example() {
let mut s = String::from("hello");
let r1 = &s; // 不可变借用开始
let r2 = &s; // OK:多个不可变借用
println!("{} {}", r1, r2);
// r1、r2作用域结束
let r3 = &mut s; // OK:可变借用开始
r3.push_str(" world");
println!("{}", r3);
// r3作用域结束
} // s离开作用域,drop
// 非词法生命周期(NLL)
fn nll_example() {
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1);
// r1最后使用,编译器判断其生命周期到此结束
let r2 = &mut s; // OK:r1已不再使用
r2.push_str(" world");
}
所有权与集合
// Vec所有权
let v = vec![
String::from("a"),
String::from("b"),
String::from("c"),
];
// ❌ 错误:move发生
let first = v[0]; // String未实现Copy
// ✅ 方案1:克隆
let first = v[0].clone();
// ✅ 方案2:引用
let first = &v[0];
// ✅ 方案3:移除(转移所有权)
let mut v = vec![String::from("a"), String::from("b")];
let first = v.remove(0);
// ✅ 方案4:swap_remove(O(1))
let first = v.swap_remove(0);
// HashMap所有权
use std::collections::HashMap;
let mut map = HashMap::new();
let key = String::from("color");
let value = String::from("blue");
map.insert(key, value);
// key、value所有权已转移,不可再用
// ✅ 使用引用作为键
let text = String::from("hello");
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert(&text, 1); // text仍可用
内存布局对比
// 栈分配(Copy类型)
let x: i32 = 5; // 栈:4字节
let y = x; // 复制4字节
// 堆分配(String)
let s1 = String::from("hello");
// 栈:ptr(8字节) + len(8字节) + capacity(8字节) = 24字节
// 堆:"hello"数据
let s2 = s1; // 仅复制栈上24字节,堆数据不复制
// Box<T>
let b = Box::new(100);
// 栈:指针8字节
// 堆:i32值4字节
// 对比
struct Point { x: i32, y: i32 } // 栈8字节
struct Data { v: Vec<i32> } // 栈24字节 + 堆数据
Cow(写时克隆)
use std::borrow::Cow;
fn process(input: &str) -> Cow<str> {
if input.contains("bad") {
// 需要修改,分配新String
Cow::Owned(input.replace("bad", "good"))
} else {
// 无需修改,借用原字符串
Cow::Borrowed(input)
}
}
let s1 = "hello world";
let result = process(s1); // Cow::Borrowed,零拷贝
let s2 = "bad word";
let result = process(s2); // Cow::Owned,仅在需要时分配
所有权转移模式
// 构建器模式(消耗self)
struct Config {
host: String,
port: u16,
}
impl Config {
fn new() -> Self {
Config {
host: String::from("localhost"),
port: 8080,
}
}
fn host(mut self, host: String) -> Self {
self.host = host;
self // 返回所有权
}
fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
}
// 链式调用
let config = Config::new()
.host(String::from("example.com"))
.port(3000);
// Option的take方法
let mut opt = Some(String::from("hello"));
let s = opt.take(); // 取出值,opt变为None
assert_eq!(opt, None);
assert_eq!(s, Some(String::from("hello")));
// mem::replace
use std::mem;
let mut s = String::from("hello");
let old = mem::replace(&mut s, String::from("world"));
// s现在是"world",old是"hello"
// mem::take(默认值替换)
let mut opt = Some(vec![1, 2, 3]);
let v = mem::take(&mut opt); // opt变为None,v是Some(vec![1,2,3])
核心: 所有权系统通过编译期检查保证内存安全和线程安全,消除悬空指针、数据竞争等问题。