所有權(quán)

2018-08-12 22:03 更新

所有權(quán)

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

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

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

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

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

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

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

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

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

所有權(quán)

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

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

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

移動語義

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

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

    let v2 = v;

但是,如果我們在之后嘗試使用 v 時,我們將發(fā)現(xiàn)一個錯誤:

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

    let v2 = v;

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

它會報出如下錯誤:

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

當(dāng)我們定義了一個取得所有權(quán)的函數(shù),然后在我們已經(jīng)將它作為參數(shù)傳遞后,然后使用時,類似的情況將會發(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]);

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

詳細(xì)信息

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

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

    let v2 = v;

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

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

復(fù)制類型

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

    let v = 1;

    let v2 = v;

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

在這種情況下,v 是一個 i32,這實(shí)現(xiàn)了 Copy 的特性。這意味著,就像一個移動,當(dāng)我們將 v 賦值給 v2 時,我們就完成了數(shù)據(jù)的一個副本。但是,與移動不同,我們在之后仍然可以使用 v。這是因?yàn)?i32 在別處沒有指向數(shù)據(jù)的指針,這是一個完整的副本。

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

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

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

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

    // hand back ownership
    v
    }

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

    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);

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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號