Rust 的列舉型別是如何實現包含不同資料型別的?

時間 2021-06-02 02:43:52

1樓:Jason Hu

去年夏天改過rust編譯器,目前了解的應該還不算過期。可能有點記憶不太準確的地方,望見諒。在rustc中,編譯流程大致如下:

Text:程式原始碼

AST:parsing

HIR:high-level intermediate representation,通過了type checking。相當於AST + type info

MIR:middle-level intermediate representation,通過borrow checking,是乙個control diagram

backend:一般是LLVM IR,然後通過LLVM編譯到可執行檔案

首先,rustc中每個型別都有幾個重要屬性,在原始碼中經常會看見:

ZST (zero sized type) 也就是不需要任何記憶體表示的型別,這種型別只有乙個例項,並且對應的值固定;

uninhabited type也就是不存在例項的型別,如!。

rustc還追蹤乙個型別的連續未定義域,最終用到乙個叫niche optimization的優化上。

在rustc中,每個型別的size都會被追蹤,以上是前提。這些型別資訊在HIR都會被確定下來並且得知。簡單來說,rustc的codegen和C++差不多,都是每個例項生成乙個二進位制函式,命名問題通過mangling來解決。

在HIR到MIR的轉化時存在乙個monomorphization的過程:

在這個過程當中,所有的泛型都會被例項化,所產生的MIR是沒有泛型的(monomorphized)[1]。當從MIR轉換到LLVM IR時,rustc只需要考慮已經例項化的型別即可。這裡有幾種情況[2]:

一些已知基本型別需要hard code,這些型別為整型,浮點,char,諸如此類。注意builtin types不一定都如整型每乙個bit pattern都有意義。比方說bool是1 byte,但是只有兩個值0和1,所以rustc會記下bool的值域是0-1,而2-255為未定義。

struct基本上是遞迴下去。但是rustc中有個優化就是基於已知的size資訊,排列組合出乙個需要最少padding的field的順序。

引用和指標存在乙個thin和fat pointer的區別,這裡有點複雜,我不太記得,按下不表。

enum則是這個問題問的。已經有答主給出了一些optimization,我這裡再強調這中間的兩種情況和再解釋清楚其中乙個非常重要的優化,niche optimization。

首先最基本的是tagged union。這個情況下,rust一般再加乙個整型(一般是4 byte,有例外)用作tag,然後每個case都是乙個struct,最後在union起來。比方說[3]

enum

Message

,ChangeColor

(i32

,i32

,i32),}

在C中大概寫成

struct

Message

Quit

;struct

Move

;struct

ChangeColor;}

cases;}

這是最基礎的形式。

但是這種形式卻是不盡如人意。比如Option<&i32> 如用tagged union的話則在64bit系統用到了8 + 4 = 12 bytes,多用了4bytes。因為&i32不可能是null,我們完全可以將其編譯到8 bytes。

niche optimization則解決了這個問題。給定下面的enum

enum

Foo這種形式的enum只有乙個case有field。首先C1按照struct算出合適的field的順序。每個field找出(連續)未定義域最大的。

比方說如果C1有bool和&i32,bool從2-255未定義而&i32只有0,那麼rustc則選出這個bool的field,稱之為niche。

然後再比較這個未定義域的大小k與m-1,如m-1大,則niche optimization不成立,只能用tagged union。否則, 從niche的未定義域中由小選出m-1個值代表C2到Cm。這也可以得出Foo 的niche及其未定義域。

未定義域的大小變為k-(m-1)。

比方說enum

Foo可以被優化成

struct

Foo當_2為0或者1時Foo表示C1,為2時表示C2,為3時表示C3。 後兩種情況下的_1的值未定義。

這裡要注意的是,每個泛型例項的layout都不一定一樣。比方說Option表示為tagged union因為i32沒有未定義域。但是Option<&i32>通過niche optimization直接變成乙個指標,因為&i32在null為定義,所以Nonecase編譯成了null。

當然,其中還有其他優化,如ZST在編譯中擦去等細節,可以自行翻原始碼檢視。

2樓:默可思

題主自己回答的部分說對了乙個tag,但是並不需要指標。可以看看死靈書對各種資料結構記憶體布局的說法:https://

doc.rust-lang.org/nomicon/repr-rust.html

可以把enum看成乙個struct,它有乙個成員叫tag,然後其他的成員就是data。根據不同的tag來解釋data是什麼型別的資料,把這個data看成是c裡的union就行。

3樓:

Enum的每個成員不是型別,而是值。

Enum 是帶tag的Union。

每個Enum就是乙個型別。

它是乙個復合型別,就像struct。Enum裡面的列舉值包含的型別構造器,可能你看上去是不像不同型別,但每次例項化只可能是唯一乙個型別例項。

你想多了。

建議看看Rust的書。

rust中如何實現返回可變引用的迭代器?

Jason5Lee 找到乙個trade off pubstruct RowIter a impl a Iterator forRowIter a Nugine 沒有什麼所有權問題是 Rc 不能解決的,如果有,那就再套一層。開完玩笑,讓我們看看為什麼 Safe Rust 中無法實現題目中的邏輯。迭代器...

動態型別如何實現在執行時變換變數的型別

babypapa 有別人已經回答的很不錯了,我只補充一點,你理解的乙個誤區是動態語言能夠更換型別,各種動態語言的toString之類的介面,其實是返回乙個新的變數,並不是改變乙個變數的型別。 根據我的了解就是指標實現,但是對指標做了乙個小封裝,給它附加了乙個型別的資訊。通過這個型別資訊就能在執行的時...

go語言中匯入包名為何是字串型別的?

鄧毅 import 後面跟的字串是包的路徑,這個路徑可以是任何作業系統可以接受的相對路徑,相對於 GOPATH中的任何乙個路徑 再加上 src 等 這裡必須用字串,因為裡面可以包含空格或者其他 Go 不認識的字元。通常,直接引用開源的 package 的話,這裡面一定包含了至少乙個 例如 http ...