Rust 導引筆記系列 #02 -- 數字加總程式範例
在討論基本語法及型別之前,我想再用一個範例讓大家了解一下要如何處理使用者的輸入以及 Rust 程式的風格
在上一篇的 Hello world 之後,相信大家對於 Rust 的輸出有了一些概念,在討論基本語法及型別之前,我想再用一個範例讓大家了解一下要如何處理使用者的輸入以及 Rust 程式的風格。
這次要寫的程式是從我這學期上的「演算法概論」中,教授給的測試題借來的。
題目輸入給一行以空白分開的整數,輸出他們加總的結果
Sample Input:
1 12 4 56 17 0 3
---
Sample Output:
93
看起來很簡單吧~假如用 Rust 寫會是怎麼樣呢?
use std::io::stdin;
fn main() {
let mut linebuf = String::new();
stdin()
.read_line(&mut linebuf)
.expect("Failed to read from stdin");
let sum: i32 = linebuf
.split_whitespace()
.filter_map(|s| s.parse::<i32>().ok())
.sum();
println!("{}", sum);
}
額…這不是很簡單的程式嗎?為什麼看起來那麼複雜啊?相信所有人一開始都會有這個疑問,明明C++寫出來只需要不到10行,為什麼用 Rust 寫會變兩倍長度啊?
以下我就來慢慢解說
程式碼解說
最上方的 use std::io::stdin;
是和 using std::cin;
類似的東西,由於 Rust 不像 C++ 把所有東西都丟在 std 下,所以往後經常會用到它,之後會提到更進階的用法。
let mut linebuf = String::new();
這裡宣告一個新的字串結構,叫做 linebuf。注意 mut
這個關鍵字,在 Rust 中所有的變數預設都是『不可更動(immutable)』的,加上這個關鍵字才能讓變數的資料可以被改變(mutable)。這似乎違反了我們以前學程式的想法,但在實際寫了之後,會發現用到 let mut
的次數其實沒想像中的多。很多時候,我們宣告變數只是暫存用,或是代表一個抽象的物件(如:Http Request、Socket…),這個設計可以避免變數被不小心誤改,更加強調安全性。
stdin().read_line(&mut linebuf).expect("Failed to read from stdin");
這整段算是一個陳述式。呼叫 stdin()
會回傳一個 Stdin
物件。再對它使用 .read_line(&mut linebuf)
讀取一行並存入方才宣告的字串內。在參數的部份可以看到傳入的是 &mut
,它代表一個可變的參考(mutable reference),跟 C 的指標有點像吧 XD。最後的 .expect("Failed to read from stdin")
是當發生錯誤時,要提示使用者的訊息,如果成功則會回傳結果。
let sum: i32 = linebuf.split_whitespace().filter_map(|s| s.parse::().ok()).sum();
最後,也是最複雜的部份,這裡用到了迭代器(Iterator),如果有 Functional Programming 經驗的人應該會感到蠻熟悉的。先將讀入的數字們用 .split_whitespace()
分割後,我們會拿到一個迭代器,對於每個分割出的子字串,我們要把它轉換為整數。但問題是字串並不一定能轉換為整數啊…,所以 s.parse::<i32>()
會回傳一個代表成功與否的物件 Result
。在此,使用 .ok()
來取得成功轉換為數字的部份, filter_map
就會負責過濾掉。至此,我們已經取得內含整數型別的迭代器了,最後對整串迭代器用 .sum()
取得全部的加總,輸出之後就完成了~~
雖然 Rust 比起其他語言更加複雜一些,但卻能在追求效能的同時兼顧安全性,不會出現未定義行為(Undefined behavior)。另外,這次出現的迭代器也是 Rust 中被廣泛運用的部份,可以方便操作大量物件並增加程式碼可讀性,卻不會額外消耗過度資源。
下一篇:#03 -- 變數宣告