Rust的全部權(Ownership)

做爲一個java開發者,我所知道的關於內存分配的全部內容都是由一些稱爲垃圾收集的process處理的,這是JVM的問題,而不是個人問題。
所以,當我打開Rust Book並看到Rust沒有垃圾收集機制時,我變得有點擔憂。處理記憶管理的責任是否應該壓在我身上?顯然,對於C這樣的系統編程語言來講,處理內存分配是一件大事,若是作得很差就會產生重大影響。java

一、Stack(棧) & Heap(堆)

圖片描述

棧和堆是在運行時管理內存的方法。
棧被認爲是快速高效的,由於它有序地存儲和訪問數據。棧頂是在棧中添加、刪除數據的惟一位置。這被稱爲LIFO,後進先出,意味着咱們在釋放內存時只需知道棧頂的地址。
棧快速的另外一個緣由,棧所需的內存空間在編譯時是已知的。這意味着咱們能夠在將數據存儲到其中以前分配一塊固定大小的內存。
例如,若是你知道有四我的參加您的晚宴,你能夠提早決定每一個人的座位,準備多少食物,並在他們到達以前練習他們的名字。這是超級高效的!若是你不能提早確切地知道有多少人蔘加您的晚宴,你可使用堆。使用堆意味着須要準備足夠多但未知數目的椅子並向到來的人發出名稱標籤(name字段)。編程

當在運行時期間須要存儲未知大小的數據時,計算機會在堆上搜索內存,對其進行標記並返回一個指針,該指針指向內存中的位置。這稱爲分配內存。而後,您能夠在棧上使用該指針,可是,當你要檢索真實數據時,須要讀取指針指向的內存位置的數據。app

當我不斷深刻了解棧和堆時,管理堆中的數據會很困難。例如,你須要確保在完成使用一塊內存後容許計算機從新分配這塊內存。可是,若是其中一個代碼塊釋放了內存中的某個位置,而另外一個代碼塊仍然持有該內存的指針,則會出現懸空指針(dangling pointer)編程語言

跟蹤哪部分代碼正在使用堆上的哪些數據,最小化堆上的數據拷貝,以及清理堆上未被使用的數據使其不會耗盡內存空間,這是全部權解決的全部問題。
Rust Book

二、Ownership(全部權) & Scope(做用域)

Rust關於全部權的三個規則:函數

  • Rust中的每一個值都有一個稱爲其全部者的變量名。(例如:let name = "xxx")
  • 同一時間只能有一個全部者。
  • 當全部者超出做用域時,該值將被刪除。

2.1全部權的最簡單的例子

全部權的最簡單的例子是關於變量的做用域:
圖片描述
一旦當前函數做用域結束,由}表示,變量hello超出做用域會被刪除。這一點和大多數編程語言的本地變量是一致的。但這不是全部權的所有,當咱們須要在傳遞值,並將字符串常量(暫時認爲數據存儲在棧中,其實在永久的常量池中)切換到String類型(數據存儲在堆上的)時,事情會變得更有趣。this

2.2全部權發生改變

圖片描述
當使用字符串常量時,正如咱們所指望的那樣,Rust將hello的值複製到hello1中。 可是當使用String類型時,Rust會移交該值的全部值。編譯時會拋出錯誤:error[E0382]: use of moved value: 'hello’。spa

看起來在使用字符串常量時,Rust會將該一個變量的值複製到另外一個變量中,可是當咱們使用String類型時,它會移交全部權。關於這個話題在論壇裏有相關討論,請閱讀此貼子 The Copy trait - what does it actually copy?.指針

如下是我對討論後的總結:
Copy trait when using &str
字符串常量「Hello,World!」存儲在只讀內存中的某個位置(既不在棧中也不在堆中),而且指向該字符串的指針存儲在棧中。 這裏的指針一般是稱爲引用,這意味着咱們使用指向存儲在永久內存中的字符串常量的引用(參見Ownership in Rust, Part 2中有關引用的更多信息),並保證它在整個程序的運行時間裏是有效的(它有一個靜態的生命週期)。orm

變量hello和hello1存儲在棧。 當咱們使用=運算符時,Rust會將存儲在hello中的指針值的副本綁定到變量hello1。 在做用域的最後,Rust會調用drop方法從棧中刪除變量以釋放內存。 這些變量能夠存儲並輕鬆地在棧中進行復制,由於它們的大小在編譯時是已知的。生命週期

圖片描述
在堆上,字符串類型的值爲「hello,world!」使用 string:from 方法綁定到變量hello。可是,與字符串常量不一樣,綁定到變量hello的是數據自己而不只僅是指針,而且這些數據的大小能夠在運行時更改。=運算符將變量hello指向的數據綁定到新變量hello1,有效地將數據的全部權從一個變量移交給另外一個變量。變量hello如今是無效的,根據全部權規則2:「同一時間只能有一個全部者。」

2.3爲何不老是copy數據?

但爲何這樣呢?爲何Rust不始終複製數據並將其綁定到新變量?
回想一下棧和堆之間的差別,堆上存儲的數據大小在編譯時是不可知的,這意味着咱們須要在運行時進行一些內存分配步驟。這可能會代價很高。根據咱們的數據量,若是咱們成天都在copy數據,可能會很快耗盡內存。除此以外,Rust的默認行爲會保護咱們免受內存問題的影響(可能在其餘語言中遇到)。

將數據存儲在堆上並在棧上存儲指向該數據的指針。可是,與使用指針指向只讀內存(存儲字符串常量)不一樣,堆上的數據可能會發生變化。指針值<< DATA >>綁定到存儲String類型的hello變量。若是咱們將相同的指針值綁定到兩個不一樣的變量,看起來像這樣:
圖片描述

咱們有兩個變量hello和hello1,它們共享相同值的全部權。 這違反了規則2:「同一時間只能有一個全部者」,但讓咱們繼續。
在變量hello和hello1的做用域結束時,咱們必須將他們在堆上的內存釋放:
圖片描述

首先,咱們將hello1指向的堆上內存數據釋放,如今當咱們釋放hello時會發生什麼?
圖片描述

這稱爲雙重釋放錯誤(double free error),我認爲在這個StackOverflow答案中有最好的總結:https://stackoverflow.com/a/2...

A double free in C, technically speaking, leads to undefined behavior. This means that the program can behave completely arbitrarily and all bets are off about what happens. That’s certainly a bad thing to have happen! In practice, double-freeing a block of memory will corrupt the state of the memory manager, which might cause existing blocks of memory to get corrupted or for future allocations to fail in bizarre ways (for example, the same memory getting handed out on two different successive calls of malloc).
Double frees can happen in all sorts of cases. A fairly common one is when multiple different objects all have pointers to one another and start getting cleaned up by calls to free. When this happens, if you aren't careful, you might free the same pointer multiple times when cleaning up the objects. There are lots of other cases as well, though.
templatetypedef

Rust就是要避免犯這類錯誤。經過使hello無效,編譯器知道只在hello1上發出一個釋放內存的調用(drop)。

2.4深度拷貝

這一切都很好,但有些狀況下咱們確實想要copy存儲在堆中的數據。 Rust中可使用 clone()方法 實現:
圖片描述

請記住,調用clone()的代價可能會很高,這就是Rust默認阻止這類「deep copy」的緣由。

三、參考

Rust Book
Rust Language Form Post about The Copy Trait

未完待續

顯然,Rust的全部權涉及的知識還有不少: 借用(borrowing),引用(referencing)和切片(slicing)!後續補充......

相關文章
相關標籤/搜索