最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解Rust所有權(quán)

 更新時間:2025年06月26日 10:30:01   作者:UestcXiye  
Rust通過所有權(quán)系統(tǒng)在編譯時確保內(nèi)存安全,無需垃圾回收,所有權(quán)追蹤數(shù)據(jù)生命周期,借用允許共享數(shù)據(jù),切片解決數(shù)據(jù)不一致問題,共同實(shí)現(xiàn)高效且可靠的內(nèi)存管理,下面就來了解一下

所有權(quán)是 Rust 最獨(dú)特的特性,對語言的其他部分有著深刻的影響。它使 Rust 能夠在不需要垃圾收集器的情況下保證內(nèi)存安全,因此理解所有權(quán)是如何工作的很重要。在本文中,我們將討論所有權(quán)以及幾個相關(guān)的特性:借用、切片,以及 Rust 如何在內(nèi)存中布局?jǐn)?shù)據(jù)。

什么是所有權(quán)?

所有權(quán)是一組控制 Rust 程序如何管理內(nèi)存的規(guī)則。

所有程序在運(yùn)行時都必須管理它們使用計算機(jī)內(nèi)存的方式。一些語言有垃圾收集,在程序運(yùn)行時定期查找不再使用的內(nèi)存;在其他語言中,程序員必須顯式地分配和釋放內(nèi)存。Rust 使用了第三種方法:內(nèi)存通過一個所有權(quán)系統(tǒng)進(jìn)行管理,該系統(tǒng)擁有一組編譯器檢查的規(guī)則。如果違反了任何規(guī)則,程序?qū)o法編譯。

所有權(quán)的主要目的是管理堆數(shù)據(jù)。
跟蹤代碼的哪些部分正在使用堆上的哪些數(shù)據(jù),最小化堆上的重復(fù)數(shù)據(jù)量,以及清理堆上未使用的數(shù)據(jù),這樣就不會耗盡空間,這些都是所有權(quán)可以解決的問題。

所有權(quán)規(guī)則

首先,讓我們看一下所有權(quán)規(guī)則:

  • Rust 中的每個值都有一個所有者。
  • 一次只能有一個所有者。
  • 當(dāng)所有者超出范圍時,該值將被刪除。

變量作用域

作用域是程序中某項(xiàng)有效的范圍。變量從聲明它的地方開始有效,直到當(dāng)前作用域結(jié)束。

示例:

    {                      // s is not valid here, it's not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s
    }                      // this scope is now over, and s is no longer valid

這里有兩個重要的時間點(diǎn):

  • 當(dāng) s 進(jìn)入作用域時,它是有效的。
  • 它在超出作用域之前一直有效。

在這一點(diǎn)上,作用域和變量何時有效之間的關(guān)系與其他編程語言中的關(guān)系類似。

引子:字符串變量和字面量

我們已經(jīng)看到了字符串字面量,其中字符串值被硬編碼到程序中。字符串字面值很方便,但它們并不適合我們可能想要使用文本的所有情況。一個原因是它們是不可變的。另一個問題是,當(dāng)我們編寫代碼時,不是每個字符串值都是已知的。

對于這些情況,可以創(chuàng)建字符串變量:

    let mut s = String::from("hello");
    s.push_str(", world!"); // push_str() appends a literal to a String
    println!("{s}"); // This will print `hello, world!`

這種類型的字符串可以被改變。

為什么字符串可以改變,而字面量不能?不同之處在于這兩種類型處理內(nèi)存的方式。

內(nèi)存和分配

對于字符串字面值,我們在編譯時就知道其內(nèi)容,因此文本直接硬編碼到最終的可執(zhí)行文件中。這就是字符串字面值快速高效的原因。但這些屬性僅來自字符串文字的不變性。不幸的是,我們不能為每個在編譯時大小未知且在運(yùn)行程序時大小可能改變的文本塊放入二進(jìn)制文件中的內(nèi)存塊。

對于 String 類型,為了支持可變的、可增長的文本片段,我們需要在堆上分配一定數(shù)量的內(nèi)存(在編譯時未知)來保存內(nèi)容。這意味著:

  • 內(nèi)存必須在運(yùn)行時從內(nèi)存分配器請求。
  • 我們需要一種方法,在使用完 String 后將這些內(nèi)存返回給分配器。

第一部分由我們完成,當(dāng)調(diào)用String::from時,它的實(shí)現(xiàn)請求它所需的內(nèi)存。這在編程語言中是非常普遍的。

然而,第二部分是不同的。在帶有垃圾收集器(GC)的語言中,GC 跟蹤并清理不再使用的內(nèi)存,我們不需要考慮它。

在大多數(shù)沒有 GC 的語言中,我們有責(zé)任識別內(nèi)存何時不再被使用,并調(diào)用代碼顯式釋放它,就像我們請求它一樣。正確地做到這一點(diǎn)歷來是一個困難的編程問題。如果我們忘記了,我們就會浪費(fèi)記憶。如果我們做得太早,就會得到一個無效的變量。如果我們做兩次,這也是一個 bug。我們需要恰好配對一個已分配的和一個空閑的。

Rust 采用不同的路徑:一旦擁有內(nèi)存的變量超出作用域,內(nèi)存就會自動返回。

    {
        let s = String::from("hello"); // s is valid from this point forward
        // do stuff with s
    }
    // this scope is now over, and s is no longer valid

當(dāng)變量超出作用域時,Rust 會為我們調(diào)用一個特殊的函數(shù)。這個函數(shù)被稱為 drop,該函數(shù)將內(nèi)存返回給分配器。

注意:在 C++ 中,這種在項(xiàng)目生命周期結(jié)束時釋放資源的模式有時被稱為資源獲取即初始化(RAII)。

與 Move 交互的變量和數(shù)據(jù)

在 Rust 中,多個變量可以以不同的方式與相同的數(shù)據(jù)交互。

示例 1:

    let x = 5;
    let y = x;

我們現(xiàn)在有兩個變量,x 和 y,它們都等于 5。因?yàn)檎麛?shù)是具有已知的固定大小的簡單值,并且這兩個 5 值被壓入堆棧。

示例 2:

    let s1 = String::from("hello");
    let s2 = s1;

字符串由三部分組成:指向存儲字符串內(nèi)容的內(nèi)存的指針、長度和容量。長度是指字符串的內(nèi)容當(dāng)前使用了多少內(nèi)存(以字節(jié)為單位)。容量是字符串從分配器接收到的總內(nèi)存量(以字節(jié)為單位)。

這組數(shù)據(jù)存儲在堆棧上,右邊是堆中保存內(nèi)容的內(nèi)存。

在這里插入圖片描述

當(dāng)將 s1 賦值給 s2 時,復(fù)制了 String 數(shù)據(jù),這意味著復(fù)制了堆棧上的指針、長度和容量。但是,不復(fù)制指針?biāo)赶虻亩焉系臄?shù)據(jù)。

在這里插入圖片描述

前面我們說過,當(dāng)變量超出作用域時,Rust 會自動調(diào)用 drop 函數(shù)并為該變量清理堆內(nèi)存。但是上圖顯示兩個數(shù)據(jù)指針都指向同一個位置。這是一個問題:當(dāng) s2 和 s1 超出作用域時,它們都將嘗試釋放相同的內(nèi)存。這被稱為 double free error,是內(nèi)存安全錯誤之一。釋放內(nèi)存兩次可能會導(dǎo)致內(nèi)存損壞,這可能會導(dǎo)致安全漏洞。

為了確保內(nèi)存安全,在 let s2 = s1; 行之后,Rust 認(rèn)為 s1 不再有效。因此,當(dāng) s1 超出作用域時,Rust 不需要釋放任何東西。

在創(chuàng)建 s2 之后嘗試使用 s1 會報錯:使用無效的引用。

在 C++ 中,你可能聽說過淺拷貝和深拷貝這兩個術(shù)語,那么在不復(fù)制數(shù)據(jù)的情況下復(fù)制指針、長度和容量的概念,可以視為淺拷貝。但是因?yàn)?Rust 會使第一個變量無效,所以它被稱為移動(Move),而不是淺拷貝。在這個例子中,我們會說 s1 被移動到 s2。

在這里插入圖片描述

這就解決了我們的問題!只有 s2 有效,當(dāng)它超出作用域時,它會單獨(dú)釋放內(nèi)存,這樣就完成了。

此外,這還隱含了一個設(shè)計選擇:Rust 永遠(yuǎn)不會自動創(chuàng)建數(shù)據(jù)的“深度”副本。因此,就運(yùn)行時性能而言,任何自動復(fù)制都可以被認(rèn)為是廉價的。

范圍和分配

作用域、所有權(quán)和通過 drop 函數(shù)釋放的內(nèi)存之間的關(guān)系也是如此。

當(dāng)你給一個已經(jīng)存在的變量賦一個全新的值時,Rust 會調(diào)用 drop 并立即釋放原始值的內(nèi)存。

示例:

    let mut s = String::from("hello");
    s = String::from("ahoy");

    println!("{s}, world!");

我們首先聲明一個變量 s,并將其綁定到一個值為 “hello” 的字符串。然后,我們立即創(chuàng)建一個值為 “ahoy” 的新 String,并將其賦值給 s。此時,原始字符串立即超出了作用域,Rust 運(yùn)行 drop函數(shù)立即釋放 “hello” 的內(nèi)存。

在這里插入圖片描述

克?。–lone)

Rust 提供一個叫 clone 的方法進(jìn)行深拷貝。

示例:

    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {s1}, s2 = {s2}");

堆數(shù)據(jù)確實(shí)被復(fù)制了。

在這里插入圖片描述

只在棧上的數(shù)據(jù):復(fù)制(Copy)

示例:

    let x = 5;
    let y = x;

    println!("x = {x}, y = {y}");

這段代碼似乎與我們剛剛學(xué)到的內(nèi)容相矛盾:我們沒有調(diào)用 clone,但是 x 仍然有效,并且沒有移動到 y 中。

原因是,在編譯時具有已知大小的整數(shù)等類型完全存儲在堆棧中,因此可以快速復(fù)制實(shí)際值。這意味著我們沒有理由在創(chuàng)建變量 y 后阻止 x 的有效性。換句話說,這里沒有深度復(fù)制和淺復(fù)制的區(qū)別,所以調(diào)用 clone 與通常的淺復(fù)制沒有任何不同,我們可以省略它。

Rust 有一個特殊的注釋,叫做 Copy trait,我們可以把它放在存儲在堆棧上的類型上,就像整數(shù)一樣。如果一個類型實(shí)現(xiàn)了 Copy 特性,那么使用它的變量不會移動,而是被簡單地復(fù)制,使它們在賦值給另一個變量后仍然有效。

如果類型或其任何部分實(shí)現(xiàn)了 Drop 特性,Rust 將不允許我們用 Copy 注釋類型。如果該類型需要在值超出作用域時發(fā)生一些特殊的事情,并且向該類型添加 Copy 注釋,則會得到編譯時錯誤。

一般地,任何一組簡單標(biāo)量值都可以實(shí)現(xiàn) Copy,并且不需要分配或某種形式的資源來實(shí)現(xiàn) Copy。下面是一些實(shí)現(xiàn) Copy 的類型:

  • 所有整數(shù)類型,如 u32。
  • 所所有浮點(diǎn)類型,如 f64。
  • 所布爾類型,bool。
  • 所字符類型,char。
  • 只包含實(shí)現(xiàn) Copy 的類型的元組。例如,(i32, i32)實(shí)現(xiàn)了Copy,但(i32, String)沒有。

所有權(quán)與函數(shù)

將值傳遞給函數(shù)的機(jī)制類似于將值賦給變量的機(jī)制。將變量傳遞給函數(shù)會移動或復(fù)制,就像賦值一樣。

示例:

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // because i32 implements the Copy trait,
                                    // x does NOT move into the function,
    println!("{}", x);              // so it's okay to use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.

如果我們試圖在調(diào)用 takes_ownership 之后使用 s, Rust 會拋出一個編譯時錯誤。

返回值和作用域

返回值也可以轉(zhuǎn)移所有權(quán)。

示例:

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1

    let s2 = String::from("hello");     // s2 comes into scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}

變量的所有權(quán)每次都遵循相同的模式:將值賦給另一個變量會移動該變量。當(dāng)包含堆上數(shù)據(jù)的變量超出作用域時,除非數(shù)據(jù)的所有權(quán)已移動到另一個變量,否則該值將通過 drop 清除。

Rust 允許我們使用元組返回多個值,雖然這是可行的,但是獲取所有權(quán)并返回每個函數(shù)的所有權(quán)有點(diǎn)繁瑣。

示例:

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{s2}' is {len}.");
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a String

    (s, length)
}

如果我們想讓一個函數(shù)使用一個值,但不獲得所有權(quán),該怎么辦?

幸運(yùn)的是,Rust 有一個不用轉(zhuǎn)移所有權(quán)就能使用值的特性,叫做引用。

引用(References)和借用(Borrowing)

引用類似于指針,因?yàn)樗且粋€地址,我們可以按照它訪問存儲在該地址的數(shù)據(jù)。

引用用 & 符號表示。與使用 & 進(jìn)行引用相反的是解引用,它是通過解引用操作符 * 完成的。

與指針不同,引用保證在其生命周期內(nèi)指向特定類型的有效值。

示例:

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because s does not have ownership of what
  // it refers to, the value is not dropped.

我們稱創(chuàng)建引用的操作為借用。&s1 語法允許我們創(chuàng)建一個引用 s,該引用引用 s1 的值,但不擁有該值。因?yàn)橐貌粨碛兴援?dāng)引用停止使用時,它所指向的值不會被刪除。同樣,函數(shù)的定義使用 & 表示形參 s 的類型是引用。

在這里插入圖片描述

引用也是不可變的。我們不允許修改我們引用過的東西。

示例:

fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

這段代碼會報錯:error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference。

Rust 通過“借用檢查器”確保引用的安全性。

變量對其數(shù)據(jù)有三種權(quán)限:

  • 讀:數(shù)據(jù)可以被復(fù)制到另一個位置。
  • 寫:數(shù)據(jù)可以被修改。
  • 擁有:數(shù)據(jù)可以被移動或釋放。

這些權(quán)限在運(yùn)行時并不存在,僅在編譯器內(nèi)部存在。

默認(rèn)情況下,變量對其數(shù)據(jù)具有讀/擁有的權(quán)限,如果變量是可變的,那么它還擁有寫權(quán)限。

重點(diǎn):引用可以臨時解除這些權(quán)限。

可變的引用

修改之前的代碼,使其允許我們通過使用可變引用的一些小調(diào)整來修改借用值。

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

用 &mut s 就可以創(chuàng)建一個可變引用。

可變引用有一個很大的限制:如果對一個值有一個可變引用,那么就不能有對該值的其他引用。下面的代碼試圖創(chuàng)建對 s 的兩個可變引用將會失?。?/p>

    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);

規(guī)定不能同時對同一數(shù)據(jù)進(jìn)行多個可變引用的限制,這樣做的好處是 Rust 可以在編譯時防止數(shù)據(jù)競爭。數(shù)據(jù)競爭類似于競爭條件,發(fā)生在以下三種行為時:

  • 兩個或多個指針同時訪問相同的數(shù)據(jù)。
  • 至少有一個指針被用來寫數(shù)據(jù)。
  • 沒有使用任何機(jī)制來同步對數(shù)據(jù)的訪問。

數(shù)據(jù)競爭會導(dǎo)致未定義的行為,當(dāng)你試圖在運(yùn)行時追蹤它們時,可能很難診斷和修復(fù)它們。Rust 通過拒絕編譯帶有數(shù)據(jù)競爭的代碼來防止這個問題!

我們可以使用花括號來創(chuàng)建一個新的作用域,允許多個可變引用,只是不能同時使用:

    let mut s = String::from("hello");
    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.
    let r2 = &mut s;

Rust 對可變引用和不可變引用的組合強(qiáng)制了類似的規(guī)則:

  • 對同一值有不可變引用的同時,也不能有可變引用。
  • 允許使用多個不可變引用。

示例:

    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{}, {}, and {}", r1, r2, r3);

報錯:error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable。

請注意,引用的作用域從它被引入的地方開始,一直持續(xù)到最后一次使用該引用的時候。例如,下面這段代碼可以編譯,因?yàn)椴豢勺円玫淖詈笠淮问褂檬窃?println!,在引入可變引用之前:

    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{r1} and {r2}");
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{r3}");

編譯器可以在作用域結(jié)束之前的某個點(diǎn)上判斷出引用不再被使用。

加深對可變引用的理解

可變引用提供對數(shù)據(jù)唯一的非擁有的訪問。

不可變引用是只讀的??勺円迷诓灰苿訑?shù)據(jù)的情況下,臨時提供可變訪問。

在這里插入圖片描述

可變引用臨時降級為只讀引用

對一個可變引用再進(jìn)行引用,可以暫時剝奪可變引用的寫權(quán)限,新引用只具有讀權(quán)限。

在這里插入圖片描述

權(quán)限在生命周期結(jié)束時被返回

在創(chuàng)建了 x 的引用 y 后,x 的寫和擁有權(quán)限暫時被剝奪,直到 y 的最后一次被使用之后,x 重新獲得寫和擁有權(quán)限。

在這里插入圖片描述

懸空的引用

在使用指針的語言中,很容易通過釋放一些內(nèi)存而保留指向該內(nèi)存的指針來錯誤地創(chuàng)建懸空指針。

相比之下,在 Rust 中,編譯器保證引用永遠(yuǎn)不會是懸空引用:如果你有對某些數(shù)據(jù)的引用,編譯器將確保數(shù)據(jù)不會在對數(shù)據(jù)的引用超出作用域之前超出作用域。

即,數(shù)據(jù)必須在其所有的引用存在的期間存活。

示例:創(chuàng)建了 s 的引用 s_ref 后, s 的擁有權(quán)限暫時被剝奪,于是不能移動或刪除,程序在 drop(s) 出報錯。

在這里插入圖片描述

讓我們嘗試創(chuàng)建一個懸空引用,看看 Rust 是如何用編譯時錯誤來防止它們的:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

報錯:error[E0106]: missing lifetime specifier。

這個錯誤消息涉及到一個我們尚未涉及的特性:生命周期。在這里我們先不討論這個特性,而是分析 dangle() 函數(shù)的錯誤原因:因?yàn)?s 是在 dangle 內(nèi)部創(chuàng)建的,所以當(dāng) dangle 的代碼完成時,s 將被釋放。但是我們試著返回對它的引用。這意味著這個引用將指向一個無效的字符串,于是發(fā)生了錯誤。

這里的解決方案是直接返回 String,而不是其引用:

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

總結(jié):

  1. 在任何給定的時間,可以有一個可變引用或任意數(shù)量的不可變引用。
  2. 引用必須總是有效的。

接下來,我們來看看另一種類型的引用:切片。

切片(Slice)

切片允許引用集合中連續(xù)的元素序列,而不是整個集合。

切片是一種引用,所以它沒有所有權(quán)。

這里有一個小編程問題:編寫一個函數(shù),它接受一個由空格分隔的單詞字符串,并返回它在該字符串中找到的第一個單詞的長度。如果函數(shù)在字符串中沒有找到空格,則整個字符串必須是一個單詞,因此應(yīng)該返回整個字符串的長度。

讓我們來看看如何在不使用切片的情況下編寫這個函數(shù),以理解切片將解決的問題:

fn first_word(s: &String) -> usize {
	// convert String to an array of bytes
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

我們現(xiàn)在有一種方法來找出字符串中第一個單詞末尾的索引,但是有一個問題。我們自己返回了一個 usize,但它只是一個獨(dú)立于 String 的值,所以不能保證它在將來仍然有效。

示例:

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s); // word will get the value 5

    s.clear(); // this empties the String, making it equal to ""

    // `word` still has the value `5` here, but `s` no longer has any content
    // that we could meaningfully use with the value `5`, so `word` is now
    // totally invalid!
}

這個程序編譯時沒有任何錯誤,如果在調(diào)用 s.clear() 之后使用 word,也不會出現(xiàn)任何錯誤。因?yàn)?word 根本沒有連接到 s 的狀態(tài),所以 word 仍然包含值 5。我們可以使用這個值 5 和變量 s 來嘗試提取出第一個單詞,但這將是一個 bug,因?yàn)樽詮奈覀儗?5 保存在 word 中以來,s 的內(nèi)容已經(jīng)發(fā)生了變化。

word 中的索引與 s 中的數(shù)據(jù)不同步是危險的。幸運(yùn)的是,Rust 有一個解決方案:字符串切片。

字符串切片是對字符串的一部分的引用,它看起來像這樣:

    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];

指定 [starting_index…ending_index],其中 starting_index 是片中的第一個位置,ending_index 比片中的最后一個位置大 1。

在這里插入圖片描述

如果想從下標(biāo) 0 開始,starting_index 可以省略,下面兩種切片是一樣的:

let s = String::from("hello");

let slice1 = &s[0..2];
let slice2 = &s[..2];

同樣,如果切片包含 String 的最后一個字符,ending_index 也可以省略,下面兩種切片是一樣的:

let s = String::from("hello");

let len = s.len();
let slice1 = &s[3..len];
let slice2 = &s[3..];

掌握了切片后,重寫之前的 first_word 函數(shù),這次返回的是字符串而非下標(biāo):

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

還記得之前程序中的錯誤嗎?當(dāng)我們獲得了第一個單詞末尾的索引,但隨后清除了字符串,因此索引無效。這段代碼在邏輯上是不正確的,但沒有立即顯示出任何錯誤。如果我們一直嘗試將第一個單詞索引與空字符串一起使用,那么問題就會出現(xiàn)。切片使這個 bug 不可能出現(xiàn),并讓我們更快地知道我們的代碼有問題。

示例:

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    s.clear(); // error!
    println!("the first word is: {word}");
}

報錯:error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable。

回顧借用規(guī)則,如果有對某物的不可變引用,就不能同時使用可變引用。因?yàn)?clear 需要截斷 String,所以它需要獲得一個可變引用。println! 在調(diào)用 clear 之后使用 word 中的引用,因此不可變引用在那時必須仍然是活動的。Rust 不允許 clear 中的可變引用和 word 中的不可變引用同時存在,編譯失敗。

作為切片的字符串字面量

回想一下,我們討論過將字符串字面值存儲在二進(jìn)制文件中?,F(xiàn)在我們知道了切片,我們可以正確地理解字符串字面值:

let s = "Hello, world!";

這里 s 的類型是 &str,一個指向二進(jìn)制數(shù)據(jù)中特定點(diǎn)的切片。這也是為什么字符串字面值是不可變的,&str 是一個不可變引用。

作為參數(shù)的字符串切片

知道可以取字面量和字符串值的切片后,我們對 first_word 又做了一個改進(jìn),那就是它的聲明:

fn first_word(s: &str) -> &str {

定義一個函數(shù)來接受一個字符串切片而不是一個字符串的引用,將提高函數(shù)的靈活性。如果我們有一個字符串切片,我們可以直接傳遞。如果有一個 String 對象,則可以傳遞 String 對象的切片或?qū)?String 對象的引用。這種靈活性利用了取消強(qiáng)制轉(zhuǎn)換。

其他切片

數(shù)組也可以有切片:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);

這個切片的類型是 &[i32]。

總結(jié)

所有權(quán)、借用和切片的概念確保了 Rust 程序在編譯時的內(nèi)存安全。Rust 語言提供了對內(nèi)存使用的控制,但是當(dāng)數(shù)據(jù)所有者超出范圍時,數(shù)據(jù)所有者會自動清理數(shù)據(jù),這意味著不必編寫和調(diào)試額外的代碼來獲得這種控制。

到此這篇關(guān)于深入理解Rust所有權(quán)的文章就介紹到這了,更多相關(guān)Rust所有權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Rust中into和from用法及區(qū)別介紹

    Rust中into和from用法及區(qū)別介紹

    這篇文章主要介紹了Rust中的?into和from使用及區(qū)別介紹,into和from是Rust語言中兩個用于類型轉(zhuǎn)換的函數(shù),它們分別屬于Into和From這兩個trait,本文通過實(shí)例代碼詳細(xì)講解,需要的朋友可以參考下
    2023-04-04
  • 如何在Rust中處理命令行參數(shù)和環(huán)境變量

    如何在Rust中處理命令行參數(shù)和環(huán)境變量

    在本章節(jié)中, 我們探討了Rust處理命令行參數(shù)的常見的兩種方式和處理環(huán)境變量的兩種常見方式,感興趣的朋友一起看看吧
    2023-12-12
  • Rust?Postgres實(shí)例代碼

    Rust?Postgres實(shí)例代碼

    Rust Postgres是一個純Rust實(shí)現(xiàn)的PostgreSQL客戶端庫,本文主要介紹了Rust?Postgres實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • Rust指南之生命周期機(jī)制詳解

    Rust指南之生命周期機(jī)制詳解

    Rust?生命周期機(jī)制是與所有權(quán)機(jī)制同等重要的資源管理機(jī)制,之所以引入這個概念主要是應(yīng)對復(fù)雜類型系統(tǒng)中資源管理的問題,這篇文章主要介紹了Rust指南之生命周期機(jī)制詳解,需要的朋友可以參考下
    2022-10-10
  • rust智能指針的具體使用

    rust智能指針的具體使用

    智能指針是一些數(shù)據(jù)結(jié)構(gòu),它們的行為類似于指針但擁有額外的元數(shù)據(jù)和附加功能,本文就來介紹一下rust智能指針的具體使用,感興趣的可以了解一下
    2023-12-12
  • Rust生命周期之驗(yàn)證引用有效性與防止懸垂引用方式

    Rust生命周期之驗(yàn)證引用有效性與防止懸垂引用方式

    本文介紹了Rust中生命周期注解的應(yīng)用,包括防止懸垂引用、在函數(shù)中使用泛型生命周期、生命周期省略規(guī)則、在結(jié)構(gòu)體中使用生命周期、靜態(tài)生命周期以及如何將生命周期與泛型和特質(zhì)約束結(jié)合,通過這些機(jī)制,Rust在編譯時就能捕獲內(nèi)存安全問題
    2025-02-02
  • Rust中Protobuf使用詳解

    Rust中Protobuf使用詳解

    本文介紹了如何在Rust中使用Protobuf進(jìn)行序列化和反序列化,并介紹了如何在項(xiàng)目中使用生成的Rust代碼,通過include!宏和mod.rs文件的配置,可以方便地在Rust項(xiàng)目中使用Protobuf,感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • Rust 中的文件操作示例詳解

    Rust 中的文件操作示例詳解

    Rust 中的路徑操作是跨平臺的,std::path 模塊提供的了兩個用于描述路徑的類型,本文給大家介紹Rust 中的文件操作示例詳解,感興趣的朋友一起看看吧
    2021-11-11
  • 一文詳解Rust中的錯誤處理

    一文詳解Rust中的錯誤處理

    這篇文章主要為大家詳細(xì)介紹了Rust中的錯誤處理的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-01-01
  • 使用win10 wsl子系統(tǒng)如何將 rust 程序靜態(tài)編譯為linux可執(zhí)行文件

    使用win10 wsl子系統(tǒng)如何將 rust 程序靜態(tài)編譯為linux可執(zhí)行文件

    這篇文章主要介紹了使用win10 wsl子系統(tǒng)如何將 rust 程序靜態(tài)編譯為linux可執(zhí)行文件,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-05-05

最新評論

报价| 奎屯市| 红原县| 阿坝县| 保靖县| 瑞金市| 嘉善县| 竹山县| 扎赉特旗| 留坝县| 离岛区| 军事| 玛多县| 延吉市| 施秉县| 萝北县| 台安县| 太和县| 上杭县| 开鲁县| 孟连| 灵璧县| 茌平县| 永康市| 叶城县| 阳朔县| 稻城县| 鄯善县| 长海县| 朝阳市| 彰武县| 马边| 留坝县| 溆浦县| 厦门市| 会同县| 七台河市| 井冈山市| 灌云县| 枣强县| 道真|