通俗易懂解釋Rust所有權和借用概念
Rust有三個主要概念:
- 所有權(在同一時間只有一個變數 "擁有 "資料,並且所有者負責取消分配)
- 借用(你可以向擁有的變數借用一個引用)
- 生命週期(所有的資料都會跟蹤它將被銷燬的時間)
這些都是相當簡單的概念,但它們往往與其他語言的概念背道而馳,所以我想試著更詳細地解釋一下這些概念。因為Rust並不是唯一使用這些概念的語言(例如,你可以在C++中做unique_ptr),學習這些概念不僅可以幫助你寫出更好的Rust程式碼,而且可以寫出更好的程式碼。
所有權
所有權的概念是,如果你擁有一個專案,你就負責在你完成後銷燬這個專案。
在Rust中,一塊資料在任何時候都只能有一個所有者。這與垃圾收集語言或帶有原始指標的語言有很大的不同,因為你經常要 "玩弄 "資料,而不是對同一資料有多個引用,所以在同一時間只有一個變數擁有該資料。
擁有的資料只有在擁有的變數不再擁有該資料時才會被自動刪除。這可能發生在以下情況。
- 所有者變數超出範圍並被銷燬
- 所有者變數被設定為另一個值,使原來的資料不再可被訪問
這就簡化了記憶體管理問題,並消除了在C或舊C++中經常發生的 "最後該由哪個指標來刪除資料 "的混亂問題。所有權不是Rust獨有的。現代C++推薦使用'unique_ptr',一個智慧指標,它也 "擁有 "它所包裹的資料,而不是原始指標。
當你在Rust中宣告一個變數時,該變數 "擁有 "資料。
let a = Box::new(2); // a "owns" a heap allocated integer |
當一個變數擁有某樣東西時,它可以使用賦值將其轉移到其他變數。在讓出其資料後,舊變數不能再訪問該值,而新變數是新的所有者。
let mufasa = Box::new("king"); // mufasa is the owner of "king" let scar = mufasa; // the data "king" is moved from mufasa to scar println!("{}", scar); // scar is now the owner of "king" println!("{}", mufasa); // ERROR: mufasa can no longer be accessed |
在建立所有者的範圍結束時,資料被銷燬。
{ let a = Box::new(2); // a owns a heap allocated integer } // a's data deallocated here |
技巧和竅門
1、 將數值傳入函式會將資料 "移動 "到函式變數中。一旦發生這種情況,原來的變數就不能被訪問。這似乎很有限制性,這就是為什麼下一個話題要嘗試解決這個問題。
fn hello(a: Box<i32>) { println("{:?}", a); // prints "2" } fn main() { let b = Box::new(2); hello(b); // moves b into hello's a parameter b; // ERROR: cannot access b after it gave its value to a } |
2、在使用Rust時,一個常見的問題是,當資料被一個容器(如Vec或Option)所包圍時,如果你想把資料取出來,你必須先手動克隆資料或把它從容器中移除。一個問題是,有時你想把資料從容器中移出,而又不妨礙對容器變數的訪問。要做到這一點,你可以使用mem::replace函式,它將變數 "重置 "為某個值並返回擁有的資料。之後,原來擁有的變數不再擁有這些資料,而被設定為返回值的變數現在擁有這些資料。例如,這裡是一個連結列表的程式碼片段。
use std::mem; type Link<T> = Option<Box<Node<T>>>; struct Node<T> { data: T, next: Link<T>, } pub struct Stack<T> { size: i32, head: Link<T>, } impl<T> Stack<T> { // ... other methods pub fn pop(&mut self) -> Option<T> { let head = mem::replace(&mut self.head, None); // retrieve the Node from the Option and and set self.head to be None head.map(|old_head| { let old_head = *old_head; self.head = old_head.next; self.size -= 1; old_head.data }) } } |
因為這種情況在Options中特別常見,所以有一個Options的take()方法,做同樣的事情,但不那麼冗長。
impl<T> Stack<T> { // ... other methods pub fn pop(&mut self) -> Option<T> { self.head.take().map(|old_head| { // retrieve the Node from the Option and set self.head to be None let old_head = *old_head; self.head = old_head.next; // self.head is None so you can freely set it self.size -= 1; old_head.data }) } } |
借用
上一節強調了所有權本身的一個大缺陷。在很多情況下,你想運算元據,但又不真正擁有資料。例如,你可能想把一個值傳遞給一個函式,但仍然能夠在函式之外呼叫所有者變數。
Rust允許你使用借用的概念來做到這一點。借用就像你所想的那樣,它只是允許另一個變數暫時借用你的變數中的資料,並在完成後將其歸還。
Rust允許你有兩種型別的借用。
- 帶有'&'的不可變的借用(你可以讀取借用資料的值,但你不能修改它)
- 帶有'&mut'的可變借用(你可以讀取和修改借用的資料的值)
你既可以有
- 有很多不可變的借用
- 只有一個可變的借用
在任何給定的時間內,在一個作用域中的一塊資料只有一個可變的借用。所以你應該儘量在大多數時候做不可變的借用,只有在你真正需要的時候才做可變的借用。
要訪問一個借用的引用中的值,你要使用解除引用運算子 "*"。
當你借用一個變數時,所有者變數變得不可訪問,直到借用的變數被銷燬。
let mut x = 5; let y = &mut x; // y從x那裡借用了資料。 println! ("{}", x); // ERROR: x不再擁有資料,y擁有它! |
當一個借來的變數被銷燬時,它會把借來的值還給所有者。
let mut x = 5; { let y = &mut x; // y從x那裡借來了資料。 *y += 1; // y改變借用的資料 } // y把資料還給x println!("{}", x); // x又拿回了資料(並且它被改為6)。 |
正因為如此,所有者要比借用者活得更久
let mut x: &i32; { 讓y = 3; x = &y; // ERROR: x比y活得長! } // y在這裡被毀掉了! 如果Rust編譯器不阻止這種情況的發生,x會發生什麼? |
技巧和竅門
函式引數最終大多是借來的引用,因為否則值會被移到函式內部。
函式的返回值不應該是對區域性變數的引用,Rust不會讓你這樣做。如果你在C語言中返回了一個指向區域性變數的指標,你可能會導致資料被破壞,或者返回值不知道什麼時候要去掉它的資料,不管你怎麼看,這都是不好的。
不要擔心取消引用來 "讀 "或 "寫"(取決於&或&mut)一個借用的引用的值
enum State { Hello, Bye, } fn hello(blah: &State, foo: &mut i32) { match *blah { // you are only reading an immutable reference so its fine State::Hello => println!("Hello!"), State::Bye => println!("Bye!"), } *foo += 1; // you are only writing to a mutable reference so its fine } |
不要擔心將解除引用的引用分配給變數,因為這將試圖將資料轉移到新的變數中。
enum State { Hello, Bye, } fn hello(blah: &State) { let thief = *blah; // ERROR: blah can't give a borrowed item to thief! // 小偷要拿走值而不還給原主人 } |
賦值並不是唯一會試圖移出借來的引用的東西。例如,模式匹配也會試圖移動值。為了防止這種情況,在匹配變數前面加上'ref'來借用匹配變數而不是移動
enum State { Hello(String), Bye, } fn hello(blah: &State) { match *blah { State::Hello(s) => println!("Hello, {}", s), // ERROR: moves data inside blah (the string) into the variable s State::Bye => println!("Bye!"), } // do this instead match *blah { State::Hello(ref s) => println!("Hello, {}", s), // borrow the string data inside blah State::Bye => println!("Bye!"), } } |
詳細點選標題
相關文章
- Rust 所有權和借用Rust
- 用Iterator解釋Rust所有權概念Rust
- 使用共享引用說明Rust所有權概念Rust
- 【Rust學習】記憶體安全探秘:變數的所有權、引用與借用Rust記憶體變數
- Rust:Programming Rust:所有權Rust
- Rust所有權__OwnershipRust
- Rust入門系列之引用和借用Rust
- Rust所有權及引用Rust
- Rust 所有權如何理解Rust
- RUST所有權相關問題Rust
- rust學習五、認識所有權Rust
- Rust 程式設計影片教程對應講解內容-所有權Rust程式設計
- 012 Rust死靈書之分解借用Rust
- Rust 程式設計視訊教程對應講解內容-所有權Rust程式設計
- 借用UAC完成的提權思路分享
- 機器學習術語通俗易懂的解釋機器學習
- ASM 常用概念解釋ASM
- 通俗易懂的解釋:什麼是APIAPI
- 機器學習梯度下降法,最通俗易懂的解釋機器學習梯度
- Rust記憶體安全解釋Rust記憶體
- 通俗易懂解釋一類和二類錯誤(Type I Error Type II Error)Error
- 分散式快取 - 概念解釋分散式快取
- 鮑勃大爺:SOLID概念解釋Solid
- 「Kafka應用」名詞概念解釋Kafka
- 一圖看懂所有機器學習概念機器學習
- 通俗易懂解釋什麼是PCIA(主成分分析) - stackexchange
- 用通俗易懂的方法解釋MongoDB的選舉機制MongoDB
- 前端非同步的解釋-概念性前端非同步
- Rust 1.60.0釋出Rust
- Rust 1.59.0釋出Rust
- Rust 0.4釋出Rust
- Rust 1.79.0釋出Rust
- rust程式設計(3)結構體相關概念和疑問Rust程式設計結構體
- [php]apache的許可權解釋PHPApache
- 通俗易懂的 SAP ABAP 會話管理(Session Management)概念講解,包含具體的例項會話Session
- std::io::BufReader 物件借用和引用的問題物件
- 許可權概念、許可權提升概念以及許可權提升的分類和目的 Windows 提權的基礎原理是瞭解作業系統的安全機制和許可權管理 Windows提權攻擊的進一步知識概念Windows作業系統
- 可變所有權型別型別