| name | rust-ownership-patterns |
| description | Rust 所有權、借用與生命週期模式技能。涵蓋 ownership 轉移、borrowing rules、 lifetime annotations、常見編譯器錯誤 E0382/E0502/E0597/E0106 修復策略、 Clone vs Copy semantics、Rc/Arc 共享所有權、Cow 寫時複製、interior mutability (Cell/RefCell/Mutex)、self-referential structs 問題與解法。 觸發關鍵詞:ownership, borrow checker, lifetime, E0382, moved value, cannot borrow, lifetime elision, 'a annotation, Rc, Arc, RefCell, Copy, Clone
|
Rust 所有權模式 (rust-ownership-patterns)
適用場景
- 遇到 borrow checker 編譯錯誤需要修復
- 設計資料結構時需考慮所有權策略
- 理解 lifetime annotation 何時必要
- 選擇 Clone/Rc/Arc/Cow 的適當時機
- 處理 interior mutability 需求
核心知識
所有權三大規則
- Rust 中每個值都有且只有一個 owner
- 當 owner 離開 scope,值會被 drop
- 同一時間只能有一個 mutable reference 或多個 immutable references
Borrowing 規則
&T:immutable borrow(可同時多個)
&mut T:mutable borrow(同一時間只能一個)
- borrow 的生命週期不得超過 owner
Lifetime Elision Rules
編譯器自動推斷 lifetime 的三條規則:
- 每個引用參數得到各自的 lifetime
- 若只有一個輸入 lifetime,它被指派給所有輸出
- 若有
&self 或 &mut self,self 的 lifetime 被指派給所有輸出
Copy vs Clone 語意
- Copy:按位元複製,隱式發生,僅限 stack-only 類型(i32, f64, bool, &T)
- Clone:顯式呼叫
.clone(),可自訂深拷貝邏輯,適用於 heap 資料
- 若類型實作
Copy,則 move 不會發生;賦值和傳參都是複製
let x: i32 = 42;
let y = x;
let s1 = String::from("hello");
let s2 = s1;
let s3 = s2.clone();
所有權策略選擇
| 場景 | 推薦策略 |
|---|
| 小型 Copy 類型 (i32, f64, bool) | 直接 Copy |
| 需要轉移所有權 | Move (預設行為) |
| 只需讀取 | &T immutable borrow |
| 需要修改 | &mut T mutable borrow |
| 多個 owner(單執行緒) | Rc<T> |
| 多個 owner(多執行緒) | Arc<T> |
| 可能需要複製(讀多寫少) | Cow<'a, T> |
| 內部可變性(單執行緒) | Cell<T> / RefCell<T> |
| 內部可變性(多執行緒) | Mutex<T> / RwLock<T> |
Interior Mutability 模式
當需要在持有 &T(immutable reference)時修改內部值:
| 類型 | 檢查時機 | 執行緒安全 | 適用場景 |
|---|
Cell<T> | 編譯期 | 否 | 小型 Copy 類型的原地替換 |
RefCell<T> | 執行期 | 否 | 需要動態借用檢查的非 Copy 類型 |
Mutex<T> | 執行期 | 是 | 多執行緒互斥存取 |
RwLock<T> | 執行期 | 是 | 多執行緒讀多寫少 |
use std::cell::RefCell;
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4);
println!("{:?}", data.borrow());
程式碼範例
Basic: Move 與 Borrow
fn main() {
let s1 = String::from("hello");
let s2 = s1;
let s3 = String::from("world");
let len = calculate_length(&s3);
println!("'{s3}' 的長度是 {len}");
let mut s4 = String::from("hello");
append_world(&mut s4);
println!("{s4}");
}
fn calculate_length(s: &str) -> usize {
s.len()
}
fn append_world(s: &mut String) {
s.push_str(" world");
}
Intermediate: Lifetime 標註與 Struct
#[derive(Debug)]
struct Excerpt<'a> {
content: &'a str,
}
impl<'a> Excerpt<'a> {
fn new(content: &'a str) -> Self {
Self { content }
}
fn first_word(&self) -> &str {
self.content.split_whitespace().next().unwrap_or("")
}
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() >= y.len() { x } else { y }
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{
let excerpt = Excerpt::new(novel.split('.').next().unwrap());
first_sentence = excerpt.first_word();
}
println!("第一個字: {first_sentence}");
let result = longest("long string", "xyz");
println!("較長的: {result}");
}
Advanced: Rc/Arc/Cow 共享所有權
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug)]
struct TreeNode {
value: i32,
children: RefCell<Vec<Rc<TreeNode>>>,
}
impl TreeNode {
fn new(value: i32) -> Rc<Self> {
Rc::new(Self {
value,
children: RefCell::new(vec![]),
})
}
fn add_child(parent: &Rc<Self>, child: Rc<Self>) {
parent.children.borrow_mut().push(child);
}
}
fn normalize(input: &str) -> Cow<'_, str> {
if input.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()) {
Cow::Borrowed(input)
} else {
Cow::Owned(input.to_lowercase())
}
}
fn shared_config_example() {
let config = Arc::new(String::from("shared configuration"));
let handles: Vec<_> = (0..3)
.map(|i| {
let cfg = Arc::clone(&config);
std::thread::spawn(move || {
println!("Thread {i} reads: {cfg}");
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}
fn main() {
let root = TreeNode::new(1);
let child_a = TreeNode::new(2);
let child_b = TreeNode::new(3);
TreeNode::add_child(&root, child_a);
TreeNode::add_child(&root, child_b);
println!("Tree root: {:?}", root);
println!("{}", normalize("hello"));
println!("{}", normalize("Hello World"));
shared_config_example();
}
常見錯誤對照表
| 錯誤訊息 | 原因 | 修復方式 |
|---|
E0382: borrow of moved value | 值已被 move 後再次使用 | 改用 .clone()、借用 &、或重新設計所有權 |
E0502: cannot borrow as mutable because also borrowed as immutable | 同時存在 &T 和 &mut T | 縮小 immutable borrow 的 scope 或用 Cell/RefCell |
E0597: does not live long enough | 引用的值在被使用前已被 drop | 延長值的生命週期或改用 owned type |
E0106: missing lifetime specifier | 函式或 struct 中的引用缺少 lifetime | 加上 <'a> 標註,遵循 elision rules |
E0507: cannot move out of borrowed content | 試圖從引用中取出所有權 | 使用 .clone()、std::mem::take()、或 Option::take() |
E0515: cannot return reference to local variable | 回傳了指向函式區域變數的引用 | 改為回傳 owned type(String 而非 &str) |
E0505: cannot move out of X because it is borrowed | 值仍被借用時嘗試 move | 確保所有 borrow 在 move 前結束 |
E0499: cannot borrow as mutable more than once | 同時存在兩個 &mut T | 重構為不同 scope 或使用 RefCell |
Anti-Patterns
1. 過度 Clone
fn bad(data: &[String]) {
for item in data {
process(item.clone());
}
}
fn good(data: &[String]) {
for item in data {
process_ref(item);
}
}
fn process(_s: String) {}
fn process_ref(_s: &str) {}
2. 不必要的 Rc/Arc
let data = Rc::new(vec![1, 2, 3]);
let data = vec![1, 2, 3];
Cargo.toml 依賴模板
[dependencies]
parking_lot = "0.12"
arc-swap = "1.7"
ouroboros = "0.18"
參考來源