所有權(quán)

2018-08-12 22:03 更新

所有權(quán)

這篇指南是 Rust 已經(jīng)存在的三個(gè)所有權(quán)制度之一。這是 Rust 最獨(dú)特和最令人信服的一個(gè)特點(diǎn),其中 Rust 開(kāi)發(fā)人員應(yīng)該相當(dāng)熟悉。所有權(quán)即 Rust 如何實(shí)現(xiàn)其最大目標(biāo)和內(nèi)存安全。這里有幾個(gè)不同的概念,每一個(gè)概念都有它自己的章節(jié):

  • 所有權(quán),即正在讀的這篇文章。

  • 借用,和與它們相關(guān)的功能‘引用’

  • 生存期,借用的先進(jìn)理念

這三篇文章相關(guān)且有序。如果你想完全的理解所有權(quán)制度,你將需要這三篇文章。

在我們了解細(xì)節(jié)之前,這里有關(guān)于所有權(quán)制度的兩個(gè)重要說(shuō)明需要知道。

Rust 注重安全和速度。它通過(guò)許多‘零成本抽象’來(lái)完成這些目標(biāo),這意味著在 Rust 中,用盡可能少的抽象成本來(lái)保證它們正常工作。所有權(quán)制度是一個(gè)零成本抽象概念的一個(gè)主要例子。我們將在這篇指南中提到的所有分析都是在編譯時(shí)完成的。你不用為了任何功能花費(fèi)任何運(yùn)行成本。

然而,這一制度確實(shí)需要一定的成本:學(xué)習(xí)曲線。許多新用戶(hù)使用我們喜歡稱(chēng)之為‘與借檢查器的人斗爭(zhēng)’,即 Rust 編譯器拒絕編譯那些作者認(rèn)為有效的程序的 Rust 經(jīng)驗(yàn)。這往往因?yàn)槌绦騿T對(duì)于所有權(quán)的怎樣工作與 Rust 實(shí)現(xiàn)的規(guī)則不相符的心理模型而經(jīng)常出現(xiàn)。你可能在第一次時(shí)會(huì)經(jīng)歷類(lèi)似的事情。然而有個(gè)好消息:更有經(jīng)驗(yàn)的 Rust 開(kāi)發(fā)者報(bào)告稱(chēng),一旦他們遵從所有權(quán)制度的規(guī)則工作一段時(shí)間后,他們會(huì)越來(lái)越少的與借檢查器的行為斗爭(zhēng)。

學(xué)習(xí)了這些后,讓我們來(lái)了解所有權(quán)。

所有權(quán)

變量綁定在 Rust 中有一個(gè)屬性:它們有它們綁定到的變量的‘所有權(quán)’。這意味著當(dāng)一個(gè)綁定超出范圍時(shí),將釋放它們綁定到資源。例如:

    fn foo() {
    let v = vec![1, 2, 3];
    }

當(dāng) v 進(jìn)入范圍時(shí),將新創(chuàng)建新的 Vec<T>。在這種情況下,向量也在上為三個(gè)元素分配空間。當(dāng) v 超出 foo() 函數(shù)的作用域時(shí),Rust 將清除一切與向量有關(guān)的東西,也包括為堆分配的內(nèi)存。在該作用域結(jié)束時(shí),這種情況就一定會(huì)發(fā)生。

移動(dòng)語(yǔ)義

盡管這里也有很多微妙的東西:Rust 確保任何給定的資源都有一個(gè)確定的綁定。例如,如果我們有一個(gè)向量,我們可以將它賦值給另一個(gè)綁定。

    let v = vec![1, 2, 3];

    let v2 = v;

但是,如果我們?cè)谥髧L試使用 v 時(shí),我們將發(fā)現(xiàn)一個(gè)錯(cuò)誤:

    let v = vec![1, 2, 3];

    let v2 = v;

    println!("v[0] is: {}", v[0]);

它會(huì)報(bào)出如下錯(cuò)誤:

    error: use of moved value: `v`
    println!("v[0] is: {}", v[0]);
    ^

當(dāng)我們定義了一個(gè)取得所有權(quán)的函數(shù),然后在我們已經(jīng)將它作為參數(shù)傳遞后,然后使用時(shí),類(lèi)似的情況將會(huì)發(fā)生:

    fn take(v: Vec<i32>) {
    // what happens here isn’t important.
    }

    let v = vec![1, 2, 3];

    take(v);

    println!("v[0] is: {}", v[0]);

同樣的錯(cuò)誤:‘移動(dòng)值使用’。當(dāng)我們將所有權(quán)轉(zhuǎn)移給其他東西時(shí),我們可以說(shuō)我們已經(jīng)‘移動(dòng)’了我們提到的東西。這里你不需要某種特殊注釋?zhuān)?Rust 默認(rèn)做的事情。

詳細(xì)信息

當(dāng)我們移動(dòng)一個(gè)綁定后,我們不可以使用這個(gè)綁定的原因是微妙的,但是很重要。當(dāng)我們編寫(xiě)如下代碼時(shí):

    let v = vec![1, 2, 3];

    let v2 = v;

第一行為向量對(duì)象 v 和它包含的內(nèi)容分配內(nèi)存。向量對(duì)象存放在中,同時(shí)包含一個(gè)指向存放在中的內(nèi)容 ( [1, 2, 3] ) 的指針。當(dāng)我們將 v 賦值給 v2 時(shí),它將為 v2 創(chuàng)建一個(gè)這個(gè)指針的副本。這意味著將會(huì)有兩個(gè)指針指向堆中的向量?jī)?nèi)容。它將引進(jìn)數(shù)據(jù)競(jìng)爭(zhēng),這違反了 Rust 的安全保障。因此,Rust 禁止在我們移動(dòng)后使用 v。

我們需同樣注意某種情況下優(yōu)化可能刪除在棧中字節(jié)的真正副本。所以它并不像最初看起來(lái)的那樣毫無(wú)效率。

復(fù)制類(lèi)型

在我們將所有權(quán)轉(zhuǎn)移到另一個(gè)綁定時(shí),我們已經(jīng)建立了,你不可以使用原來(lái)的綁定。然而,這里有一個(gè)特性可以改變這種行為,它被稱(chēng)為 Copy 。我們還沒(méi)有討論過(guò)這個(gè)特性,但是現(xiàn)在,你可以把它們看作是增加額外行為的一個(gè)特殊類(lèi)型的一個(gè)注釋。例如:

    let v = 1;

    let v2 = v;

    println!("v is: {}", v);

在這種情況下,v 是一個(gè) i32,這實(shí)現(xiàn)了 Copy 的特性。這意味著,就像一個(gè)移動(dòng),當(dāng)我們將 v 賦值給 v2 時(shí),我們就完成了數(shù)據(jù)的一個(gè)副本。但是,與移動(dòng)不同,我們?cè)谥笕匀豢梢允褂?v。這是因?yàn)?i32 在別處沒(méi)有指向數(shù)據(jù)的指針,這是一個(gè)完整的副本。

我們將在特性章節(jié)討論怎樣完成你自己類(lèi)型的復(fù)制。

不僅僅是所有權(quán)

當(dāng)然,如果我們不得不將每個(gè)函數(shù)的所有權(quán)交回,我們可以寫(xiě)如下代碼:

    fn foo(v: Vec<i32>) -> Vec<i32> {
    // do stuff with v

    // hand back ownership
    v
    }

這將會(huì)特別繁瑣。當(dāng)我們想要取得所有權(quán)的東西它將會(huì)越糟糕:

    fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) {
    // do stuff with v1 and v2

    // hand back ownership, and the result of our function
    (v1, v2, 42)
    }

    let v1 = vec![1, 2, 3];
    let v2 = vec![1, 2, 3];

    let (v1, v2, answer) = foo(v1, v2);

返回值類(lèi)型,返回的行,以及調(diào)用的函數(shù)獲取方式變的更加復(fù)雜。

幸運(yùn)的是,Rust 提供了一種功能,借用,它能幫助我們解決這個(gè)問(wèn)題。它是下一章節(jié)的話題!

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)