Rust 所有权系统
Rust 的所有权系统是其最核心、最独特的特性,在编译期保证内存安全,无需垃圾回收器。
目录
所有权基础
三大规则
- 每个值都有一个所有者(owner)
- 同一时刻只能有一个所有者
- 所有者离开作用域时,值被丢弃(drop)
1. 栈与堆
fn main() {
// 栈上分配(已知固定大小)
let x = 5; // i32,栈上
let y = true; // bool,栈上
let z = 3.14; // f64,栈上
// 堆上分配(大小可变或运行时确定)
let s1 = String::from("hello"); // String,堆上
let v1 = vec![1, 2, 3]; // Vec,堆上
let b1 = Box::new(5); // Box,堆上
println!("栈: {}, {}, {}", x, y, z);
println!("堆: {}, {:?}, {}", s1, v1, b1);
}
2. 基本所有权示例
fn main() {
{
let s = String::from("hello"); // s 进入作用域
println!("{}", s); // s 有效
} // s 离开作用域,自动调用 drop,内存释放
// println!("{}", s); // ❌ 错误:s 已失效
}
3. 所有权转移(Move)
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2
println!("{}", s2); // ✅ 正确
// println!("{}", s1); // ❌ 错误:s1 已失效
}
为什么会移动?
// 内部机制(简化版)
struct String {
ptr: *mut u8, // 指向堆内存的指针
len: usize, // 长度
capacity: usize, // 容量
}
// 当执行 let s2 = s1 时:
// 1. 复制栈上的数据(ptr、len、capacity)
// 2. s1 失效,防止双重释放(double free)
4. 克隆(Clone)- 深拷贝
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝,堆内存也被复制
println!("s1 = {}, s2 = {}", s1, s2); // ✅ 两个都有效
}
5. 拷贝(Copy)- 栈上数据
fn main() {
// 实现了 Copy trait 的类型会自动复制
let x = 5;
let y = x; // 复制(不是移动)
println!("x = {}, y = {}", x, y); // ✅ 两个都有效
// Copy trait 的类型:
// - 所有整数类型:i32, u64, etc.
// - 布尔类型:bool
// - 浮点类型:f64, f32
// - 字符类型:char
// - 元组(如果所有元素都是 Copy)
// - 数组(如果元素是 Copy)
}
Copy vs Clone
// Copy - 隐式、廉价、栈上
let x = 5;
let y = x; // 自动复制
// Clone - 显式、可能昂贵、可能涉及堆
let s1 = String::from("hello");
let s2 = s1.clone(); // 显式调用
移动语义
1. 移动的本质
fn main() {
let s1 = String::from("hello");
// 移动发生在:
let s2 = s1; // 1. 赋值
take_ownership(s2); // 2. 传参
// let s3 = return_string(); // 3. 返回值
// s1 和 s2 都已失效
}
fn take_ownership(s: String) {
println!("{}", s);
} // s 在这里被 drop
2. 部分移动
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
let person = Person {
name: String::from("Alice"),
age: 25,
};
// 部分移动
let name = person.name; // name 被移动
let age = person.age; // age 被复制(u32 是 Copy)
// println!("{:?}", person); // ❌ 错误:person.name 已移动
println!("age: {}", age); // ✅ 正确
println!("name: {}", name); // ✅ 正确
}
3. 移动与集合
fn main() {
let v = vec![
String::from("hello"),
String::from("world"),
];
// 索引访问会尝试移动
// let s = v[0]; // ❌ 错误:不能移出索引
// 解决方案1:克隆
let s1 = v[0].clone();
// 解决方案2:借用
let s2 = &v[0];
// 解决方案3:获取所有权(消耗 Vec)
let mut v = v;
let s3 = v.remove(0);
println!("{}, {}, {}", s1, s2, s3);
}
4. 移动与闭包
fn main() {
let data = String::from("hello");
// 闭包捕获所有权
let closure = || {
println!("{}", data); // data 被借用
};
closure();
println!("{}", data); // ✅ data 仍然有效
// 使用 move 强制移动
let data2 = String::from("world");
let closure2 = move || {
println!("{}", data2); // data2 被移动
};
closure2();
// println!("{}", data2); // ❌ 错误:data2 已移动
}
借用与引用
1. 不可变借用(&T)
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 借用 s1
println!("'{}' 的长度是 {}", s1, len); // s1 仍然有效
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s 离开作用域,但不会 drop,因为没有所有权
借用规则图示
所有者: s1 ──┐
│
├──> 借用1: &s1
├──> 借用2: &s1
└──> 借用3: &s1
可以同时存在多个不可变借用
2. 可变借用(&mut T)
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
fn change(s: &mut String) {
s.push_str(", world");
}
3. 借用规则
核心规则:
- ✅ 可以有任意数量的不可变借用
- ✅ 可以有一个可变借用
- ❌ 不可变借用和可变借用不能同时存在
fn main() {
let mut s = String::from("hello");
// ✅ 多个不可变借用
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1 和 r2 在这之后不再使用
// ✅ 一个可变借用
let r3 = &mut s;
r3.push_str(" world");
println!("{}", r3);
}
4. 借用作用域(NLL - Non-Lexical Lifetimes)
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1 和 r2 的作用域在这里结束(最后一次使用)
let r3 = &mut s; // ✅ 正确:r1 和 r2 已不再使用
r3.push_str(" world");
println!("{}", r3);
}
5. 悬垂引用(Dangling Reference)
// ❌ 错误示例
fn dangle() -> &String {
let s = String::from("hello");
&s // 错误:返回对局部变量的引用
} // s 被 drop,引用指向无效内存
// ✅ 正确方式1:返回所有权
fn no_dangle1() -> String {
let s = String::from("hello");
s // 移动所有权
}
// ✅ 正确方式2:使用生命周期参数
fn no_dangle2(s: &String) -> &String {
s // 返回传入的引用
}
6. 引用与解引用
fn main() {
let x = 5;
let y = &x; // y 是 x 的引用
// 解引用
assert_eq!(5, x);
assert_eq!(5, *y); // *y 解引用,获取值
// 自动解引用
let s = String::from("hello");
let r = &s;
println!("长度: {}", r.len()); // 自动解引用调用方法
}
生命周期
1. 生命周期基础
生命周期是引用有效的作用域。
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
// println!("{}", r); // ❌ 错误:x 已失效
} // ---------+
2. 函数中的生命周期标注
// 生命周期标注语法
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("最长的字符串是 {}", result);
}
生命周期标注含义
// 'a 表示返回值的生命周期至少与参数中较短的那个一样长
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// 返回值的生命周期 = min(x 的生命周期, y 的生命周期)
}
3. 生命周期省略规则
编译器可以自动推断生命周期的情况:
// 规则1:每个引用参数都有自己的生命周期
fn foo(x: &str) -> &str { x }
// 等价于
fn foo<'a>(x: &'a str) -> &'a str { x }
// 规则2:如果只有一个输入生命周期,赋给所有输出
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap()
}
// 规则3:如果有 &self 或 &mut self,生命周期赋给所有输出
impl MyStruct {
fn get_data(&self) -> &str {
&self.data
}
}
struct MyStruct {
data: String,
}
4. 结构体中的生命周期
// 结构体包含引用必须标注生命周期
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("注意: {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("摘录: {}", excerpt.part);
}
5. 静态生命周期('static)
// 'static 生命周期持续整个程序
let s: &'static str = "硬编码的字符串";
// 字符串字面量都是 'static
fn get_static_str() -> &'static str {
"hello"
}
// 有时需要显式标注
static GLOBAL: &str = "全局变量";
6. 多个生命周期参数
// 不同参数可以有不同生命周期
fn complex<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
println!("y = {}", y);
x // 只返回 x
}
fn main() {
let string1 = String::from("hello");
{
let string2 = String::from("world");
let result = complex(&string1, &string2);
println!("{}", result);
} // string2 可以在这里释放
}
智能指针
1. Box - 堆分配
fn main() {
// 将值放在堆上
let b = Box::new(5);
println!("b = {}", b);
// 递归类型必须使用 Box
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{:?}", list);
}
2. Rc - 引用计数
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("hello"));
println!("引用计数: {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a);
println!("引用计数: {}", Rc::strong_count(&a)); // 2
{
let c = Rc::clone(&a);
println!("引用计数: {}", Rc::strong_count(&a)); // 3
}
println!("引用计数: {}", Rc::strong_count(&a)); // 2
println!("值: {}", a);
}
3. Arc - 原子引用计数(线程安全)
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];
for i in 0..3 {
let data = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("线程 {}: {:?}", i, data);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
4. RefCell - 内部可变性
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
// 运行时借用检查
{
let mut borrow = data.borrow_mut();
*borrow += 1;
} // 可变借用在这里结束
println!("值: {}", data.borrow()); // 6
}
// 结合 Rc 实现共享可变性
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
next: Option<Rc<RefCell<Node>>>,
}
fn main() {
let node1 = Rc::new(RefCell::new(Node {
value: 1,
next: None,
}));
let node2 = Rc::new(RefCell::new(Node {
value: 2,
next: Some(Rc::clone(&node1)),
}));
println!("{:?}", node2);
}
5. Mutex 和 RwLock
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!("结果: {}", *counter.lock().unwrap());
}
所有权与函数
1. 参数传递
fn main() {
let s = String::from("hello");
// 移动所有权
takes_ownership(s);
// println!("{}", s); // ❌ 错误:s 已失效
let x = 5;
// 复制
makes_copy(x);
println!("{}", x); // ✅ 正确:x 仍然有效
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 被 drop
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // 无特殊操作
2. 返回值与所有权
fn main() {
let s1 = gives_ownership();
println!("{}", s1);
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
println!("{}", s3);
// println!("{}", s2); // ❌ 错误:s2 已移动
}
fn gives_ownership() -> String {
let some_string = String::from("yours");
some_string // 移动给调用者
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 移动给调用者
}
3. 返回多个值
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("'{}' 的长度是 {}", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length) // 返回元组
}
4. 使用引用避免所有权转移
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("'{}' 的长度是 {}", s1, len); // s1 仍然有效
}
fn calculate_length(s: &String) -> usize {
s.len()
}
所有权与结构体
1. 所有权结构体
struct User {
username: String, // 拥有所有权
email: String, // 拥有所有权
sign_in_count: u64, // Copy 类型
active: bool, // Copy 类型
}
fn main() {
let user1 = User {
username: String::from("user1"),
email: String::from("user1@example.com"),
sign_in_count: 1,
active: true,
};
// 部分移动
let username = user1.username;
// println!("{:?}", user1); // ❌ 错误:username 已移动
println!("{}", username); // ✅ 正确
}
2. 引用结构体
// 需要生命周期标注
struct UserRef<'a> {
username: &'a str,
email: &'a str,
}
fn main() {
let username = String::from("user1");
let email = String::from("user1@example.com");
let user = UserRef {
username: &username,
email: &email,
};
println!("{}, {}", user.username, user.email);
}
3. 方法中的所有权
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 不可变借用 self
fn area(&self) -> u32 {
self.width * self.height
}
// 可变借用 self
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
// 获取所有权(消费 self)
fn into_tuple(self) -> (u32, u32) {
(self.width, self.height)
}
}
fn main() {
let mut rect = Rectangle { width: 30, height: 50 };
println!("面积: {}", rect.area());
rect.scale(2);
println!("缩放后面积: {}", rect.area());
let (w, h) = rect.into_tuple();
println!("宽: {}, 高: {}", w, h);
// println!("{}", rect.area()); // ❌ 错误:rect 已被消费
}
实战模式
1. 构建器模式(Builder Pattern)
struct User {
username: String,
email: String,
age: u32,
}
struct UserBuilder {
username: Option<String>,
email: Option<String>,
age: Option<u32>,
}
impl UserBuilder {
fn new() -> Self {
Self {
username: None,
email: None,
age: None,
}
}
// 接受所有权并返回自身
fn username(mut self, username: String) -> Self {
self.username = Some(username);
self
}
fn email(mut self, email: String) -> Self {
self.email = Some(email);
self
}
fn age(mut self, age: u32) -> Self {
self.age = Some(age);
self
}
fn build(self) -> User {
User {
username: self.username.expect("username required"),
email: self.email.expect("email required"),
age: self.age.unwrap_or(0),
}
}
}
fn main() {
let user = UserBuilder::new()
.username(String::from("alice"))
.email(String::from("alice@example.com"))
.age(25)
.build();
println!("用户: {}, {}, {}", user.username, user.email, user.age);
}
2. 缓存模式(Cache-Aside)
use std::collections::HashMap;
struct Cache {
data: HashMap<String, String>,
}
impl Cache {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
// 借用 self,不转移所有权
fn get(&self, key: &str) -> Option<&String> {
self.data.get(key)
}
// 可变借用 self
fn set(&mut self, key: String, value: String) {
self.data.insert(key, value);
}
// 接受所有权
fn insert_owned(&mut self, key: String, value: String) {
self.data.insert(key, value);
}
// 借用参数
fn insert_borrowed(&mut self, key: &str, value: &str) {
self.data.insert(key.to_string(), value.to_string());
}
}
fn main() {
let mut cache = Cache::new();
cache.set(String::from("key1"), String::from("value1"));
cache.insert_borrowed("key2", "value2");
if let Some(value) = cache.get("key1") {
println!("找到: {}", value);
}
}
3. 资源管理(RAII)
use std::fs::File;
use std::io::Write;
struct FileWriter {
file: File,
}
impl FileWriter {
fn new(path: &str) -> std::io::Result<Self> {
let file = File::create(path)?;
Ok(Self { file })
}
fn write(&mut self, data: &str) -> std::io::Result<()> {
self.file.write_all(data.as_bytes())
}
}
impl Drop for FileWriter {
fn drop(&mut self) {
println!("文件被关闭");
}
}
fn main() -> std::io::Result<()> {
{
let mut writer = FileWriter::new("test.txt")?;
writer.write("Hello, World!")?;
} // writer 在这里自动关闭文件
Ok(())
}
4. 本项目实际应用
服务层所有权管理
// src/services/user_service.rs
pub struct UserService {
db: MySqlPool, // 拥有连接池
cache: RedisCache, // 拥有缓存客户端
}
impl UserService {
// 获取所有权
pub fn new(db: MySqlPool, cache: RedisCache) -> Self {
Self { db, cache }
}
// 借用 self,不转移所有权
pub async fn create_user(&self, payload: CreateUserRequest) -> Result<User> {
// payload 的所有权被转移
let password_hash = format!("hashed_{}", payload.password);
let result = sqlx::query!(
"INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)",
payload.username,
payload.email,
password_hash
)
.execute(&self.db) // 借用 db
.await?;
let user_id = result.0 as i64;
// 查询创建的用户
let user = sqlx::query_as!(
User,
"SELECT id, username, email, password_hash, created_at, updated_at FROM users WHERE id = ?",
user_id
)
.fetch_one(&self.db)
.await?;
// 缓存(借用字段)
let cache_key = format!("user:{}", user.id);
let user_json = serde_json::to_string(&user)?;
self.cache.set(&cache_key, &user_json, 3600).await?;
Ok(user) // 移动所有权给调用者
}
// 返回 Option,可能没有所有权
pub async fn get_user(&self, user_id: i64) -> Result<Option<User>> {
let cache_key = format!("user:{}", user_id);
// 先查缓存
if let Ok(Some(cached)) = self.cache.get_typed::<User>(&cache_key).await {
return Ok(Some(cached)); // 返回缓存的值
}
// 查数据库
let user = sqlx::query_as!(
User,
"SELECT id, username, email, password_hash, created_at, updated_at FROM users WHERE id = ?",
user_id
)
.fetch_optional(&self.db)
.await?;
// 写入缓存
if let Some(ref u) = user {
let user_json = serde_json::to_string(u)?;
let _ = self.cache.set(&cache_key, &user_json, 3600).await;
}
Ok(user)
}
}
控制器所有权管理
// src/controllers/user_controller.rs
pub async fn create_user(
State(state): State<AppState>, // 克隆 AppState
Json(payload): Json<CreateUserRequest>, // payload 所有权转移
) -> Result<Json<UserResponse>, (StatusCode, String)> {
// 创建 UserService(获取所有权)
let service = UserService::new(state.db, state.cache);
// 调用服务(payload 所有权转移)
match service.create_user(payload).await {
Ok(user) => {
let response = UserResponse {
id: user.id,
username: user.username,
email: user.email,
created_at: user.created_at,
updated_at: user.updated_at,
};
Ok(Json(response)) // 移动所有权
}
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("创建用户失败: {}", e),
)),
}
}
应用状态克隆
// src/config/app_state.rs
#[derive(Clone)]
pub struct AppState {
pub db: MySqlPool, // MySqlPool 实现了 Clone(内部是 Arc)
pub cache: RedisCache, // RedisCache 实现了 Clone
}
impl AppState {
pub fn new(db: MySqlPool, cache: RedisCache) -> Self {
Self { db, cache }
}
}
// 在路由中使用
let app = Router::new()
.route("/api/users", post(create_user))
.with_state(state); // state 被移动到路由
常见陷阱与解决方案
1. 忘记所有权已转移
// ❌ 错误
fn main() {
let s = String::from("hello");
let s2 = s;
println!("{}", s); // 编译错误:s 已移动
}
// ✅ 解决方案1:克隆
fn main() {
let s = String::from("hello");
let s2 = s.clone();
println!("{} and {}", s, s2);
}
// ✅ 解决方案2:使用引用
fn main() {
let s = String::from("hello");
let s2 = &s;
println!("{} and {}", s, s2);
}
2. 借用规则冲突
// ❌ 错误
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s; // 错误:不可变借用存在时不能可变借用
println!("{}, {}", r1, r2);
}
// ✅ 解决方案:确保借用不重叠
fn main() {
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1);
// r1 不再使用
let r2 = &mut s;
r2.push_str(" world");
println!("{}", r2);
}
3. 返回局部变量的引用
// ❌ 错误
fn dangle() -> &String {
let s = String::from("hello");
&s // 错误:s 被 drop
}
// ✅ 解决方案:返回所有权
fn no_dangle() -> String {
let s = String::from("hello");
s
}
4. 循环引用
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
next: Option<Rc<RefCell<Node>>>,
}
// ❌ 可能导致内存泄漏
fn create_cycle() {
let a = Rc::new(RefCell::new(Node { next: None }));
let b = Rc::new(RefCell::new(Node { next: Some(Rc::clone(&a)) }));
a.borrow_mut().next = Some(Rc::clone(&b));
// a -> b -> a 循环引用
}
// ✅ 解决方案:使用 Weak
use std::rc::Weak;
#[derive(Debug)]
struct NodeWeak {
next: Option<Rc<RefCell<NodeWeak>>>,
prev: Option<Weak<RefCell<NodeWeak>>>, // 使用 Weak 打破循环
}
总结
核心概念速查
| 概念 | 说明 | 示例 |
|---|---|---|
| 所有权 | 每个值有唯一所有者 | let s = String::from("hello"); |
| 移动 | 转移所有权 | let s2 = s; |
| 克隆 | 深拷贝 | let s2 = s.clone(); |
| 拷贝 | 栈上自动复制 | let y = x; (i32) |
| 借用 | 引用,不获取所有权 | &s / &mut s |
| 生命周期 | 引用有效期 | 'a |
借用规则
1. 同一时刻,要么有一个可变借用,要么有任意多个不可变借用
2. 引用必须总是有效的(不能悬垂)
智能指针选择
| 类型 | 用途 | 线程安全 |
|---|---|---|
Box<T> | 堆分配 | ❌ |
Rc<T> | 共享所有权 | ❌ |
Arc<T> | 共享所有权 | ✅ |
RefCell<T> | 内部可变性 | ❌ |
Mutex<T> | 互斥锁 | ✅ |
何时使用什么?
// 需要所有权:参数类型为 T
fn take_ownership(s: String) { }
// 只读访问:参数类型为 &T
fn borrow(s: &String) { }
// 修改数据:参数类型为 &mut T
fn borrow_mut(s: &mut String) { }
// 返回引用:需要生命周期标注
fn return_ref<'a>(s: &'a String) -> &'a str { }
// 共享所有权:使用 Rc/Arc
let shared = Rc::new(data);
本项目最佳实践
- ✅ 服务层持有连接池和缓存(所有权)
- ✅ 方法使用
&self借用(避免所有权转移) - ✅ DTO 通过移动传递(CreateUserRequest)
- ✅ 连接池使用 Clone(内部 Arc)
- ✅ 异步函数返回
Result<T>(明确所有权)
延伸阅读
- The Rust Book - Ownership
- Rust Nomicon - Ownership
- Visualizing Memory Layout
- 本项目代码:
src/services/,src/models/,src/controllers/