疫情最新情況今天白楊seo課程
Rust堆棧
Rust中各種類型的值默認(rèn)都存儲(chǔ)在棧中,除非顯式地使用Box::new()
將它們存放在堆上,但數(shù)據(jù)要存放在棧中,要求其數(shù)據(jù)類型的大小已知。對(duì)于靜態(tài)大小的類型,可直接存儲(chǔ)在棧上,如裸指針、布爾、字符、整數(shù)浮點(diǎn)數(shù),數(shù)組等。
動(dòng)態(tài)大小的(Vec
、string
)都是存堆的
一些注意事項(xiàng)
- 棧中的數(shù)據(jù)賦值給變量的時(shí)候,數(shù)據(jù)是直接放在棧中的。
- 類型的值都默認(rèn)放在棧中,所以創(chuàng)建引用的時(shí)候,引用的是棧里的值。
- 容器中保存的是原始類型的棧里的值或者指向堆數(shù)據(jù)的引用
- 字符串字面量,
static
靜態(tài)變量都會(huì)硬編碼嵌入到二進(jìn)制程序的全局內(nèi)存區(qū) const
定義的常量,會(huì)在編譯期間直接以硬編碼的方式內(nèi)聯(lián)插入到使用常量的地方,即,直接硬編碼到對(duì)應(yīng)代碼行。同時(shí)函數(shù)也可以內(nèi)聯(lián),即函數(shù)對(duì)應(yīng)代碼體會(huì)直接展開(kāi)并插入到調(diào)用函數(shù)的地方,省去調(diào)用函數(shù)的開(kāi)銷。
位置與值
位置:某一塊內(nèi)存位置,它有自己的地址,有自己的空間,有自己所保存的值。
值:存儲(chǔ)到位置中的數(shù)據(jù)(即保存在內(nèi)存中的數(shù)據(jù))
位置的產(chǎn)生
- 會(huì)產(chǎn)生變量的時(shí)候(初始化)
- 需要保存某個(gè)值的時(shí)候(函數(shù)調(diào)用參數(shù)和返回值)
- 產(chǎn)生新的值(引用,解引用)
let 語(yǔ)句
let a = 1;
a
:為變量名,也是對(duì)內(nèi)存位置的一個(gè)可讀代號(hào),編譯期間會(huì)被替換為更低級(jí)的代號(hào)或者直接為地址。也就是位置,是存值1
的一塊內(nèi)存。
每個(gè)位置就是它所放值的所有值,因?yàn)槊總€(gè)值都只能存放在一個(gè)位置中,所以每個(gè)值都只能有一個(gè)所有者。let v = vec![1, 2, 3, 4];
v
:位置,代表?xiàng)V械囊粔K內(nèi)存,值是一個(gè)指針地址,實(shí)際數(shù)據(jù)是放在堆里的。
引用
Rust的引用是一種原始數(shù)據(jù)類型,位置仍然是棧里,保存的值和指針一樣,是一個(gè)地址。該地址指向了**位置(**也就是前面的a
和v
)
如
let n = 33; // 假設(shè)n的地址為0x234
let nn = &n; // 假設(shè)nn的地址為0x123
那么,nn
的位置是0x123
,它存的值是0x234
,也就是n
的地址。
編譯器維護(hù)棧內(nèi)存,所以它知道棧中的某個(gè)內(nèi)存是否安全,而堆內(nèi)存由程序員自己負(fù)責(zé),程序員自己的行為是無(wú)法保證安全的。
所以,Rust的行為模式是將是涉及到內(nèi)存安全的概念扔到棧上,讓程序員遠(yuǎn)離對(duì)堆的操作。所以,允許允許對(duì)棧中同一個(gè)數(shù)據(jù)的多個(gè)指向,不允許對(duì)堆中同一個(gè)內(nèi)存的多個(gè)指向,即變量存在多個(gè)引用,但所有權(quán)只能有一個(gè)。
位置的屬性
位置的屬性和狀態(tài)都由編譯器在編譯期進(jìn)行維護(hù)。
位置有類型,有標(biāo)記(是否被引用,可變引用還是不可變,共享還是獨(dú)占等等),根據(jù)位置的類型是否實(shí)現(xiàn)Copy Trait
來(lái)決定該位置的是拷貝還是移走。
所有權(quán)和借用
變量作用域
我們知道rust 變量在脫離作用域之后就會(huì)被銷毀,但事實(shí)是,變量在跳出作用域時(shí),會(huì)自動(dòng)Drop Trait
的drop
函數(shù)來(lái)銷毀內(nèi)存中堆和棧的數(shù)據(jù),全局內(nèi)存中的數(shù)據(jù)是從程序啟動(dòng)到終止期間一直存在。
另外rust的作用域?yàn)橐粚?duì)大括號(hào){}
,大括號(hào)的作用域是可以訪問(wèn)大括號(hào)外部的變量,而在函數(shù)的作用域內(nèi)則不行,這被稱為捕獲環(huán)境,函數(shù)是不能捕獲環(huán)境的,而大括號(hào)可以捕獲環(huán)境。
數(shù)據(jù)的拷貝
由于所有權(quán)問(wèn)題和變量脫離作用域而引起的內(nèi)存二次釋放問(wèn)題,rust是不允許有兩個(gè)指針同時(shí)指向同一塊內(nèi)存的。所以rust 沒(méi)有淺拷貝和深拷貝的概念,取而代之的是move
、copy
和clone
。
move
:也就是轉(zhuǎn)移所有權(quán),涉及到的過(guò)程是拷貝到目標(biāo)變量,同時(shí)會(huì)將原來(lái)的變量設(shè)置到未初始的狀態(tài)。rust 默認(rèn)使用的就是move
。
當(dāng)使用值的時(shí)候,就會(huì)產(chǎn)生位置,那么就會(huì)發(fā)生移動(dòng)。解引用,字段訪問(wèn),索引訪問(wèn)等都會(huì)隱式移動(dòng)。
copy
:和move
的區(qū)別就是,拷貝之后原來(lái)的變量還是可以用。如果要使用copy
,就需要要拷貝的數(shù)據(jù)類型實(shí)現(xiàn)了Copy Trait
,手動(dòng)實(shí)現(xiàn)的時(shí)候需要同時(shí)實(shí)現(xiàn)Clone Trait
。clone
:clone
和copy
很接近,區(qū)別在于,- Copy時(shí),只拷貝變量本身的值,如果這個(gè)變量指向了其它數(shù)據(jù),則不會(huì)拷貝其指向的數(shù)據(jù)。
- Clone時(shí),拷貝變量本身的值,如果這個(gè)變量指向了其它數(shù)據(jù),則也會(huì)拷貝其指向的數(shù)據(jù)。
函數(shù)調(diào)用之后也是會(huì)轉(zhuǎn)移所有權(quán)的,有時(shí)候這樣是很不方便的,所以在傳參的時(shí)候可以傳遞到變量的引用,引用時(shí)保存在棧里,也實(shí)現(xiàn)了Copy Trait
,這樣效率會(huì)更高。
可變引用的排他性
不可變引用是可以共存的,但是可變引用具有排他性,在同一作用域同一數(shù)據(jù)只能有一個(gè)。
這里的排他性,應(yīng)該看作一把獨(dú)占鎖,在當(dāng)前作用域內(nèi),從第一次使用可變引用開(kāi)始創(chuàng)建這把獨(dú)占鎖,之后無(wú)論使用原始變量(即所有權(quán)擁有者)、可變引用還是不可變引用都會(huì)搶占這把獨(dú)占鎖,以保證只有一方可以訪問(wèn)數(shù)據(jù),每次搶得獨(dú)占鎖后,都會(huì)將之前所有引用變量給鎖住,使它們變成不可用狀態(tài)。當(dāng)離開(kāi)當(dāng)前作用域時(shí),當(dāng)前作用域內(nèi)的所有獨(dú)占鎖都被釋放。
回顧一下可變引用的幾個(gè)性質(zhì)
- 同一作用域,特定數(shù)據(jù)只能有一個(gè)可變引用
- 可變借用不能用于不可變借用上
- 有了可變借用就不能再有不可變借用
- 引用作用域和變量作用域不一樣,它的結(jié)束位置再最后一次使用的位置
自從第一次使用可變引用導(dǎo)致獨(dú)占鎖出現(xiàn)后,可以隨時(shí)使用原始變量、可變引用或不可變引用來(lái)?yè)尓?dú)占鎖,但搶鎖后以前的引用變量就不能再用,且當(dāng)前持有的鎖也可以隨時(shí)被搶走。不可變引用搶占之后所有的包括自身都是不可用的,但再次使用可變引用搶占鎖之后,該可變引用是可用的。
一切都由程序員控制,程序員可以在任意代碼位置通過(guò)原始變量或引用來(lái)?yè)屾i。
模式匹配
rust中可分為
- 不可反駁的模式(irrefutable):一定會(huì)匹配成功,否則編譯錯(cuò)誤,如
let
賦值,for
迭代,函數(shù)傳參等。 - 可反駁的的模式(refutable):可以匹配成功,也可以匹配失敗,匹配失敗的結(jié)果是不執(zhí)行對(duì)應(yīng)分支的代碼,如
if let
和while let
match
匹配支持兩個(gè)模式
- 當(dāng)明確給出分支的Pattern時(shí),必須是可反駁模式,這些模式允許匹配失敗
- 使用
_
作為最后一個(gè)分支時(shí),是不可反駁模式,它一定會(huì)匹配成功 - 如果只有一個(gè)Pattern分支,則可以是不可反駁模式,也可以是可反駁模式
再談Trait
組合
Trait
最基本的作用是從多種類型中抽取出共性的屬性或方法,主要表現(xiàn)為泛型數(shù)據(jù)類型。可以理解為,它描述了一種通用的功能,功能都要求具有某些特殊的行為,同時(shí)功能可以被很多種類型實(shí)現(xiàn)。
同樣,一個(gè)類型也可以實(shí)現(xiàn)很多種Trait
,組合出很多功能,這和一般面向?qū)ο缶幊陶Z(yǔ)言的繼承有所不同,不用繼承冗余的功能,而更加的自由。
組合和繼承的關(guān)系可以理解為 **has a **和 is a 的關(guān)系。
特征對(duì)象
也就是具有某個(gè)特征功能的類的實(shí)例。
上篇提到過(guò),Duck Typing ,也就是只需要叫起來(lái)想鴨子,就可以當(dāng)成鴨子來(lái)使用。(只需要你會(huì)打螺絲,不管你是不是大學(xué)生。)
這里的意思就是,實(shí)現(xiàn)了某個(gè)特征的眾多對(duì)象都具有該功能,而由于 Trait
自身不能當(dāng)作數(shù)據(jù)類型來(lái)用(因?yàn)?#xff0c;可能一種類型實(shí)現(xiàn)了很多中 Trait
,顯然無(wú)法用一種 Trait
來(lái)代替這種數(shù)據(jù)類型)。因此就誕生了 Trait Object
,也就是將實(shí)現(xiàn)了 Trait A
的類型 B,C,D 當(dāng)作 Trait A
的 Trait Object
使用。(可以類比為繼承里的父類)
Trait object
的創(chuàng)建是通過(guò)&dyn T
或者指針Box<dyn T>
,Rc<dyn T>
等等
本質(zhì)就是由于 Trait object
的大小是不定的,所以選擇將引用存在棧中,包含兩部分?jǐn)?shù)據(jù)
- 指向數(shù)據(jù)的指針:指向?qū)崿F(xiàn)了
Trait
的具體類型的實(shí)例, - 指向一個(gè)虛表 vtable 的指針:因?yàn)閷?shí)現(xiàn)了
Trait
的類型有很多,而每個(gè)類型擁有的方法時(shí)各不相同的,所以需要一個(gè)虛表來(lái)區(qū)分保存。虛表中保存了實(shí)例可以調(diào)用的,實(shí)現(xiàn)的來(lái)自特征Trait
的方法。當(dāng)該對(duì)象調(diào)用方法時(shí),直接從虛表中找到方法,然后調(diào)用。
其他
Struct
和Enum
類型需要手動(dòng)實(shí)現(xiàn)Trait
,即,使用#[derive()]
- 特征是支持繼承的,如
trait B{}
trait A: B{}
當(dāng)類型想實(shí)現(xiàn) Trait A
的時(shí)候,需要要求同時(shí)實(shí)現(xiàn) Trait B
參考
Rust入門(mén)秘籍