行业资讯

Rust学习文档(二)

发布时间:2026/7/25 23:30:40
Rust学习文档(二) 目录02 所有权与借用2.1 什么是所有权OwnershipRust 为什么需要所有权所有权三条铁律作用域ScopeString 类型与栈/堆移动Move—— 核心机制克隆Clone—— 深拷贝Copy Trait —— 栈上拷贝函数传参与所有权转移函数返回值与所有权转移2.2 引用与借用References Borrowing问题所有权转移导致使用不便解决方案引用Reference可变引用Mutable Reference可变引用的限制 —— 同一时刻只能有一个不可变引用 vs 可变引用的互斥悬垂引用Dangling References—— Rust 在编译期防止引用规则总结2.3 切片Slices字符串切片字符串切片的危险性 —— UTF-8 边界函数参数中的字符串切片数组切片切片在内存中的表示2.4 所有权决策树速查学习目标检验02 所有权与借用2.1 什么是所有权OwnershipRust 为什么需要所有权语言内存管理方式问题C/C手动malloc/free忘记释放 → 内存泄漏重复释放 → 悬垂指针Java/Go垃圾回收GC运行时开销GC 暂停影响延迟Rust所有权系统编译期检查零运行时开销编译器保证安全所有权三条铁律1. Rust 中每一个值都有一个被称为其 所有者owner的变量2. 一次只能有一个所有者3. 当所有者离开作用域时这个值将被丢弃drop作用域Scope{ let s String::from(hello); // s 从这里开始有效 // 使用 s } // 作用域结束s 被丢弃drop内存自动释放String 类型与栈/堆let s1 String::from(hello); // 堆上分配可变长度 let s2 hello; // 栈上str 切片类型不可变 // String 由三部分组成存储在栈上 // - 指向堆数据的指针ptr // - 长度len // - 容量capacity移动Move—— 核心机制let s1 String::from(hello); let s2 s1; // s1 的所有权移动到 s2 // println!({s1}); // 编译错误s1 已失效 println!({s2}); // OK // 原因如果 s1 和 s2 都指向同一块堆内存 // 作用域结束时两者都会尝试释放 → 双重释放 → 内存不安全 // Rust 选择在移动时使原变量失效来避免此问题克隆Clone—— 深拷贝let s1 String::from(hello); let s2 s1.clone(); // 深拷贝堆数据独立 println!(s1 {s1}, s2 {s2}); // 两者都有效Copy Trait —— 栈上拷贝let x 5; let y x; // i32 实现了 Copy trait直接复制值栈拷贝 println!(x {x}, y {y}); // 两者都有效 // 实现 Copy trait 的类型全部存储在栈上 // - 所有整数类型i8, u32, usize 等 // - 所有浮点类型f32, f64 // - 布尔 bool // - 字符 char // - 元组仅当所有元素都是 Copy 时如 (i32, f64)函数传参与所有权转移fn main() { let s String::from(hello); takes_ownership(s); // s 已移动不可再使用 let x 5; makes_copy(x); // x 仍可用i32 是 Copy 类型 } fn takes_ownership(some_string: String) { println!({some_string}); } // some_string 离开作用域drop 被调用内存释放 fn makes_copy(some_integer: i32) { println!({some_integer}); } // some_integer 离开作用域无特殊操作函数返回值与所有权转移fn gives_ownership() - String { let some_string String::from(yours); some_string // 所有权移动给调用者 } fn takes_and_gives_back(a_string: String) - String { a_string // 原样返回所有权 } fn main() { let s1 gives_ownership(); let s2 String::from(hello); let s3 takes_and_gives_back(s2); // s2 已移动到函数内不可再用s3 拿到了返回的所有权 }2.2 引用与借用References Borrowing问题所有权转移导致使用不便fn calculate_length(s: String) - (String, usize) { let length s.len(); (s, length) // 必须同时返回所有权才能继续使用 } fn main() { let s1 String::from(hello); let (s2, len) calculate_length(s1); println!({} 的长度是 {}, s2, len); }解决方案引用Referencefn calculate_length(s: String) - usize { // 借用 String不获取所有权 s.len() } fn main() { let s1 String::from(hello); let len calculate_length(s1); // 创建指向 s1 的引用 println!({} 的长度是 {}, s1, len); // s1 仍然有效 }术语含义借用Borrowing创建一个引用的行为借用者拿到引用的函数/变量所有者原始变量始终拥有所有权可变引用Mutable Referencefn main() { let mut s String::from(hello); change(mut s); println!({s}); // hello, world } fn change(s: mut String) { s.push_str(, world); }可变引用的限制 —— 同一时刻只能有一个let mut s String::from(hello); let r1 mut s; // let r2 mut s; // 编译错误不能同时有两个可变引用 // println!({r1} {r2}); // 好处在编译期防止数据竞争data race // 数据竞争的三个条件Rust 在编译期全部排除 // 1. 两个或更多指针同时访问同一数据 // 2. 至少有一个指针在写入 // 3. 没有同步机制不可变引用 vs 可变引用的互斥let mut s String::from(hello); let r1 s; // 不可变引用 OK let r2 s; // 多个不可变引用 OK println!({r1} and {r2}); // r1, r2 的最后一次使用结束于此 let r3 mut s; // 可变引用 OKr1/r2 已不再使用 println!({r3}); // 关键NLLNon-Lexical Lifetimes非词法生命周期 // 作用域结束 ≠ 引用失效引用在最后一次使用后就失效了悬垂引用Dangling References—— Rust 在编译期防止// 这段代码在 Rust 中无法编译其他语言允许是常见 bug 源 fn dangle() - String { let s String::from(hello); s // 返回指向局部变量的引用 } // s 在此被 drop引用变成悬垂指针 // 编译器报错missing lifetime specifier // 正确写法直接返回所有权 fn no_dangle() - String { let s String::from(hello); s // 所有权移动出去不会有悬垂引用 }引用规则总结1. 在任意给定时间要么只能有一个可变引用要么只能有多个不可变引用2. 引用必须始终有效不能悬垂2.3 切片Slices字符串切片let s String::from(hello world); let hello s[0..5]; // hello let world s[6..11]; // world let whole s[..]; // 等价于 s[0..s.len()] let half s[..5]; // 等价于 s[0..5] // 切片类型str字符串字面量也是 str let s: str hello; // 类型是 str不可变字符串切片的危险性 —— UTF-8 边界let s String::from(你好); // let h s[0..1]; // 运行时 panic你 占 3 字节不是 1 字节 // 正确做法 let h s[0..3]; // 你3 字节函数参数中的字符串切片// 接受 String仅持有 String 引用时可用 fn first_word(s: String) - str { ... } // 接受 strString 和字符串字面量都能用更通用 fn first_word(s: str) - str { ... } fn main() { let s String::from(hello world); let word first_word(s); // 传入 String自动解引用为 str let word2 first_word(literal); // 传入 str 字面量也 OK }最佳实践函数参数优先使用str而非String更通用。数组切片let a [1, 2, 3, 4, 5]; let slice a[1..3]; // [i32] 类型值为 [2, 3]切片在内存中的表示String:┌─────────┬─────┬───────────┐│ ptr │ len │ capacity │ ← 栈上├─────────┼─────┼───────────┤│ │ │ ││ ┌───────────────────┐ ││ │ h e l l o \0 │ │ ← 堆上│ └───────────────────┘ │└─────────┼─────┼───────────┘str 切片┌─────┬─────┐│ ptr │ len │ ← 栈上没有 capacity└─────┴─────┘│└──→ 指向 String 堆数据的一部分2.4 所有权决策树速查需要传递数据给函数吗├── 函数需要修改数据│ └── 是 → 传 mut T可变引用└── 函数只需要读取├── 数据较小Copy 类型→ 传值自动复制└── 数据较大 → 传 T不可变引用需要从函数返回数据吗├── 在函数内创建的数据 → 直接返回所有权转移└── 调用方仍需持有 → 先 clone 再传不确定该用什么→ 默认用引用T编译器会告诉你何时需要所有权学习目标检验能清晰解释所有权三条铁律能区分移动和复制知道哪些类型是 Copy能解释为什么 Rust 要禁止悬垂引用能写出正确的可变引用和不可变引用组合理解 NLL非词法生命周期的含义能正确使用字符串切片str作为函数参数理解Stringvsstr的区别