Rust能力養成之(4):使用者的自由度
前言
上一篇,介紹了Rust語言的
-
條件與決策
-
Match表達
-
迴圈語句
這一篇,介紹Rust語言的使用者自定義資料型別
-
結構體
-
列舉
顧名思義,使用者自定義型別是由使用者自己參與定義,這些資料型別或者是對基本資料型別的封裝(wrapper),或者是對既有使用者自定義型別的進一步結合。通常有3種形式,學過C的同學想必會有印象,也就是所謂的結構體(structures->structs),列舉(enumerations->enums)和聯合(unions->union)。
不可否認,這些資料型別都提升了資料的表現力。在Rust中,所提及的前兩種使用者自定義型別,要比C中的結構體和列舉在功能上更為強大;而在第三項---聯合,與C近似,具體會在後續章節中再談。如上所提及,本文主要介紹結構體和列舉。
結構體(Structs)
在Rust中,我們可以宣告三種形式的結構體。其中最簡單的是unit struct,於此可以姑且稱之為單元結構,語法上是由struct關鍵字,對應名稱和一個分號組成。以下程式碼示例定義了一個單元結構。
單元結構(unit struct)
//unit_struct.rs
structDummyStruct;
fnmain(){
letvalue=DummyStruct;
}
非常簡單吧,該程式碼中定義了一個名為Dummy的單元結構。在main函式中,可以只使用其名稱來初始化該型別。變數value現在包含了Dummy的例項,並且在佔用系統資源方面是一個大小(size)為0的值。
在執行時,如果沒有與之相關的資料,單元結構不會索取任何空間。就其用途而言,主要表現為3種:
-
用於對沒有資料或狀態關聯的實體進行建模。
-
表示錯誤型別,因為結構本身就足以指代錯誤內容,已經不需要對其進行描述
-
表徵狀態機(state machine)實現中的狀態
元組結構(tuple struct)
這裡再談一下第二種結構體,所謂的元組結構,其與資料相關聯。再者,不需要對單個欄位命名,可以通過在其定義中的位置來引用。比如你正在為你的圖形應用程式編寫一個顏色轉換/計算庫,並想在程式碼中表示RGB顏色的數值。
我們看下如下程式碼:
//tuple_struct.rs
structColor(u8,u8,u8);
fnmain(){
letwhite=Color(255,255,255);
//Youcanpullthemoutbyindex
letred=white.0;
letgreen=white.1;
letblue=white.2;
println!("Redvalue:{}",red);
println!("Greenvalue:{}",green);
println!("Bluevalue:{}\n",blue);
letorange=Color(255,165,0);
//Youcanalsodestructurethefieldsdirectly
letColor(r,g,b)=orange;
println!("R:{},G:{},B:{}(orange)",r,g,b);
//Canalsoignorefieldswhiledestructuring
letColor(r,_,b)=orange;
println!("R:{},G:{},B:{}(orange)",r,g,b);
letColor(_,_,_)=orange;
println!("R:{},G:{},B:{}(orange)",r,g,b);
}
可見,
-
第2行,定義Color是這裡的元組結構體
-
第5行,為white變數賦值
-
第8-10行, 通過variable.
方式,訪問結構體變數域內引用位置,為對應變數賦值 -
第12-14行,列印相應值
-
第16行,為orange變數賦值
-
第19-20行,用解構方式訪問結構體中的個體資料,並列印結果
-
這顯然是訪問個體資料的有一種方式
-
-
第22-26,如果你懶得一個一個寫個體引數,可以用下劃線_來應付一下各自位置,相應的結果依舊可以被正確推斷並打印出來。
該程式碼結果如下:
常規結構體(Common Struct)
對屬性在四五個以內的資料進行建模時,元組結構體是一個理想的選擇;一旦超過這個資料範圍,就會影響到程式碼嗎的可讀性和語義推理。因此,對於具有三個以上欄位的資料型別,建議使用類似c的結構體,也就是要介紹的第三種形式,同時是最常用的一種。
我們看下以下程式碼:
//structs.rs
structPlayer{
name:String,
iq:u8,
friends:u8,
score:u16
}
fnbump_player_score(mutplayer:Player,score:u16){
player.score+=120;
println!("Updatedplayerstats:");
println!("Name:{}",player.name);
println!("IQ:{}",player.iq);
println!("Friends:{}",player.friends);
println!("Score:{}",player.score);
}
fnmain(){
letname="Alice".to_string();
letplayer=Player{name,
iq:171,
friends:134,
score:1129};
bump_player_score(player,120);
}
這個程式碼讀下來,可見,
-
第3-8行,該結構體定義方式與元組結構體相同。不同點在於是在大括號內進行較為正式的型別宣告和命名
-
第10-17行,定義對結構體資料進行操作的函式,可以看到第一個引數是可變繫結,讀者可以想想用意何在。
-
第20行,初始化變數name
-
第21-24行,初始化結構體變數player,仔細看下大括號內部的結構,後三個是名稱+冒號+賦值,而name的寫法是一種省略的方式,被稱為field init shorthand
程式碼結果如下:
常規結構體,或者簡稱就是結構體,與元組結構體相比,一個優勢就在於可以任意順序對結構體物件進行賦值。此外,結構體所佔的空間大小一般是所有個體屬性之和,這其中會存在資料對齊的情況,除此之外,再無額外空間花銷。
列舉(enums)
當需要對不同種類物件進行建模時,可以使用列舉,在定義過程中,每一種可能性或屬性稱為變體(variants),這些變體可以定義為是否包含資料,而所包含的資料可以是任何基本型別、結構、元組結構,甚至是列舉。
然而,如果是遞迴,比如有一個列舉Foo,還有一個儲存Foo的變體,則該變體需要在一個指標型別之後,以避免遞迴無限型別定義。因為列舉也可以在棧上建立,所需要有一個預先確定的所佔空間,而無限的型別定義無法在編譯時確定該數值。
我們看一個例子。
//enums.rs
#[derive(Debug)]
enumDirection{
N,
E,
S,
W
}
enumPlayerAction{
Move{
direction:Direction,
speed:u8
},
Wait,
Attack(Direction)
}
fnmain(){
letsimulated_player_action=PlayerAction::Move{
direction:Direction::N,
speed:2,
};
matchsimulated_player_action{
PlayerAction::Wait=>println!("Playerwantstowait"),
PlayerAction::Move{direction,speed}=>{
println!("Playerwantstomoveindirection{:?}withspeed{}",
direction,speed)
}
PlayerAction::Attack(direction)=>{
println!("Playerwantstoattackdirection{:?}",direction)
}
};
}
首先請讀者注意一下列舉定義的格式。再者,不難看出,這裡定義了兩個列舉,Direction和PlayerAction,基於此,可以建立例項,這些在主函式中已有體現,比如第21-23行
注意,列舉必須要初始化的,但是選擇任意一個變體就可以。根據給定的列舉值,進行匹配並執行相應結果,則是第25-34行的內容,不難看到其中的三種行為,Wait,Move和Attack。
最後,看下第3行的 #[derive(Debug)],該屬性為了保證println!()使用{:?}格式字串,是由Debug的特性生成方法來實現的。
程式碼結果如下:
結語
從函數語言程式設計的角度來看,結構體和列舉也被稱為代數資料型別(Algebraic Data Types ,ADTs),因為其可表示值的可能範圍可以使用代數規則表示。比如加法和乘法的列舉是各自變體運算值的範圍。後續還會對此再討論一下。
本篇介紹了兩種使用者自定義資料型別:結構體和列舉,其中已經體現了Rust語言的一部分設計重點。下一篇,會講一下與型別相關的函式功能與方法。
主要參考和建議讀者進一步閱讀的文獻
https://doc.rust-lang.org/book
1.Rust程式設計之道,2019, 張漢東
2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
3.Hands-On Data Structures and Algorithms with
4.Rust,2018,Claus Matzinger
5.Beginning Rust ,2018,Carlo Milanesi
6.Rust Cookbook,2017,Vigneshwer Dhinakaran