2020 Rust 入門(6) 所有權(quán)

rust.jpeg

之前我們了解 java、javascript 和 cpp 這些語言對內(nèi)存管理無非是由語言通過 GC 來管理內(nèi)存還是由 developer 來管理內(nèi)存,而今天 rust 給我們帶來一種的新的內(nèi)存管理方式。

rust 通過所有權(quán)機(jī)制來管理內(nèi)存,編譯器在編譯就會(huì)根據(jù)所有權(quán)規(guī)則對內(nèi)存的使用進(jìn)行檢查來回收內(nèi)存。

堆和棧

是兩種數(shù)據(jù)結(jié)構(gòu),都是數(shù)據(jù)項(xiàng)按序排列的數(shù)據(jù)結(jié)構(gòu),棧是個(gè)特殊的存儲(chǔ) 區(qū),主要功能是暫時(shí)存放數(shù)據(jù)和地址,堆是隊(duì)列優(yōu)先,先進(jìn)先出(FIFO—first in first out)而棧是先進(jìn)后出(FILO—First-In/Last-Out)。

stack-heap.png

有關(guān)的詳細(xì)信息,請參照詳解 JVM 的機(jī)制

作用域

有關(guān)作用域,作用域與變量相關(guān),也就是變量作用的范圍,通過劃分一定范圍,變量只在這個(gè)范圍(作用域)內(nèi)有效。

fn main() {
    let x:i32 = 1;
    let y:i32 = 2;
    println!("x = {}",x);
    println!("y = {}",y);
}

我們輸出 x 和 y 的值,當(dāng)我們用一對大括號(hào)(定義范圍)來將 y 變量括起來,在大括號(hào)內(nèi) y 是有效,當(dāng) y 離開大括號(hào)限定的作用域外,就被垃圾回收了。所以在定義 y 的大括號(hào)外我們無法訪問到 y 所有對應(yīng)變量的。

fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
    }
    println!("y = {}",y);
}
println!("y = {}",y);
  |                       ^ help: a local variable with a similar name exists: `x`

表示 y 有效區(qū)域(作用域)只在作用域內(nèi)有效,出作用域就被回收。

fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
        println!("y = {}",y);
    }
}

對于指定確定類型的變量通常會(huì)被分配到棧內(nèi)存上,例如 x 和 y 已經(jīng)指定了類型 i32 所有分配的內(nèi)存大小也是固定所有分配在棧上。

fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
        println!("y = {}",y);
    }
    {
        let str = String::from("hello");
        println!("str = {}",str);
    }
}

這里 str 定義在堆上,String 類型字符串類型,編譯器是無法確定 Str 的大小的,例如我們可以通過 push_str來改變字符串。


fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
        println!("y = {}",y);
    }
    {
        let mut str = String::from("hello");
        str.push_str(" world");
        println!("str = {}",str);
    }
}
fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
        println!("y = {}",y);
    }
    {
        let str1 = String::from("hello");
        //str1.push_str(" world");
        println!("str1 = {}",str1);
        
        let str2 = str1;
        println!("str2 = {}",str2);
        println!("str1 = {}",str1);
    }
}

通過上面代碼大家知道為什么 String 大小不是固定的了。所以編譯器將 str 分布

  • prt 指針
  • len 長度
  • capacity 擴(kuò)展能力
圖-1

在定義 str1 變量,變量 str1 保存一個(gè)指向內(nèi)存地址指針,也就是看成是內(nèi)存的引用。

圖-2

在 cpp 語言我們通過將變量 str1 賦值給 str2,那么 str1 和 str2 保存指針指向同一個(gè)內(nèi)存地址,當(dāng) str1 和 str2 離開作用域就會(huì)被回收,但是因?yàn)樗麄兌贾赶蛲粔K內(nèi)存,所以這塊內(nèi)存會(huì)被釋放兩次所有發(fā)生錯(cuò)誤。

圖-3

在 rust 當(dāng)將 str1 賦值給 str2 不再是 str1 和 str2 同時(shí)指向同一塊內(nèi)存,而是將 str1 對這塊內(nèi)存所有權(quán)將給了 str2。

所以在當(dāng) str1 將所有權(quán)交給了 str2 后再次打印 str1 就會(huì)報(bào)錯(cuò)。

println!("str1 = {}",str1);
   |                              ^^^^ value borrowed here after move

這里所說 move 將所有權(quán)從 str1 移至 str2。

移動(dòng)

這里不得不說一下 rust 報(bào)錯(cuò)信息很準(zhǔn)確詳細(xì)的,這也是他能夠得到大家喜歡的一個(gè)原因吧。有點(diǎn)類似 cpp 的淺拷貝,也就是復(fù)制指針同時(shí)指向同一塊內(nèi)存。我們知道在 String 類型離開作用域時(shí)候會(huì)調(diào)用 drop 方法來釋放內(nèi)存,因?yàn)?str1 和 str2 都指向同一塊內(nèi)存所以當(dāng)離開作用域,他們指向內(nèi)存將會(huì)被釋放 2 次。所以 rust 語言在復(fù)制,move 后 str1 就無效,并不不是指向同一,所以... str1 是無效,所以發(fā)生上面報(bào)錯(cuò)。rust 通過move 指針來解決了上面內(nèi)存釋放兩次問題


fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
        println!("y = {}",y);
    }
    {
        let str1 = String::from("hello");
        //str1.push_str(" world");
        println!("str1 = {}",str1);
        
        let str2 = str1;
        println!("str2 = {}",str2);
        // println!("str1 = {}",str1);
    }
}

clone


fn main() {
    let x:i32 = 1;
    {
        let y:i32 = 2;
        println!("x = {}",x);
        println!("y = {}",y);
    }
    {
        let str1 = String::from("hello");
        //str1.push_str(" world");
        println!("str1 = {}",str1);
        
        let str2 = str1;
        println!("str2 = {}",str2);
        // println!("str1 = {}",str1);
        
        //clone
        let str3 = str2.clone();
        println!("str2 = {}",str2);
        println!("str3 = {}",str3);
    }
}

棧上數(shù)據(jù)拷貝

fn main() {
    let a  = 1;
    let b  = a;
    println!("a = {}, b= {}",a,b);
}

在棧上變量直接拷貝,分配在棧上只有就是clone行為,叫 copy 的特征。只要類型實(shí)現(xiàn) copy 特征,在賦值其他變量依然可以使用,常用具有 copy 特征類型,所有整型、浮點(diǎn)型、布爾型、字符類型和元組。

函數(shù)和作用域

fn take_ownership(str:String){
    println!("{}",str);
}

fn make_copy(a:i32){
    println!("a = {}",a)
}
fn main() {
    let str1 = String::from("hello");
    take_ownership(str1);
    
    let x = 5;
    make_copy(x);
}
fn main() {
    let str1 = String::from("hello");
    take_ownership(str1);
    
    println!("{}",str1);
    
    let x = 5;
    make_copy(x);
}
fn main() {
    let str1 = String::from("hello");
    take_ownership(str1);
    
    // println!("{}",str1);
    
    let x = 5;
    make_copy(x);
    println!("x = {}",x);
}
fn take_ownership(str:String)->String{
    println!("{}",str);
    str
}
fn main() {
    let str1 = String::from("hello");
    let str2 = take_ownership(str1);
    
    println!("{}",str2);
    
    let x = 5;
    make_copy(x);
    println!("x = {}",x);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一:在JAVA中,有六個(gè)不同的地方可以存儲(chǔ)數(shù)據(jù): 1. 寄存器(register)。 這是最快的存儲(chǔ)區(qū),因?yàn)樗?..
    辰321閱讀 504評(píng)論 0 0
  • ———————————————回答好下面的足夠了---------------------------------...
    恒愛DE問候閱讀 1,843評(píng)論 0 4
  • 1.棧(stack)與堆(heap)都是Java用來在Ram中存放數(shù)據(jù)的地方。與C++不同,Java自動(dòng)管理?xiàng):投?..
    陌上花開Y可緩緩歸矣閱讀 1,059評(píng)論 0 2
  • 多線程、特別是NSOperation 和 GCD 的內(nèi)部原理。運(yùn)行時(shí)機(jī)制的原理和運(yùn)用場景。SDWebImage的原...
    LZM輪回閱讀 2,124評(píng)論 0 12
  • 寶寶每次洗澡都哭,是爸爸媽媽最頭疼的問題。天氣一天比一天暖,尤其到了夏天,每天給寶寶洗澡是寶爸寶媽必修的功課。那么...
    西雅媽媽閱讀 665評(píng)論 0 4

友情鏈接更多精彩內(nèi)容