国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Rust所有權

這篇具有很好參考價值的文章主要介紹了Rust所有權。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

什么是所有權

什么是所有權

所有程序在運行時都必須管理其使用計算機內存的方式:

  • 一些語言中具有垃圾回收機制,在程序運行時有規(guī)律地尋找不再使用的內存,比如C#和Java。
  • 在另一些語言中,程序員必須自行分配和釋放內存,比如C/C++。

而Rust則是通過所有權系統(tǒng)管理內存:

  • 所有權是Rust最獨特的特性,它讓Rust無需GC就可以保證內存安全,這也是Rust的核心特性。
  • 通過所有權系統(tǒng)管理內存,編譯器在編譯時會根據一系列的規(guī)則進行檢查,如果違反了所有權規(guī)定,則程序不能通過編譯。
  • 在程序運行時,所有權系統(tǒng)不會減慢程序的運行速度,因為所有權規(guī)則的檢查是在編譯時進行的。

Stack vs Heap

在很多語言中,程序員不需要經常考慮到堆和棧,但在Rust這樣的系統(tǒng)編程語言中,一個值存儲在heap上還是stack上,會很大程度上影響語言的行為,所以這里先對堆和棧進行簡單介紹。

分配內存

棧(stack):

  • stack按值的接收順序來存儲,按相反的順序將它們移除,即后進先出(LIFO)。
  • 所有存儲在stack上的數據必須擁有已知的固定大小,添加數據叫做入棧,移除數據叫做出棧。
  • 將數據存放在stack時不需要尋找用來存儲數據的空間,因為那個位置永遠在stack的頂端。

堆(heap):

  • 在編譯時大小未知的數據或運行時大小可能發(fā)生變化的數據,必須存放在heap上。
  • 當把數據放入heap時,需要先在heap上分配對應大小的空間,即操作系統(tǒng)在heap中找到一塊足夠大的空間把它標記為在用,并將該空間的地址返回,后續(xù)訪問heap上的數據時,需要通過指針來定位。

訪問數據

  • 訪問heap中的數據比訪問stack中的數據慢,因為需要通過指針才能找到heap中的數據,屬于間接訪問。
  • 對于現代的處理器來說,由于緩存的緣故,如果指令在內存中的跳轉的次數越少,那么速度就越快。
  • stack中數據存放的距離比較近,而heap中數據存放的距離比較遠,因此訪問heap中的數據比訪問stack中的數據慢。

所有權存在的原因

所有權存在的原因,就是為了管理存放在heap上的數據:

  • 跟蹤代碼的哪些部分正在使用heap上的哪些數據。
  • 最小化heap上的重復數據量。
  • 清理heap上未使用的數據,以避免內存泄露。

所有權規(guī)則

所有權規(guī)則

所有權的規(guī)則如下:

  • Rust中的每一個值都有一個對應的變量作為它的所有者。
  • 在同一時間內,每個值有且只有一個所有者。
  • 當所有者離開自己的作用域時,它持有的值就會被釋放掉。

變量作用域

變量作用域

  • 作用域(scope)指的是程序中一個項,在程序中的有效范圍。

在下面的代碼中,變量s從第三行聲明開始變得可用,在第五行代碼塊結束時離開作用域變得不可用。如下:

fn main() {
    //s不可用
    let s = "hello"; //s可用
                     //可以對s進行相關操作
} //s作用域到此結束,s不再可用

String類型

String類型

為了后續(xù)講解Rust的所有權,我們需要借助一個管理的數據存儲在heap上的類型,這里選擇String類型。

  • Rust中基礎的標量類型的數據是存儲在stack上的,而String類型比這些類型更加復雜,它管理的數據是存儲在heap上。
  • String類型管理的數據存儲在heap上,因此String類型能夠存儲在編譯時未知大小的文本,即String類型是可變的。
  • Rust中有兩種字符串類型,一種是字符串字面值,它是不可變的,另一種就是String類型,其管理的字符串是可變的。

String類型由三部分組成:

  • ptr:指向存放字符串內容的指針。
  • len:表示字符串的長度。
  • capacity:表示字符串的容量。

String類型的這三部分數據存儲在stack上,而String管理的字符串則存儲在heap上。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

創(chuàng)建String字符串

創(chuàng)建String字符串可以使用from函數,該函數可以基于字符串字面值來創(chuàng)建String字符串。如下:

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

    s.push_str(" String");

    println!("{}", s); //Hello String
}

說明一下:

  • 代碼中的::,表示from是String類型的命名空間下的函數。
  • String類型的push_str方法,可以將指定的字符串插入到String字符串的后面。

內存與分配

內存與分配

  • 對于字符串字面值來說,在編譯時就知道它的內容了,其文本內容會直接被硬編碼到最終的可執(zhí)行文件中,因此訪問字符串字面值快速且高效。
  • 而String類型為了支持可變性,需要在heap上分配內存來保存編譯時未知的文本內容,其必須在運行時向內存分配器請求內存,當我們處理完String時再將內存返回給分配器。
注:在Rust中,當某個值離開作用域時,會自動調用drop函數釋放內存。

變量與數據交互的方式:移動(Move)

在Rust中,多個變量可以采取不同的方式與同一數據進行交互。如下:

fn main() {
    let x = 10;
    let y = x;

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

說明一下:

  • 代碼中先將整數字面值10綁定到了變量x,接著生成了變量x的拷貝,并將其綁定到變量y。
  • 因為整數是已知固定大小的簡單值,因此x和y都被放入到了棧中,在賦值后兩個變量都有效。

如果將代碼中的整數換成String,那么運行程序將會產生報錯。如下:

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

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

報錯的原因就是我們借用了已經被移動的值x。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

現在我們來分析一下代碼,剛開始聲明變量x的時候,整體布局如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

當把變量x賦值給變量y時,String的數據被拷貝了一份,但拷貝的僅僅是stack上的String元數據,而并沒有拷貝指針所指向的heap上的數據。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

當變量離開作用域時,Rust會自動調用drop函數釋放內存,為了避免這種情況下heap上的數據被二次釋放,因此Rust會讓賦值后的變量x失效,此時當x離開作用域時就不會釋放內存。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

這就是為什么在賦值后訪問變量x就會產生報錯的原因,因為此時變量x已經失效了。

說明一下:

  • stack上的拷貝可以視為淺拷貝,heap上的拷貝可以視為深拷貝。
  • 由于深拷貝的成本比較高,因此Rust不會自動進行數據的深拷貝。

變量與數據交互的方式:克隆(Clone)

如果確實需要對String的heap上的數據進行拷貝,那么可以使用String的clone方法。如下:

fn main() {
    let x = String::from("hello");
    let y = x.clone(); //深拷貝

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

拷貝后變量x和變量y都是有效的,因為String的clone方法會將stack和heap上的數據都進行拷貝。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

stack上的數據:拷貝(Copy)

  • Copy trait可以用在類似整型這樣存儲在棧上的類型,如果一個類型實現了Copy trait,那么舊的變量在賦值給其他變量后仍然可用。
  • 任何簡單標量的組合類型都可以實現Copy trait,任何不需要分配內存或某種形式資源的類型也都可以實現Copy trait。
  • 所有整數類型、浮點類型、布爾類型、字符類型都實現了Copy,此外,如果元組中所有字段都實現了Copy,那么這個元組也是可Copy的,比如(i32, i32)是可Copy的,而(i32, String)是不可Copy的。

說明一下:

  • 如果一個類型或該類型的一部分實現了Drop trait,那么Rust不允許它再實現Copy trait。
  • 如果一個類型要實現Copy trait,那么該類型也必須實現Clone trait。
  • String賦值后變量會失效,就是因為String沒有實現Copy trait,在賦值時會發(fā)生移動。

所有權與函數

所有權與函數

將值傳遞給函數和給變量賦值的原理類似:

  • 對于沒有實現Copy trait類型的變量來說,將值傳遞給函數時會發(fā)生移動,調用函數后變量失效。
  • 對于實現了Copy trait類型的變量來說,將值傳遞給函數時會發(fā)生拷貝,調用函數后變量仍然有效。

例如,下面代碼中變量s傳入函數時將發(fā)生移動,后續(xù)不再有效,而變量x傳入函數時將發(fā)生拷貝,后續(xù)仍然有效。如下:

fn main() {
    let s = String::from("hello world");
    take_ownership(s); //發(fā)生移動
	//println!("s = {}", s); //error

    let x = 10;
    makes_copy(x); //發(fā)生拷貝
    println!("x = {}", x); //x = 10;
}

fn take_ownership(some_string: String) {
    println!("{}", some_string); //hello world
}

fn makes_copy(some_number: i32) {
    println!("{}", some_number); //10
}

返回值與作用域

函數在返回值的過程中同樣會發(fā)生所有權的轉移。如下:

fn main() {
    let s1 = gives_ownership();

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

    let s3 = takes_and_gives_back(s2);
}

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string
}

代碼說明:

  • gives_ownership函數的作用是,創(chuàng)建了一個String,并將其所有權返回。
  • takes_and_gives_back函數的所用是,取得了一個String的所有權,然后再將其所有權返回。

引用與借用

引用與借用

  • 對于String類型來說,&String就是String類型的引用,我們將創(chuàng)建一個引用的行為稱為借用。
  • 一個類型的引用不會取得該類型變量的所有權,因此當引用離開作用域時不會釋放對應的空間。

例如,下面代碼中的calculate_length函數的參數類型是&String,該函數返回傳入String的長度但不獲取其所有權,函數調用后傳入的String變量仍然有效。如下:

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

    let len = calculate_length(&s1);

    println!("'{}'的長度是{}", s1, len); //'hello world'的長度是11
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

實際calculate_length函數的參數s就是一個指針,它指向了傳入的實參s1。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

說明一下:

  • &引用相反的操作是解引用,解引用運算符是*。

可變引用

可變引用

引用和變量一樣默認也是不可變的,要讓引用變得可變,同樣需要使用mut關鍵字。如下:

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

    let len = calculate_length(&mut s1);

    println!("'{}'的長度是{}", s1, len); //'hello world!!!'的長度是14
}

fn calculate_length(s: &mut String) -> usize {
    s.push_str("!!!"); //修改了引用的變量
    s.len()
}

但可變引用有一個重要的限制就是,在特定作用域內,一個變量只能有一個可變引用,否則會產生報錯。如下:

fn main() {
    let mut s = String::from("hello world");
    let s1 = &mut s;
    let s2 = &mut s; //error

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

Rust這樣做可以在編譯時就防止數據競爭,但可以通過創(chuàng)建新的作用域來允許非同時的創(chuàng)建多個可變引用,因為只要保證同一個作用域下一個變量只有一個可變引用即可。如下:

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

可變引用的其他限制

Rust中不允許一個變量同時擁有可變引用和不可變引用,否則會產生報錯。如下:

fn main() {
    let mut s = String::from("hello world");
    let r1 = &s;
    let r2 = &s;
    let s1 = &mut s; //error

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

原因: 不可變引用的要求其引用的值不能發(fā)生改變,而可變引用卻可以改變其引用的值,因此一個變量同時擁有可變引用和不可變引用,就是的不可變引用的作用失效了,但一個變量同時擁有多個不可變引用是可以的。

懸垂引用

懸垂引用(Dangling References)

懸垂引用指的是,一個指針引用了內存中的某個地址,但這塊內存可能已經釋放了。如下:

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

fn dangle() -> &String {
    let s = String::from("hello world");
    &s //懸垂引用
}

在Rust中編譯器確保引用永遠不會變成懸垂狀態(tài),因為編譯器會確保數據不會在其引用之前離開作用域,因此上述代碼會編譯報錯。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

說明一下: 報錯內容說缺少一個生命周期說明符,生命周期相關的內容會在后續(xù)博客中講解。

引用的規(guī)則

引用的規(guī)則

引用的規(guī)則如下:

  • 在任何時刻,一個變量只能有一個可變引用,或任意數量的不可變引用。
  • 引用必須一直有效。

切片

字符串切片

字符串切片

  • 除了引用之外,Rust還有另一種不持有所有權的數據類型,叫做切片(slice)。
  • 字符串切片就是指向字符串中一部分值的引用,切片形式為:&字符串變量名[開始索引..結束索引]。
  • 字符串切片中的開始索引指的是切片起始位置的索引值,結束索引指的是切片終止位置的下一個索引值。
  • 如果切片的起始位置是0,那么開始索引可以省略,如果切片的終止位置是字符串的長度,那么結束索引可以省略。
  • 字符串切片的類型是&str,字符串切片是不可變的。

例如,下面分別創(chuàng)建了字符串hello worldhello的切片和world的切片。如下:

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

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

    println!("hello = {}", hello); //hello = hello
    println!("world = {}", world); //world = world
}

切片中包含一個指針和一個長度,比如上述的world切片,其指針指向字符串索引為6的位置,其長度就是5。如下:

Rust所有權,Rust,1024程序員節(jié),rust,開發(fā)語言

切片在Rust中是非常有用的,比如獲取字符串中的第一個單詞,那么借助字符串切片可以編寫出如下代碼:

fn main() {
    let s = String::from("hello world");
    let word = first_word(&s);

    //s.clear(); //error: s已經存在一個不可變引用
    println!("word = {}", word); //word = hello
}

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

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

說明一下:

  • 如果不使用字符串切片,也可以通過返回字符串中第一個空格的索引來間接表示字符串中第一個單詞的位置,但此時這個索引是獨立于這個字符串存在的,當字符串中的內容被清除后這個索引就沒有意義了。
  • 而切片是不可變的,因此如果一個字符串存在一個切片,那么在這個切片沒有離開作用域之前,這個字符串中的內容是無法被修改的,因為Rust不允許一個變量同時擁有可變引用和不可變引用,否則會產生報錯。
  • as_bytes方法的作用是將String轉化為字節(jié)數組,以方便遍歷String的每一個字節(jié)來與空格進行比較。
  • iter方法的作用是在字節(jié)數組上創(chuàng)建一個迭代器,它將會返回字節(jié)數組中的每一個元素,而enumerate方法的作用是對iter的結果進行包裝,將這些元素作為元組的一部分來返回。enumerate返回的元組中,第一個元素是索引,第二個元素是集合中元素的引用。
  • 在for循環(huán)中,通過模式對enumerate返回的元組進行解構,由于元組中第二個元素是集合中元素的引用,因此item需要使用&。

注意:

  • 字符串切片的范圍索引必須發(fā)生在有效的UTF-8字符邊界內。
  • 如果嘗試從一個多字節(jié)的字符中創(chuàng)建字符串切片,程序會報錯并退出。

字符串字面值就是切片

字符串字面值的類型實際上就是字符串切片&str,這就是為什么字符串字面值不可變的原因,因為字符串切片&str就是不可變的。如下:

fn main() {
    let s = "hello world"; //s的類型是&str
}

將字符串切片作為參數

如果要將字符串切片作為函數的參數,那么最好將函數的參數類型定義為&str,而不是&String,這樣就能同時接收&String和&str的參數了,能夠使我們的API更加通用且不會損失任何功能。如下:

fn main() {
    let my_string = String::from("hello world");
    let word = first_word(&my_string); //接收&String

    let my_string_literal = "hello world";
    let word = first_word(my_string_literal); //接收&str
}

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

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

說明一下:

  • &String等價于整個String的切片,因此可以用&str接收,而字符串字面值的類型本來就是&str。

其他類型的切片

其他類型的切片

與字符串切片類似,其他類型也可以有切片,比如對于下面代碼中的數組來說,其切片類型就是&[i32]。如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-722807.html

fn main() {
    let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3]; //&[i32]類型
}

到了這里,關于Rust所有權的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Rust-所有權(ownership)

    Rust入門學習系列-Rust 的核心功能(之一)是 所有權(ownership)。引入這個概念是為了更好的管理計算機的內存。下面篇幅讓我們來研究下這個功能有什么神奇之處。 常見的編程語言中計算機內存管理方式: Java:Java使用Java虛擬機(JVM)來管理計算機內存。JVM有一個垃圾回收

    2024年02月19日
    瀏覽(31)
  • rust學習——棧、堆、所有權

    rust學習——棧、堆、所有權

    棧和堆是編程語言最核心的數據結構,但是在很多語言中,你并不需要深入了解棧與堆。 但對于 Rust 這樣的系統(tǒng)編程語言,值是位于棧上還是堆上非常重要, 因為這會影響程序的行為和性能。 棧和堆的核心目標就是為程序在運行時提供可供使用的內存空間。 棧 棧按照順序存

    2024年02月07日
    瀏覽(31)
  • Rust-所有權和移動語義

    Rust-所有權和移動語義

    拿C語言的代碼來打個比方。我們可能會在堆上創(chuàng)建一個對象,然后使用一個指針來管理這個對象: 接下來,我們可能需要使用這個對象: 然而,這段代碼之后,誰能猜得到,指針p指向的對象究竟發(fā)生了什么?它是否被修改過了?它還存在嗎,是否已經被釋放?是否有另外一個指

    2024年01月18日
    瀏覽(24)
  • Rust核心功能之一(所有權)

    Rust核心功能之一(所有權)

    目錄 1、什么是所有權? 1.1?所有權規(guī)則 ?1.2 變量作用域 1.3 String 類型 1.4 內存與分配 變量與數據交互的方式(一):移動 變量與數據交互的方式(二):克隆 只在棧上的數據:拷貝 1.5 所有權與函數 1.6 返回值與作用域 所有權(系統(tǒng))是 Rust 最為與眾不同的特性,對語言的

    2024年02月04日
    瀏覽(35)
  • 【rust】| 06——語言特性 | 所有權

    【rust】| 06——語言特性 | 所有權

    系列文章目錄 【rust】| 00——開發(fā)環(huán)境搭建 【rust】| 01——編譯并運行第一個rust程序 【rust】| 02——語法基礎 | 變量(不可變?)和常量 【rust】| 03——語法基礎 | 數據類型 【rust】| 04——語法基礎 | 函數 【rust】| 05——語法基礎 | 流程控制 【rust】| 06——語言特性 | 所有權 ?

    2024年02月04日
    瀏覽(29)
  • Rust語言從入門到入坑——(5)Rust 所有權

    Rust語言從入門到入坑——(5)Rust 所有權

    主要介紹Rust所有權的知識,涉及到變量的作用域,內存釋放機制,移動,克隆,引用等知識,很多知識是Rust語言特有機制。 所有權有以下三條規(guī)則: - Rust 中的每個值都有一個變量,稱為其所有者。 - 一次只能有一個所有者。 - 當所有者不在程序運行范圍時,該值將被刪除

    2024年02月10日
    瀏覽(19)
  • Rust語法:所有權&引用&生命周期

    Rust語法:所有權&引用&生命周期

    垃圾回收管理內存 Python,Java這類語言在管理內存時引用了一種叫做垃圾回收的技術,這種技術會為每個變量設置一個引用計數器(reference counter),來統(tǒng)計每個對象的引用次數。 一旦某個對象的引用數為0,垃圾回收器就會擇取一個時機將其所占用的空間回收。 以Python為例子

    2024年02月12日
    瀏覽(23)
  • 30天拿下Rust之所有權

    概述 ????????在編程語言的世界中,Rust憑借其獨特的所有權機制脫穎而出,為開發(fā)者提供了一種新穎而強大的工具來防止內存錯誤。這一特性不僅確保了代碼的安全性,還極大地提升了程序的性能。在Rust中,所有權是一種編譯時檢查機制,用于追蹤哪些內存或資源何時可

    2024年03月08日
    瀏覽(24)
  • 【跟小嘉學 Rust 編程】四、理解 Rust 的所有權概念

    【跟小嘉學 Rust 編程】四、理解 Rust 的所有權概念

    【跟小嘉學 Rust 編程】一、Rust 編程基礎 【跟小嘉學 Rust 編程】二、Rust 包管理工具使用 【跟小嘉學 Rust 編程】三、Rust 的基本程序概念 【跟小嘉學 Rust 編程】四、理解 Rust 的所有權概念 本章節(jié)將講解 Rust 獨有的概念(所有權)。所有權是 Rust 最獨特的特性,它使得 Rust 能夠

    2024年02月10日
    瀏覽(30)
  • Rust 基礎入門 —— 2.3.所有權和借用

    Rust 的最主要光芒: 內存安全 。 實現方式: 所有權系統(tǒng) 。 因為我們這里實際講述的內容是關于 內存安全的,所以我們最好先復習一下內存的知識。 然后我們,需要理解的就只有所有權概念,以及為了開發(fā)便利,進一步引出的引用借用概念。 內存作為存儲程序運行時數據

    2024年02月12日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包