在先前的章節中我們已經稍微用過Result
列舉了,在這個章節中,我們將會學著定義自己的列舉型別,以及使用另一個同樣是由Rust內建,也很常用的Option
列舉。列舉和同樣我們先前已經用過的match
關鍵字經常互相搭配著使用,在這個章節中,我們也會更深入地學習match
關鍵字和if let
、while let
語法的用法。
先舉一個例子來說明列舉的用途。現在我們需要用Rust程式來處理IP位址,而IP位址有兩個主要版本,分別是32位元的「IPv4」和128位元的「IPv6」,我們的程式也只要處理這兩種版本的IP位址。所有的IP位址都一定是屬於「IPv4」或是「IPv6」,但不能同時是「IPv4」和「IPv6」。我們可以定義出一個IP位址種類的列舉IpAddrKind
,且這個列舉有V4
、V6
兩種變體(variant)。程式如下:
enum IpAddrKind {
V4,
V6,
}
列舉與結構體類似,定義出來後只是一個型別,要作為「值」使用的話一樣還要經過實體化。IpAddrKind
列舉可以實體化出V4
和V6
兩種不同類型的值,實體化的程式碼如下:
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
}
實體化出列舉值的方式十分簡單。以上程式,four
和six
變數的型別都是IpAddrKind
,也就是說如果要將它們傳入函數作為參數使用,參數的型別可以使用IpAddrKind
。例如:
enum IpAddrKind {
V4,
V6,
}
fn route(ip_type: IpAddrKind) {}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(four);
route(six);
}
將目前的IpAddrKind
列舉搭配結構體使用看看吧!程式如下:
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr { kind: IpAddrKind::V4, address: String::from("127.0.0.1") };
let loopback = IpAddr { kind: IpAddrKind::V6, address: String::from("::1") };
}
程式第6行到第9行,定義了一個新的結構體IpAddr
,用來表示一個IP位址種類和位址。種類的型別即為IpAddrKind
列舉,而位址則使用字串型別來儲存。這樣的寫法並沒有什麼問題,但我們可以再寫得更精簡一點,只用一個列舉,不搭配其它結構體,就能夠完成一樣的事情。程式如下:
enum IpAddr {
V4(String),
V6(String),
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
在定義列舉的變體時,可以在變體名稱後面加上小括號()
來定義其攜帶其它值的欄位,類似元組結構體的語法。每個列舉的變體可以攜帶的值,數量並沒有限制。例如:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
以上程式將V4
從儲存一個字串改成儲存4個8位元的無號整數。雖然home
和loopback
變數都是屬於IpAddr
型別,但它們攜帶的值卻可以是不同數量、不同型別。
接著再來看看其它列舉的例子。請看以下程式:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
這個Message
列舉有四個變體,能用來處理四種不同類型的訊息。這裡要注意的是變體Move
定義的方式,它的語法類似結構體,使用大括號{}
,且擁有欄位名稱。如果把Message
列舉的四個變體轉成結構體的寫法的話,程式如下:
struct QuitMessage; // unit-like struct
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct
使用結構體的方式改寫原先的Message
列舉後,QuitMessage
、MoveMessage
、WriteMessage
和ChangeColorMessage
就都不是同一個型別了。
列舉的變體跟結構體很像,除了可以定義資料欄位之外,它也可以使用impl
關鍵字來定義方法和關聯函數。如以下程式:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// method body would be defined here
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
impl
關鍵字搭配列舉所實作出來的方法適用於該列舉底下的所有變體。也就是說,以上程式,不論是Message::Write
、Message::Quit
、Message::Move
還是Message::ChangeColor
的實體,都可以使用call
方法。在call
方法可以利用self
參數對實體本身做「型樣匹配」(pattern matching)來決定要執行什麼程式,這個部份將在這個章節後半部做介紹。
先來介紹一下Rust程式語言提供的一個常用的列舉型別Option
。Option
列舉和Result
列舉都是在定義Rust程式語言的方法或是函數時,常使用的型別。Result
列舉用來表示函數或是方法有沒有執行成功,而Option
列舉則可以用來包裹函數或是方法的回傳值。Result
列舉的Ok
變體不是就可以包裹回傳值了嗎?為什麼還要用Option
列舉呢?
在許多程式語言中有null
或nil
關鍵字可以使用,即「空值(沒有這個值)」的概念。而在Rust程式語言中,並不能直接使用null
或nil
關鍵字來代表「空值」。可是在很多種情況下,函數或是方法雖然在邏輯上是執行成功的,但是因某些條件的關係而不會回傳值出來,這時候要怎麼讓Rust的函數或是方法回傳「空值」呢?
Option
列舉就是為了表示「空值」而存在的,其定義如下:
enum Option<T> {
Some(T),
None,
}
<T>
是泛型的語法,這個在之後的章節會介紹。現在只要知道Option
列舉有Some
和None
兩種變體,且Some
可以包裹其他型別的值,而None
就是表示「空值」啦!實體化Option
列舉的方式如下:
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
在使用Result
列舉和Option
列舉的變體時不需要撰寫Result::
和Option::
來指定使用哪個列舉下的變體,因為Result
列舉和Option
列舉的變體都已經被包含在std::prelude
模組中,會自動被引用進目前程式的scope。
另外,如果想要用類似的方式實體化Result
列舉的話,必須要先定義好Result
的泛型型別,才能通過編譯,因為Result
的泛型有兩個參數,各自是Ok
和Err
變體所包裹的值的型別。Result
列舉的定義如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
舉例來說:
let ok_number = Ok(5);
let err_string = Err("a string");
以上程式會編譯錯誤,因為直接實體化Ok(5)
,只能推測T
是i32
,並不能推測E
的型別;直接實體化Err(5)
,也只能推測E
是&str
,並不能推測T
的型別。
所以要明確定義Result
列舉的型別所用的泛型型別,才可以通過編譯,程式如下:
let ok_number: Result<i32, &str> = Ok(5);
let err_string: Result<i32, &str> = Err("a string");
到這裡如果還是不懂<>
所表示的「泛型」到底是什麼也沒關係,後面的章節會有詳細的介紹。
接下來介紹一下match
關鍵字吧!雖然我們之前有用過,但並沒有將它徹底地瞭解。match
關鍵字可以用來做任何值的「型樣匹配」(pattern matching),早在練習我們寫猜數字程式的時候就已使用過,複習一下,程式如下:
use std::cmp::Ordering;
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: i32 = match guess.trim().parse() {
Ok(-1) => {
println!("Bye bye!");
break;
}
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
再舉一個新的例子,請看以下程式碼:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
這個程式中的value_in_cents
函數可以將Coin
變體的實體轉成其對應的一個32位元的無號整數。match
和if
關鍵字不同的地方在於:if
關鍵字必須使用布林值來進行流程判斷;而match
關鍵字則可以判斷任何型別的值,經常與列舉型別的值一同使用。
以match
關鍵字來匹配Option
列舉的值來舉例:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
以上程式,plus_one
函數可以接受一個Option<i32>
的值。如果傳入的值是None
的實體的話,就回傳None
;如果傳入的值是Some
的實體的話,就將傳入的Some
實體包裹在第一個參數的值加一之後,再用新的Some
包裹起來回傳。
match
關鍵字有一個很重要的特性,那就是他必須將要判斷的值的所有可能都列在「arm」,如果有遺漏,程式就會編譯失敗。舉例來說:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
以上程式會編譯失敗,因為x
的型別為Option<i32>
,它可能的值除了Some(i32)
外,還有None
,但是match
中並未有arm去判斷當x
為None
時要做的事情。
當然,如果每次使用match
關鍵字時都要將所有可能的值都寫成arm會很麻煩,如果我們要判斷的值是u8
型別,那不就要寫256(28)條arm嗎?別緊張,對於我們想要統一用一樣的方式處理的其它值,可以使用底線_
作為匹配樣本。舉例來說:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
_
的概念就像是if
的else
,當_
先前的arm都匹配失敗時,就會執行_
的arm。也就是說,_
的arm應該放在最後一個,如果安插在arm跟arm的之間,編譯時將會出現警告訊息。
再來看一個match
關鍵字和Option
列舉的例子:
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
以上程式的目的是要判斷當some_u8_value
值為Some
的實體,且包裹著3
時,就印出three
,否則不做任何事情。在很多時候,我們在意的只有Some
變體所包裹的值,而不想去理會當Option
列舉的實體如果是None
時應該要做什麼事。Rust程式語言另外還有一種語法能使用,也就是if let
語法。以上程式,可以使用if let
語法改寫為:
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
先不要去想match
關鍵字寫的程式敘述要怎麼轉成if let
語法,而是去想如果我們要用之前學到的if
關鍵字來判斷some_u8_value
變數的值是不是Some(3)
的話要怎麼寫,最直覺的程式碼如下:
fn main() {
let some_u8_value = Some(0u8);
if Some(3) == some_u8_value {
println!("three");
}
}
這樣的程式是可以編譯並執行的哦!但是如果我們只是要判斷some_u8_value
變數的值是不是Some
的實體的話,用if
關鍵字要怎麼做到呢?拿剛才的plus_one
函數來修改看看:
fn plus_one(x: Option<i32>) -> Option<i32> {
if Some(i) == x {
Some(i + 1)
} else {
None
}
}
以上程式會編譯錯誤,因為i
被當成是要作為參數傳入Some
變體的變數了,而不是要代替Some
實體第一個參數所包裹的值,因此編譯器會認為i
沒有事先宣告出來,產生編譯錯誤。
為了要讓編譯器了解這邊的i
是要代替Some實體第一個參數所包裹的值,我們必須使用if let
語法來改寫。如下:
fn plus_one(x: Option<i32>) -> Option<i32> {
if let Some(i) = x {
Some(i + 1)
} else {
None
}
}
除了在if
關鍵字後面加上let
關鍵字外,還需要將判斷值是否相等的==
改為指派值用的=
。請注意=
是將右邊的值指派給左邊的變數,因此if let Some(i) = x
不能夠寫成if let x = Some(i)
。
當然就和一般的if
關鍵字組成的程式敘述一樣,if let
和else if
也是可以一起使用的。如下:
fn test(x: Option<i32>) -> Option<i32> {
if Some(3) == x {
Some(0)
} else if Some(4) == x {
Some(1)
} else if let Some(5) = x {
Some(2)
} else if let Some(i) = x {
Some(i + 1)
} else {
None
}
}
if let
語法就是原本的if
關鍵字所組成的程式敘述再加上let
關鍵字的特殊用法,不要用match
關鍵字去想它會比較容易理解。如果想單獨將let
關鍵字和列舉實體拆出來使用的話,是不行的。舉例來說:
let Some(i) = Some(3);
我們預期會宣告出一個變數i
並指派3
給它,然而這行程式會編譯錯誤。
除了if let
語法之外,還有while let
語法。舉例來說:
fn main() {
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
程式第8行開始的while迴圈,會在stack
變數的Vec
實體呼叫pop
方法的回傳值為Some
變體的時候,才繼續執行迴圈。
if let
或是while let
語法也與match
關鍵字一樣,可以使用|
字元,來表示「或」(or)的邏輯。舉例來說:
enum Creature {
Crab(String),
Lobster(String),
Person(String),
}
fn main() {
let state = Creature::Crab("Ferris");
if let Creature::Crab(name) | Creature::Person(name) = state {
println!("This creature's name is: {name}");
}
}
事實上,型樣匹配不只限於在match
關鍵字、if let
或是while let
語法才能使用。我們使用let
關鍵字宣告變數,如let x = 5
、let (x, y) = (1, 2)
,甚至是函數或方法的參數,如(x, y): (i32, i32)
,或是for迴圈,其實也都是在做型樣匹配哦!舉例來說:
fn main() {
let a = 5;
let b = 6;
let (x, y) = (a, b);
println!("{x}");
println!("{y}");
let tuple = (10, 11);
let list = vec![1, 2, 3];
for (v, &i) in list.iter().enumerate() {
println!("{i}: {v}");
}
f(tuple);
}
fn f((x, y): (i32, i32)) {
println!("{x}");
println!("{y}");
}
以上程式執行結果如下:
6
1: 0
2: 1
3: 2
10
11
Rust程式語言將型樣匹配分為「可辯駁」(refutable)和「不可辯駁」(irrefutable)兩種,let x = 5
、let (x, y) = (1, 2)
、(x, y): (i32, i32)
這樣的用法都是屬於「不可辯駁」的型樣匹配,因為不論是x
和y
都可以是任何值,匹配一定可以成功。而if let Some(x) = a_value
這種的匹配方式為「可辯駁」的,因為a_value
實際的值可能為Some
變體,也可能為None
變體,有可能會匹配失敗。
函數或方法的參數、let
關鍵字和for迴圈,只能支援「不可辯駁」的型樣匹配,if let
和while let
語法只支援「可辯駁」的型樣匹配,match
的arm,可以同時支援「不可辯駁」和「可辯駁」的型樣匹配。
match
關鍵字的型樣匹配,還可以有一些特殊的變化。例如我們可以在每個arm要匹配的樣本中,使用|
字元,來表示「或」(or)的邏輯。舉例來說:
let x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
也可以同時使用多個|
字元。舉例來說:
let x = 5;
match x {
1 | 2 | 3 | 4 | 5 => println!("one through five"),
_ => println!("something else"),
}
以上程式,由於12345
是連續的整數數值範圍,我們也可以在型樣匹配的樣本中使用先前提過的..
和..=
範圍語法(前者屬於exclusive_range_pattern
特色,由於其還在實驗階段,所以需加上#![feature(exclusive_range_pattern)]
,並使用Rust的nightly版本來編譯程式)。舉例來說:
let x = 5;
match x {
1..= 5 => println!("one through five"),
_ => println!("something else"),
}
型樣匹配除了可以用在列舉之外,一般的結構體也能使用,可以快速地「解開」結構體。舉例來說:
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
println!("{x}");
println!("{y}");
}
以上程式執行結果如下:
7
再舉一個例子:
struct Point {
x: i32,
y: i32,
}
fn main() {
let points = vec![
Point { x: 0, y: 0 },
Point { x: 1, y: 5 },
Point { x: 10, y: -3 },
];
let mut sum_of_squares = 0;
for Point { x, y } in points.iter() {
sum_of_squares += x * x + y * y;
}
println!("{sum_of_squares}");
}
執行結果如下:
我們也可以對結構體使用可辯駁的型樣匹配。舉例來說:
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x: 0, y } => {
println!("x is zero, y is {y}");
}
Point { x, y } => ()
}
}
如果不想要直接使用結構體的欄位名稱作為變數名稱的話,也可以直接在:
字元後面定義該欄位的值要匹配到的變數名稱。舉例來說:
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x: 0, y: y_var } => {
println!("x is zero, y is {y_var}");
}
Point { x, y } => ()
}
}
那麼如果我們想要對結構體使用可辯駁的型樣匹配,同時又想使用自己的變數名稱的話,要怎麼做呢?那就是在變數名稱後面使用@
字元來串接要匹配的值。舉例來說:
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x: 0, y: y_var @ 5...7 } => {
println!("x is zero, y is {y_var}");
}
Point { x, y } => ()
}
}
當然也可以讓多個結構體和列舉複合使用,舉例來說:
struct Point {
x: i32,
y: i32,
}
fn main() {
let a: (Option<Point>, Option<Point>) = (Some(Point { x: 3, y: 4 }), None);
if let (Some(Point { x, y }), None) = a {
println!("{x}");
println!("{y}");
}
}
以上程式執行結果如下:
4
底線_
其實不只能夠被單獨使用在匹配樣本中,它的匹配方式就相當於x
、y
這樣的變數,只是_
在匹配完成後就會忽略掉匹配成功的值了。舉例來說:
let v = Some(5);
match v {
Some(_) => {
println!("Some");
}
_ => {
println!("None");
}
}
Some(_)
和Some(x)
可以說是完全一樣的匹配樣本,只是如果使用Some(_)
來匹配的話,就不會有一個實際的變數來儲存匹配到的值。如果我們將以上程式的Some(_)
改為Some(x)
的話:
let v = Some(5);
match v {
Some(x) => {
println!("Some");
}
_ => {
println!("None");
}
}
程式在編譯時將會出現unused variable: `x`
的警告訊息。不管在哪,如果我們宣告了變數卻完全沒去使用的話,Rust的編譯器都會出現這個訊息。如果想要讓編譯器不出現這個警告訊息的話,可以讓變數的名稱以底線_
開頭,如下:
let v = Some(5);
match v {
Some(_x) => {
println!("Some");
}
_ => {
println!("None");
}
}
這樣一來編譯器就不會出現unused variable: `x`
的警告訊息了!
我們除了能用_
在型樣匹配時忽略不想要的值之外,也可以使用..
語法來直接忽略掉資料結構中所有剩下來的值。要注意這邊的..
語法並不是範圍語法哦!舉例來說:
struct Point {
x: i32,
y: i32,
z: i32,
}
fn main() {
let p = Point { x: 0, y: 7, z: 9 };
let Point { x, .. } = p;
println!("{x}");
}
以上程式執行結果如下:
再舉一個例子:
fn main() {
let t = (3, 4, 5, 6, 7, 8);
let (x, y, ..) = t;
println!("{x}");
println!("{y}");
}
以上程式執行結果如下:
4
當..
語法用在元組的時候,其位置不一定要放在最右邊,但是只能夠出現一次。舉例來說:
fn main() {
let t = (3, 4, 5, 6, 7, 8);
let (.., x, y) = t;
println!("{x}");
println!("{y}");
}
以上程式執行結果如下:
8
再舉一個例子:
fn main() {
let t = (3, 4, 5, 6, 7, 8);
let (x, y, .., z) = t;
println!("{x}");
println!("{y}");
println!("{z}");
}
以上程式執行結果如下:
4
8
接著來談談型樣匹配時的擁有權吧!先看以下的程式:
fn main() {
let robot_name = Some(String::from("Magic Len"));
match robot_name {
Some(name) => println!("Found a name: {name}"),
None => (),
}
}
這個程式會編譯成功,並且在螢幕上印出:
然而,如果我們在最後添加一些程式進去,來使用robot_name
變數的話,會發生什麼事呢?程式如下:
fn main() {
let robot_name = Some(String::from("Magic Len"));
match robot_name {
Some(name) => println!("Found a name: {name}"),
None => (),
}
println!("robot_name is: {robot_name:?}");
}
我們添加的程式第9行會編譯失敗,因為robot_name
變數原本的值,已經移動給程式第5行match的arm所匹配好的變數name
了。為了解決在型樣匹配過程中擁有權轉移的問題,我們可以在匹配樣本的變數前使用ref
關鍵字來取得匹配到的值的參考並指派給變數,而不是直接將匹配到的值的本身指派給變數。至於為什麼不使用&
就好,那是因為&
在型樣匹配中代表的意義是「匹配參考型別」,而不是「取得值的參考」。
舉例來說:
fn main() {
let s = String::from("Hello");
let n = Some(5);
let v = Some(&n);
match v {
Some(&Some(a)) => {
println!("&Some!")
}
_ => {}
}
}
用&
來做型樣匹配其實看起來蠻奇怪,雖然現在的Rust在做類似的型樣匹配時可以省略&
,但在早期的Rust版本,&
卻是必要的。不過總之,就是因為&
在型樣匹配中已經有它既定的用途了,因此我們需要使用ref
關鍵字來代替用來取得參考的&
。
將剛才的Robot Name
,加上ref
關鍵字,改寫如下:
fn main() {
let robot_name = Some(String::from("Magic Len"));
match robot_name {
Some(ref name) => println!("Found a name: {name}"),
None => (),
}
println!("robot_name is: {robot_name:?}");
}
程式第5行的變數name
,型別變成&String
,並不會更動到原先String
結構實體的擁有者,因此程式可以通過編譯。執行結果如下:
robot_name is: Some("Magic Len")
如果要使用可變參考,也可以讓ref
關鍵字和mut
關鍵字搭配使用。例如:
fn main() {
let mut robot_name = Some(String::from("Magic Len"));
match robot_name {
Some(ref mut name) => {
println!("Found a name: {name}");
name.push_str(" (checked)");
}
None => (),
}
println!("robot_name is: {robot_name:?}");
}
執行結果如下:
robot_name is: Some("Magic Len (checked)")
另外,match
關鍵字的型樣匹配還有「匹配守衛」(match guard)的用法,需要讓match
的arm,搭配if
關鍵字使用。利用這個匹配守衛語法,我們可以讓arm增加一些匹配條件。當匹配守衛的條件參數運算結果為true
,這個arm就會匹配成功,執行其所對應的程式區塊。舉例來說:
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {x}"),
Some(x) => println!("{x}"),
None => (),
}
前面有提到,match
關鍵字必須將要判斷的值的所有可能都列在「arm」,如果我們想要讓match
表達式在成功完成某個匹配後回傳true
,如果不匹配就回傳false
的話,程式寫起來會像是這樣。例如:
let x = 5;
let in_range = match x {
1..=5 | 25..=99 => true,
_ => false,
};
我們可以使用matches!
巨集將以上程式化簡如下:
let x = 5;
let in_range = matches!(x, 1..=5 | 25..=99);
總結
這章節我們學會了如何使用列舉來撰寫Rust程式,並且也深入瞭解了Rust程式語言的型樣匹配了。我們現在的Rust程式功力已經足夠寫出各式各樣的運算程式(單執行緒限定),可以去找一些演算法的題目來練習看看。接下來的章節會介紹模組(mod)的用法,將我們寫好的程式包裝起來給別人使用!
下一章:模組。