懸垂參考的危險性:
使用懸垂參考是非常危險的,因為它指向的記憶體可能已經被其他資料覆寫,或者已經不再有效。嘗試存取懸垂參考會導致不可預測的行為,例如程式崩潰、讀取到錯誤的資料,甚至可能引發安全漏洞。
fn main() {
let result = area();
}
fn area() -> &String {
let city = String::from("New York");
&city
}
fn area() -> &String
這個函式的簽名表明它會回傳一個指向 String 的參考 (&String)。
let city = String::from("New York");
在 area 函式內部,這一行建立了一個新的 String 物件,其內容是 "New York"。這個 String 物件被儲存在堆疊 (stack) 上,並且它的所有權屬於變數 city。
&city
這一行創建了一個指向 city 變數的 String 的參考,並試圖將這個參考回傳。
懸垂參考的產生:
問題在於 city 變數是在 area 函式內部宣告的。當 area 函式執行完畢後,city 變數會離開其作用域 (scope),並且它所擁有的 String 物件所佔用的記憶體會被釋放 (dropped)。
然而,area 函式卻試圖回傳一個指向這個已經被釋放的記憶體的參考。在 main 函式中,result 變數會接收到這個指向無效記憶體的參考。這個 result 就變成了一個懸垂參考 (dangling reference)。
fn main() {
let result = area();
println!("{}", result);
}
fn area() -> String {
String::from("New York")
}
我們將程式碼修正後,可以發現,area 函式的簽名表明它會直接回傳一個 String 的所有權,而不是回傳一個指向在函式結束後就會被釋放的局部變數的參考,也避免了懸垂參考的出現。
在 Rust 中,所有權(ownership)不僅適用於單一變數,也適用於資料集合,如陣列(array)與元組(tuple)。接著我們就來看看,資料在陣列與元組中的存放方式、是否會觸發擁有權移轉,以及如何安全地存取元素,基本上來說,前面的擁有權了解之後,這邊的概念也是一樣的。
fn main() {
let registrations = [true, false, true];
let first = registrations[0];
println!("{:?} and {:?}", registrations, first);
}
registrations
是布林值陣列,屬於 Copy 型別。let first = registrations[0]
時,值被複製,不會轉移所有權。registrations
與 first
都可以正常使用。fn main() {
let languages = [String::from("Rust"), String::from("Java")];
let first = &languages[0];
println!("{:?} and {:?}", languages, first);
}
languages
是 String
陣列,String
是 非 Copy 型別(擁有堆積資料)。let first = languages[0];
將會觸發所有權移轉,導致 languages
之後無法使用。let first = &languages[0];
可以 借用(borrow) 資料,避免所有權被移轉。fn main() {
let tuple = ("Roger", 21, "human");
let first = tuple.0;
println!("{:?} and {}", tuple, first);
}
tuple
中是 &'static str
與 i32
,這些都是 Copy 型別或靜態借用型別。tuple.0
是 Copy,所以 tuple
本身仍然可以使用。tuple
中包含 String
或其他堆積型別,則存取後會影響擁有權。資料型別 | 是否 Copy | 取出元素是否移轉所有權 | 解法建議 |
---|---|---|---|
bool , i32 |
✅ 是 | ❌ 否 | 直接取用即可 |
String , Vec |
❌ 否 | ✅ 是(除非用借用) | 使用 &array[i] |
&str |
✅ 是 | ❌ 否 | 可安心使用 |
&T
)能安全地借用資料,避免所有權問題。📚 參考文件: