Rust 所有權:你需要知道的 10 條規則
像 C/C++ 這樣的編程語言爲開發人員提供了對內存管理的最大控制。但這也帶來了很大的風險——很容易犯錯誤,導致崩潰、安全漏洞!
C/C++ 程序員面臨的一些常見問題包括:
-
釋放內存後再使用內存,這將導致崩潰或奇怪的漏洞。
-
用完後忘記釋放內存,隨着時間的推移,這會導致內存泄漏。
-
兩部分代碼同時訪問同一內存,這可能導致競態條件。
爲了避免這些問題,Rust 使用了所有權系統。這增加了一些編譯器檢查的規則,以確保內存安全。
關鍵思想是 Rust 中的每個值都有一個所有者,所有者對這個值負責——管理它的生命週期,釋放它,允許訪問它,等等。
通過跟蹤所有權,Rust 的編譯器可以確保值在使用時是有效的,防止數據競爭,並在需要時釋放內存。所有這些都不需要垃圾收集器!
這種所有權模型增強了 Rust 的安全性和速度,通過遵循一些所有權規則,使你的 Rust 程序將免受內存相關問題的影響。
讓我們來看看 Rust 所有權提供的 10 種超能力:
- 每個值都有一個變量,稱爲其所有者
在 Rust 中,每個值 (如字符串或整數) 都有一個所有者。所有者是綁定到該值的變量。例如:
let x = 5;
這裏,x 是整數值 5 的所有者。變量 x 跟蹤並管理 5 的值。
這種所有權系統避免了多個變量指向相同值的混亂情況。對於單一所有權,很明顯 x 是數據 5 的唯一所有者。
- 當所有者超出範圍時,該值將被刪除
當所有者變量超出作用域時,Rust 將調用該值的 drop 函數並清理它:
{
let y = 5; // y是5的所有者
} // y超出了作用域,5被刪除
作用域指的是變量在哪個塊中有效,在上面的例子中,y 只存在於 {} 花括號中。一旦執行離開該塊,y 就消失了,值 5 也被刪除了。
這種自動釋放數據的方式避免了內存泄漏。一旦所有者消失,Rust 就會清理這個值。再也不用擔心懸空指針或內存膨脹了!
- 一次只能有一個所有者
Rust 強制每個值的單一所有權,這避免了昂貴的引用計數方案:
let z = String::from("5");
let x = z; // z的所有權移到了x
// z不再擁有5
在這個例子中,將所有權從 z 轉移到 x 的成本很低。有些語言使用引用計數,其中多個變量可以指向一個值,但這有開銷。
對於單一所有權,Rust 只更新一個內部所有者變量來將所有權從 z 移到 x。沒有代價高昂的計數器更新。
- 複製所有者時,將移動數據
將所有者變量賦值給一個新變量會移動數據:
let s1 = "hello".to_string(); // s1擁有“hello”
let s2 = s1; // s1的所有權轉移到了s2
// s1不能再使用“hello”了
這裏我們創建字符串 “hello” 並將其綁定到 s1。然後我們把 s1 賦值給一個新的變量 s2。
這將所有權從 s1 轉移到 s2。S1 不再擁有字符串!數據本身沒有被複制,只是所有權移動了。
這可以防止意外地製作昂貴的副本。要真正複製數據,必須明確的使用 Rust 的 clone() 方法。
- 所有權可以通過引用來借用
我們可以創建引用變量來借用所有權:
let s = "hello".to_string(); // s擁有“hello”
let r = &s; // r不變地借用s
// s仍然擁有“hello”
println!("{}", r);
& 操作符創建了一個引用 r,該引用在這個作用域中從 s 借用了所有權。
可以把 r 看作是暫時借用 s 所擁有的數據,s 仍然保留對數據的完全所有權,r 只允許讀取 "hello" 字符串。
- 可變引用具有獨佔訪問權
一次只能有一個對值的可變引用:
let mut s = "hello".to_string();
let r1 = &mut s; // r1是s的可變引用
let r2 = &mut s; // error!
這可以防止編譯時的數據競爭。可變引用 r1 對 s 有獨佔的寫訪問,所以在 r1 完成之前不允許有其他引用。
這使同時訪問數據變得不可能,從而避免了微妙的併發性錯誤。
- 引用的有效期必須短於其所有者
引用的生命週期必須短於它們所有者的生命週期:
{
let r = String::from("hello");
drop(r);
let s = &r;
println!("{}", s);
}
Rust 通過強制執行引用不能比其所有者的生命週期更長這個規則來防止在內存釋放後再次使用引用。
- 結構體可以通過 move 或 borrow 傳遞
我們可以轉移或借用 struct 的所有權:
struct User {
name: String,
age: u32
}
let user1 = User {
name: "John".to_string(),
age: 27
};
let user2 = user1; // 所有權轉移到user2
// user1不能再使用
let borrow = &user1; // 通過引用借用user1
// user1仍然擁有數據
結構體將相關數據組織在一起,但是所有權規則仍然適用於它們的字段。
我們可以將結構體的所有權傳遞給函數和線程,或者不變地借用它們。
- 所有權規則也適用於在堆上分配的數據
let s1 = String::from("hello"); // 棧上的s1擁有堆數據
let s2 = s1.clone(); // 堆數據複製到新位置
// s1和s2各自擁有獨立的數據
let r = &s1; // r不變地借用s1的堆數據
// s1仍然擁有堆數據
這裏 s1 是在棧上分配的,但包含一個指向堆分配的 String。即使它在堆上,所有權規則也同樣適用。
Rust 防止重複釋放或在 free 之後使用,即使在使用指針時也是如此,所有權系統保證堆分配安全。
- 所有權支持安全併發
所有權增強了 Rust 的無畏併發性:
use std::thread;
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
我們使用 move 閉包將 v 的所有權移到派生線程中。這可以防止多個線程併發訪問 v。
在 Rust 中,所有權系統使得併發安全且簡單。不需要鎖,因爲編譯器強制單一所有權。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/KhAHXI1xuRsz1bp_7g7oHA