坑愈來愈深了,在坑裏的同窗讓我看到大家的雙手!html
前面咱們聊過了Rust最基本的幾種數據類型。不知道你還記不記得,若是不記得能夠先複習一下。上一個坑挖好之後,有同窗私信我說坑太深了,下來的時候差點崴了腳。我只能對他說抱歉,下次還有可能更深。不過這篇文章不會那麼深了,本文我將帶你們探索Structs和Enums這兩個坑,沒錯,是雙坑。是否是很驚喜?好了,言歸正傳。咱們先來介紹Structs。git
Structs在許多語言裏都有,是一種自定義的類型,能夠類比到Java中的類。Rust中使用Structs使用的是struct關鍵字。例如咱們定義一個用戶類型。github
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
複製代碼
初始化時能夠直接將上面對應的數據類型替換爲正確的值。編程
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
複製代碼
下面仔細觀察這email: email
和username: username
這兩行代碼,有沒有以爲有點麻煩?,若是User的全部屬性值都是從函數參數傳進來,那麼咱們每一個參數名都要重複一遍。還好Rust爲咱們提供了語法糖,能夠省去一些代碼。編程語言
對於上面的初始化代碼,咱們能夠作一些簡化。函數
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
複製代碼
你能夠認爲這是Rust的一個語法糖,當變量名和字段名相同時,初始化Struct的時候就能夠省略變量名。讓開發者沒必要作過多無心義的重複工做(寫兩遍email)。ui
除了上面的語法糖之外,在建立Struct時,Rust還提供了另外一個語法糖,例如咱們新建一個user2,它只有郵箱和用戶名與user1不一樣, 其餘屬性都相同,那麼咱們可使用以下代碼:spa
#![allow(unused_variables)]
fn main() {
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
}
複製代碼
這裏的..user1
表示剩下的字段的值都和user1相同。code
接下來再來介紹兩個特殊形式的Struct,一種是Tuple Struct,定義時與Tuple類似htm
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
複製代碼
它與Tuple的不一樣在於,你能夠賦予Tuple Struct一個有意義的名字,而不僅是無心義的一堆值。須要注意的是,這裏咱們定義的Color和Point是兩種不一樣的類型,它們之間不能互相賦值。另外,若是你想要取得Tuple Struct中某個字段的值,和Tuple同樣,使用.
便可。
這裏還有一種特殊的Struct,即沒有字段的Struct。它叫作類單元結構(unit-like structs)。這種結構體通常用於實現某些特徵,但又沒有須要存儲的數據。
方法和函數很是類似,不一樣之處在於,定義方法時,必須有與之關聯的Struct,而且方法的第一個參數必須是self。咱們先來看一下如何定義一個方法:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
複製代碼
咱們提到,方法必須與Struct關聯,這裏使用impl
關鍵字定義一段指定Struct的實現代碼,而後在這個代碼塊中定義Struct相關的方法,注意咱們的area方法符合規則,第一個參數是self。調用時只須要用.
就能夠。
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
rect1.area();
}
複製代碼
這裏的&self
實際上是代替了rectangle: &Rectangle
,至於這裏爲何要使用&符號,咱們在前文已經作了介紹。固然,這裏self也不是必需要加&符號,你能夠認爲它是一個正常的參數,根據須要來使用。
有些同窗可能會有些困惑,咱們已經有了函數了,爲何還要使用方法?這其實主要是爲了代碼的結構。咱們須要將Struct實例能夠作的操做都放到impl實現代碼塊中,方便修改和查找。而使用函數則可能存在開發人員隨便找個位置來定義的尷尬狀況,這對於後期維護代碼的開發人員來說將是一種災難。
如今咱們已經知道,方法必須定義在impl代碼塊中,且第一個參數必須是self,但有時你會在Impl代碼塊中看到第一個參數不是self的,並且Rust也容許這種行爲。
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
}
複製代碼
這是什麼狀況?剛纔說的不對?其實否則,這種函數叫作相關函數(associated functions)。它仍然是函數,而不是方法而且直接和Struct相關,相似於Java中的靜態方法。調用時直接使用雙冒號(::
),咱們以前見過不少次的String::from("Hi")
就是String的相關函數。
最後提一點,Rust支持爲一個Struct定義多個實現代碼塊。可是咱們並不推薦這樣使用。
至此,第一個坑Struct就挖好了,接下來就是第二個坑Enum。
不少編程語言都支持枚舉類型,Rust也不例外。所以枚舉對於大部分開發人員來講並不陌生,這裏咱們簡單介紹一些使用方法及特性。
先來看一下Rust中如何定義枚舉和獲取枚舉值。
enum IpAddrKind {
V4,
V6,
}
let six = IpAddrKind::V6;
let four = IpAddrKind::V4;
複製代碼
這裏的例子只是最簡單的定義枚舉的方法,每一個枚舉的值也能夠關聯其餘類型的的值。例如
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
複製代碼
此外,Enum也能夠像Struct擁有impl代碼塊,你也能夠在裏面定義方法。
Option是Rust標準庫中定義的一個枚舉。若是你用過Java8的話,必定知道一個Optional類,專門用來處理null值。Rust中是不存在null值的,由於它太容易引發bug了。但若是確實須要的時候怎麼辦呢,這就須要Option枚舉登場了。咱們先來看一看它的定義:
enum Option<T> {
Some(T),
None,
}
複製代碼
很簡單對不對。它是一個枚舉,只有兩個值,一個是Some,一個是None,其中Some還關聯了一個類型T的值,這個T相似於Java中的泛型,即它能夠是任意類型。
在使用時,能夠直接使用Some或None,前面不用加Option::
。當你使用None時,必需要指定T的具體類型。
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
複製代碼
須要注意的是Option<T>與T並非相同的類型。你能夠在官方文檔中查看從Option<T>中提取出T的方法。
Rust有一個很強大的流程控制操做叫作match,它有些相似於Java中的switch。首先匹配一系列的模式,而後執行相應的代碼。與Java中switch不一樣的是,switch只能支持數值/枚舉類型(如今也能夠支持字符串),match能夠支持任意類型。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
複製代碼
此外,match還能夠支持模式中綁定值。
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
複製代碼
前面咱們聊到了從Option<T>中提取T的值,咱們來介紹一種經過match提取的方法。
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
複製代碼
這種方法在參數中必須聲明T的具體類型,這裏再思考一個問題,若是咱們肯定x必定不會是None,那麼可不能夠去掉None的那個條件?
_
佔位符答案是不能夠,Rust要求match必須列舉出全部可能的條件。例如,若是一個u8類型的,就須要列舉0到255這些條件。這樣作的話,可能一天也寫不了幾個match語句吧。因此Rust又給咱們準備了一個語法糖。
針對上述狀況,就能夠寫成下面這樣:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
複製代碼
咱們只須要列舉咱們關心的幾種狀況,而後用佔位符_
表示剩餘全部狀況。看到這我只想感嘆一句,這糖真甜啊。
對於咱們只關心一個條件的match來說,還有一種更加簡潔的語法,那就是if let。
舉個栗子,咱們只想要Option<u8>中值爲3時打印相關信息,利用咱們已經掌握的知識,能夠這樣寫。
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
複製代碼
若是用if let呢,就會更加簡潔一些。
if let Some(3) = some_u8_value {
println!("three");
}
複製代碼
這裏要注意,當match只有一個條件時,纔可使用if let替代。
有同窗可能會問,既然叫if let,那麼有沒有else條件呢?答案是有的。對於下面這種狀況
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
複製代碼
若是替換成if let語句,應該是
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
複製代碼
第二個坑也挖好了,來總結一下吧。本文咱們首先介紹了Struct,它相似於Java中的類,能夠供開發人員自定義類型。而後介紹了兩種初始化Struct時的簡化代碼的方法。接着是定義Struct相關的方法。在介紹完Struct之後,緊接着又介紹了你們都很熟悉的Enum枚舉類型。重點說了Rust中特殊的枚舉Option,而後介紹了match和if let這兩種流程控制語法。
最後,按照國際慣例,我仍是要誠摯的邀請你早日入坑。坑裏真的是冬暖夏涼~