Rust中的所有权与借用
Rust 所有权与借用知识总结
堆与栈的基础概念
栈(Stack)
存储方式:后进先出(LIFO),按顺序存储和释放数据。 数据特点:存储固定大小的已知数据(如基本类型、指针等)。 性能:分配和释放速度快(仅操作栈顶),无需动态内存管理。 示例:函数调用时参数、局部变量入栈,函数结束自动出栈。
堆(Heap)
存储方式:动态分配,需手动请求和释放内存空间。 数据特点:存储大小未知或可能变化的数据(如 String、集合等)。 性能:分配速度较慢(需操作系统查找可用空间),需跟踪内存生命周期。 示例:String::from("hello") 在堆上分配内存,栈中存储指向堆的指针。
一、所有权的核心规则
-
核心规则
• 每个值有且仅有一个所有者(变量)。 • 当所有者离开作用域时,值会被自动释放(调用drop
)。 • 赋值或传参时,默认会转移所有权(称为 移动)。 -
示例说明
let s1 = String::from("hello"); let s2 = s1; // s1 的所有权被移动到 s2,s1 不再有效 // println!("{}", s1); // 编译错误:s1 已失效
二、所有权转移与复制
-
移动(Move)
• 默认行为:赋值或传参会转移所有权,原变量失效。 • 适用于堆分配的数据(如String
、Vec
)。 -
复制(Copy)
• 基本类型(如i32
、bool
)实现Copy
trait,赋值时直接复制值。 • 栈上数据直接复制,无所有权转移。let x = 5; let y = x; // 复制值,x 和 y 均有效
-
显式深拷贝(Clone)
• 使用clone
方法复制堆数据:let s1 = String::from("hello"); let s2 = s1.clone(); // 深拷贝,s1 和 s2 均有效
三、函数与所有权
- 传参与返回值
• 传参时所有权可能被转移:• 函数返回时转移所有权:fn take_ownership(s: String) { /* ... */ } let s = String::from("hello"); take_ownership(s); // s 的所有权转移给函数 // s 不再可用
fn create_string() -> String { String::from("new") } let s = create_string(); // 所有权转移到 s
四、引用与借用机制
-
不可变引用(&T)
• 允许只读访问数据,不转移所有权。 • 同一作用域内可存在多个不可变引用:let s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{} {}", r1, r2); // 允许
-
可变引用(&mut T)
• 允许修改数据,同一时刻仅允许一个可变引用。 • 不可与不可变引用共存:let mut s = String::from("hello"); let r1 = &mut s; // let r2 = &mut s; // 编译错误:同一作用域只能有一个可变引用 // let r3 = &s; // 编译错误:不可变与可变引用共存
-
借用规则(借用检查器)
• 规则一:任意时刻,要么只能有一个可变引用,要么有多个不可变引用。 • 规则二:所有引用必须始终有效(避免悬垂引用)。
五、避免悬垂引用
- 生命周期保证
Rust 编译器确保引用不会指向已释放的内存:// 错误示例:返回局部变量的引用 fn dangle() -> &String { let s = String::from("hello"); &s // 编译错误:s 离开作用域后被释放 } // 正确做法:返回所有权 fn no_dangle() -> String { String::from("hello") }
六、引用作用域优化(NLL)
- Non-Lexical Lifetimes (NLL)
• 引用作用域在最后一次使用时结束(而非花括号结束)。 • 允许更灵活的借用:let mut s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{} {}", r1, r2); // r1、r2 作用域结束 let r3 = &mut s; // 允许,因为 r1 和 r2 已不再使用
关键总结
特性 | 所有权 | 借用 |
---|---|---|
核心机制 | 通过编译时检查确保内存安全,避免 GC 和手动管理 | 通过引用(&T /&mut T )临时访问数据,不转移所有权 |
数据竞争防护 | 单所有者机制避免多线程数据竞争 | 同一时刻只能有一个可变引用或多个不可变引用,防止读写冲突 |
性能影响 | 无运行时开销 | 引用为指针操作,性能等同于 C/C++ |
典型场景 | 管理堆数据(如 String 、Vec ) | 函数传参、避免深拷贝、临时修改数据 |
通过所有权和借用机制,Rust 在编译期解决了内存安全问题,同时兼顾性能与灵活性。