前情提要
來到「猜謎遊戲」的最終篇章了,前面兩篇[Rust]教學系列-猜謎遊戲:取得使用者輸入值、[Rust]教學系列-猜謎遊戲:取亂數,我們已經把前置作業該做的都做完了,最終篇就是重頭戲中的重頭戲,將取得的使用者輸入值與隨機產生的亂數做一個比較,若一樣則比賽結束,若不一致則可以讓使用者繼續猜,跟著筆者一起實作吧。
筆者先預告一下此篇主要是會使用到Rust
的重要方法match
以及若使用者輸入值與神秘數字不一樣時,可以讓使用者一直猜下去的loop
技巧。
內容
比較數字
需要引入一些基礎模組套件並使用
1 | use std::cmp::Ordering; |
可以發現上述的程式碼是無法建置的,是因為guess為使用者輸入值,是一個String
型態,secret_number
則是透過gen_range
產生的亂數,是數字型態,兩邊資料型別不對等的情況下,是無法比較的。VSCode
中的Rust Extension
有Rust Language Server
服務,會偵測到錯誤並提示
1 | mismatched types |
意即以第一個比較對象為基準,因為guess
為使用者輸入值,資料型別理所當然是String
型態,因此透過cmp
方法比較的兩邊,資料型別要一致的狀態下,會跳出string
對string
的提示。
解釋match
方法結構,筆者找不出更直白的方式敘述,以下兩段敘述引用官方教學文件中的翻譯敘述:
match
表達式由分支(arms)所組成。分支包含一個模式(pattern)以及對應的程式碼,這在當match
表達式開頭的數值能與該分支的模式配對時就能執行。Rust 會用match
得到的數值依序遍歷每個分支中的模式。match
結構與模式是 Rust 中非常強大的特色,能讓你表達各種程式碼可能會遇上的情形,並確保你有將它們全部處理完。這些特色功能會在第六章與第十八章分別討論其細節。
讓我們看看在此例中使用match
表達式時會發生什麼事。假設使用者猜測的數字是 50 而這次隨機產生的祕密數字是 38。當程式碼比較 50 與 38 時,cmp
方法會回Ordering::Greater
,因為 50 大於 38。match
表達式會取得Ordering::Greater
數值並開始檢查每個分支的模式。它會先查看第一個分支的模Ordering::Less
並看出數值Ordering::Greater
無法與Ordering::Less
配對,所以它忽略該分支的程式碼,並移到下一個分支。而下個分支的模Ordering::Greater
能配對到Ordering::Greater
!所以該分支對應的程式碼就會執行並印出太大了!
到螢幕上。最後match
表達式就會結束,因為在此情境中它已經不需要再查看最後一個分支。
看完上述敘述,給筆者一種感覺很像其他語言中的switch case
,畢竟match
在Rust
語言的世界中屬流程控制
,筆者認為應該就是筆者熟悉的C#世界中的switch case
,官方教學文件也會有一篇專門介紹match
的章節,到時再跟著筆者慢慢體會其作法及威力吧。
字串轉型
接著就需要用到字串轉型
的技巧了,因為畢竟我是猜數字遊戲,需要當作數值做比較才有其意義,因此將使用者輸入值guess
(String型別)轉型成可以跟secret_number
可以比較的型態
1 | let guess: u32 = guess.trim().parse().expect("請輸入一個數字!"); |
看到這裡,有沒有一種感覺,這什麼鬼 XD,上面已經有guess
變數了,可以再宣告一個guess
嗎?這是Rust
語言的特色之一:shadow遮蔽
,因為有這個特色,我們可以重複使用變數名稱,且右邊的guess
就是第一個String型別
的資料,經過運算之後指派給新的變數guess
,這個概念之後會有專門一篇說明。
程式裡面使用到trim().parse()
,經過[上篇]的說明,應該知道回傳是一個泛型Result型別
,因此可以再接著使用expect
這個方法,筆者試看看若command line
輸入的是不是數字時的反應
1 | Finished dev [unoptimized + debuginfo] target(s) in 0.02s |
階段性執行結果
寫到這邊,已經完成一個可執行的猜謎遊戲了
1 | cargo run |
當然以執行結果來說,是不是覺得不夠完美,有以下幾個項目可以改善
- 不應該印出祕密數字
- 轉換資料型別失敗時的處理
- 若猜錯,是否可以再繼續進行猜數字,直到猜中為止
迴圈設計
筆者這邊解釋一下為何要加入迴圈設計
- 依照轉型失敗時的處理,勢必要讓使用者再輸入一次,直到可以將其值成功轉換成數字型別
- 猜錯數字,必須得讓使用者再猜一次
基於上述兩個理由,必須使用迴圈將主要邏輯包住,可以重複執行同樣的邏輯,Rust語言的迴圈設計則使用loop
關鍵字,將需要重複執行的程式邏輯區塊包住即可有效果,另外搭配
continue
:中間有邏輯符合則執行到最後,直接進入下一個迴圈break
:已達終止條件,終止迴圈
完整程式碼
筆者就不賣關子了,直接把上章節說的不完美的地方加入迴圈設計後的改善結果吧,講一下程式實作邏輯
- 產生1-100間的亂數
- loop開始
- 取得使用者輸入值
- 將使用者輸入值轉換資料型別為數值(若轉換型別錯誤則讓
loop
繼續) - 資料比較邏輯:若猜中,終止
loop
最後列出完整程式碼
1 | use rand::Rng; |
執行結果
筆者自己也來玩一場吧
1 | cargo run |
結論
做完有趣的題目,下篇開始要無聊的介紹資料型別
,基礎設計概念
等觀念,還有一些Rust
獨有的概念原則,開始一一揭開Rust
神秘的面紗,筆者希望可以講完基礎概念後,可以實作一些Side Project
,讓自己可以更加靈活的運用Rust
語言。最後筆者Recap一下今天的一些重要觀念
match
的用法:很像其他語言中的switch case
,屬流程控制的一環parse
方法:轉型方法,回傳泛型Result型別
loop
技巧:包住想要重複執行的程式邏輯區塊,搭配continue
或break
使用
參考