Rust 基礎
我們直接打開 main.rs
來寫我們的程式吧,首先 //
開頭的是程式的註解,它是給人看的,電腦看到會直接忽略,我直接使用註解來說明程式的內容,希望你可以照著程式碼自己打一遍,這樣做相信會比較有印像,當然,註解的部份可以不用照著打,你也可以用你自己的方式用註解做筆記。請照著底下的內容輸入:
// 宣告使用外部的函式庫 rand
// 這告訴我們的編譯器我們要使用 rand 這個套件
extern crate rand;
// 引入需要的函式,如果不引入的話我們就需要在程式中打出全名來,
// 比如像下面使用到的 thread_rng 的全名就是 rand::thread_rng ,
// 但這裡我們選擇引入 rand::prelude::* 這是個比較方便的寫法,
// 很多套件的作者為了使用者方便,都會提供個叫 prelude 的模組,
// 讓你可以快速的引入必要的函式,我們要使用的 thread_rng 也有包含在裡面,
// 但並不是每個套件作者都會這麼做,請注意。
use rand::prelude::*;
// 這是標準輸入,也就是來自鍵盤的輸入,我們等下要從鍵盤讀玩家的答案。
use std::io::stdin;
// 這是一備函式,函式就是一段的程式,
// 我們可以在一個程式裡根據不同的功能將程式拆成一個個的函式,
// 不過今天這個程式並不大,我們直接全部寫在 main 這個函式裡就好了,
// main 是個特殊的函式, Rust 的程式都會從 main 開始執行。
fn main() {
// 我們在這定義了一個變數 ans 來當作我們的答案,
// 將它設定成 1~100 之間的隨機數字
let ans = thread_rng().gen_range(1, 100);
// 這邊又定義了兩個變數,分別代表答案所在的上下範圍,
// 之後我們要把這個範圍做為提示顯示給玩家,
// 因為之後需要修改這兩個變數的值,所以這邊必須加上 mut 來表示這是可修改的
let mut upper_bound = 100;
let mut lower_bound = 1;
// 這是迴圈,它會重覆的執行包在這裡面的內容,
// 因為這邊的迴圈沒有條件,所以它會一直反覆的執行,
// 直到執行到如 break 才會結束,
// 等下還會介紹另外兩種有條件的迴圈
loop {
// 這邊要建一個用來暫放玩家輸入的答案用的變數,
// String 是個存放一串文字用的型態,也就是字串型態,
// String::new 會建立一個空的字串
let mut input = String::new();
// 這邊要印出提示使用者輸入的顯示,同時我們也印出答案所在的上下界,
// println! 在印完會自動的換行,也就是接下來的輸入輸出會從下一行開始,
// 而裡面的 {} 則是用來占位子用的,分別是我們要印出上下界的位置,
// 之後傳給 println! 的變數就會被放在這兩個位置
println!(
"答案在 {}~{} 之間,請輸入一個數字",
lower_bound, upper_bound
);
// 這邊我們使用 read_line 從鍵盤讀入一整行進來,
// 也就是到玩家按下 Enter 的字都會讀進來,
// 讀進來的文字會被放進 input 裡,
// 而因為放進 input 代表著修改 input 的內容,
// 所以這邊比較特別一點,我們要加上 &mut 來允許 read_line 修改 input ,
// 而 read_line 會除了把輸入放進 input 外也會傳回是否有成功讀取輸入,
// 於是這邊就使用了 expect 來處理,若回傳的值代表錯誤時,
// expect 會印出使用者傳給它的訊息並結束掉程式
stdin().read_line(&mut input).expect("Fail to read input");
// trim() 會把字串前後的空白字元 (空格與換行) 去掉,
// 而 parse::<i32>() 則是把字串從原本的文字型態轉換成數字,
// 這樣我們在之後才可以拿它來跟答案做比較,
// 我們這邊又重新定義了一次 input 來放轉換成數字後的結果,
// 如果你有學過其它的語言可能會覺得奇怪,為什麼允許這麼做,
// 這也是 Rust 一個有趣的地方, Rust 允許你重覆使用同一個變數名稱,
// parse 也是回傳代表正確或錯誤的 Result 不過這次我們不用 expect 了,
// 這次我們判斷是不是轉換失敗,如果是則代表玩家輸入了不是數字的東西,
// 那我們就讓玩家再輸入一次, match 是用來比對多個條件的語法,之後
// 會有一篇來介紹這個語法,因為它是 Rust 裡一個很強大的功能。
let input = match input.trim().parse::<i32>() {
// Ok 代表的是正確,同時它會包含我們需要的結果
// 因此這邊把轉換完的數字拿出來後回傳
// Rust 裡只要沒有分號,就會是回傳值
Ok(val) => val,
// Err 則是錯誤,它會包含一個錯誤訊息,不過我們其實不需要,
// 這邊我們直接提示使用者要輸入數字並結束這次迴圈的執行
Err(_) => {
println!("Please input a number!!!");
// continue 會直接跳到迴圈的開頭來執行,也就是 loop 的位置
continue;
}
};
// 這邊使用 if 來判斷玩家的答案跟正確答案是不是一樣,
// if 會判斷裡面的判斷式成不成立,如果成立就執行裡面的程式,
// 要注意的是判斷相等是雙等號,因為單個等於已經用在指定了。
if input == ans {
println!("恭喜你,你答對了");
// break 則會直接結束迴圈的執行,
// 於是我們就可以離開這個會一直跑下去的迴圈
break;
// 如果不一樣,而且玩家的答案比正確答案大的話就更新答案的上限
} else if input > ans {
upper_bound = input;
// else 會在 if 的條件不成立時執行,並且可以串接 if 做連續的判斷,
// 像上面一樣。都不是上面的情況的話就更新下限
} else {
lower_bound = input;
}
}
}
變數宣告
定義一個變數的語法是像這樣的:
let ans: i32 = 42;
變數是用來存放程式的資料用的,等號在這邊是指定的意思,並不是數學上的相等,我們把 42
指定給 ans
這個變數,而因為 42
是一個數字,因此我們把 ans
這個變數指定為 i32
這個整數型態,然而事實上大部份情況下其實是不需要給型態的,因為 Rust 可以自動的從初始值,也就是你一開始指定的值推導出你的變數的型態。
在 Rust 裡預設變數是不可以修改的,一但給了值就不能改變,如果要能修改就必須加上 mut
,這可以讓你在之後還能修改變數的值。
錯誤的範例:
let ans = 42;
ans = 123; // 這邊重新指定了值給 ans ,但 ans 並沒有宣告為可改變的
正確的範例:
let mut ans = 42;
ans = 123;
在 Rust 中的變數都必須要有初始值,如果沒有初始值會是一個錯誤,當然,你可以先宣告再給值,但只要有可能會發生沒有指定初始值的情況就會發生錯誤。
錯誤的範例:
let ans;
if true {
ans = 42;
}
// 這邊我們沒有 else 的情況就使用了,所以 Rust 認為 ans 可能沒有初始值
println!("{}", ans);
正確的範例:
let ans;
if true {
ans = 42;
} else {
ans = 0;
}
println!("{}", ans);
基本型態
在電腦裡資料都有其型態,電腦必須知道資料屬於哪一種才能做出正確的處理,而 Rust 的基本型態則有:
以下為了說明都有在定義變數時把型態寫出來,但平常因為可以自動推導,所以並沒有必要寫出來。
- unit: 型態是
()
,同時值也是()
,這是一個代表「無」的型態
// 就是「無」,你沒辦法拿這個值來做什麼
let unit: () = ();
- 布林值
bool
: 值只有真true
與假false
,一個代表真假的型態,同時也是判斷式所回傳的型態
let t: bool = true;
// if 判斷的結果一定要是布林值,其實所有的判斷式的結果也都是布林值
if t {
println!("這行會印出來");
}
if false {
println!("這行不會印出來");
}
- 整數: 上面使用的
i32
就是整數,實際上整數家族有具有正負號的i8
、i16
、i32
、i64
、i128
與只能有正整數的的u8
、u16
、u32
、u64
、u128
它們之間的差別在有沒有正負號,以及能存的數字最大的大小,平常通常只會用到i32
,先只要記得i32
就行了
let num: i32 = 123;
// 數字你可以做基本的四則運算:加法 (+) 、減法 (-) 、乘法 (*) 、除法 (/)
// 和 取餘數 (%) ,當然也還有比如取絕對值之類的方法在,不過這邊先不提
println!("1 + 1 = {}", 1 + 1);
println!("10 % 3 = {}", 10 % 3);
- 浮點數: 就是小數,有
f32
與f64
,但平常只會用到f64
,浮點數也可以像整數一樣做計算,只是無法取餘數
let pi: f64 = 3.14;
- 字串:
String
,代表的是一串的文字,另外還有切片型態的str
(這兩個詳細的差別之後再提) ,如果你要讀使用者的輸入或是要能夠修改內容的要用String
,如果要放固定的字串 (比如顯示的訊息) 用str
let message: &str = "這是個固定的訊息";
println!("{}", message);
let mut s: String = String::from("可以用 from 來建一個 String");
s.push_str(",同時你也可以增加內容在 String 的結尾");
println!("{}", s);
- 字元:
char
一個字就是一個字元,比如'a'
或是'三'
也是
let c1: char = '中';
let c2: char = '文';
let c3: char = '也';
let c4: char = '行';
這邊為了說明都有在變數後加上型態,如果平常沒加型態而是讓編譯器自動推導的話,整數預設會使用
i32
,符點數會使用f64
,但若之後把變數傳入了其它函式,則會配合那個函式的需求改變。
複合型態
- 陣列 (array) :由一串相同型態與固定長度的資料組成的
// i32 是元素的型態, 4 則是長度
let mut array: [i32; 4] = [1, 2, 3, 4];
// 這邊印出第一個數字與最後一個數字,陣列的編號是從 0 開始的
println!("{}, {}", array[0], array[3]);
// 我們也可以修改元素的值,同樣的,請注意上面的定義是有加 mut 的
array[1] = 42;
- 元組 (tuple):可由不同的型態組成
// 元組的型態要把每個欄位的型態都寫出來
let mut tuple: (i32, char) = (42, 'a');
// 印出第一個值
println!("{}", tuple.0);
// 改變第二個值
tuple.1 = 'c';
條件判斷 if
// if 裡面要放條件判斷,或是布林值,
// 條件判斷除了上面出現的 == 、 > 、 < 外還有個不等於
if 10 != 5 {
println!("10 != 5");
}
如果要判斷多個條件可以用底下的方式串起來:
&&
:「且」,兩邊都成立時才成立||
:「或」,其中一邊成立就成立
if 2 > 1 && 3 > 2 {
println!("2 > 1 且 3 > 2");
}
還有 !
可以把判斷式的結果反轉,也就是把布林值的 true
變 false
, false
變 true
:
if !false {
println!("這行會印出來");
}
另外你可以用 if
來根據條件來指定變數:
let ans = if true {
// 請注意,這邊沒有分號
42
} else {
123
}; // 這邊要加分號
Rust 裡只要不加分號就會變回傳值
while
while
是有條件的迴圈,只有當條件滿足時才會繼續執行下去:
let mut i = 0;
while i < 5 {
println!("i = {}", i);
// 這是 i = i + 1; 的縮寫,數學運算都可以這樣寫
if i == 3 {
// 我們在 i 為 3 時就結束迴圈了,所以你會看到從 0 印到 3
break;
}
i += 1;
}
而 break
可以用來中斷迴圈,而 continue
可以直接結束這次的迴圈,跳到迴圈的開頭執行。
for
for
是用來跑過一個「範圍」的資料的,比如像陣列,以後會再詳細介紹背後的機制。
for item in [1, 2, 3, 4, 5].iter() {
println!("{}", item);
}
for item in 0..5 {
println!("{}", item);
}
其中 0..5
是 Rust 的 range
代表的是 0~4 (不含結尾),含結尾的話要寫成 0..=5
(有等號),這代表一個數字的範圍,以後講到切片時會再提到。
函式
上面說了函式就是一小段的程式,如果你的程式裡出現了重覆的程式碼,你可以試著把程式碼抽出來變成函式,這樣以後要修改也會比較方便。
函式來自於數學的函數的觀念「給予一個值,會對應到另一個固定的值」,函式同樣的也需要輸入的值與輸出的值,底下是個範例:
// 這個函式有兩個整數的輸入值 a 與 b 並且回傳一個整數
// 函式的開頭是 fn 接下來跟著函式的名字,後面的括號裡放著函式的輸入
// 其中當作輸入的鑾數都一定要有型態,之後 -> 後放著的是回傳的型態
fn add(a: i32, b: i32) -> i32 {
// 這邊示範如何使用 return ,如果 a 與 b 都是 1 ,就直接回傳 2
if a == 1 && b == 1 {
// return 會提早結束函式的執行,並且把後面的值當成回傳值
return 2;
}
// 注意這邊沒有括號,沒有括號的代表回傳值,當然你也可以像上面使用 return
a + b
}
// 這個函式沒有寫出回傳值,這代表它其實會回傳一個 () ,只是可以省略不寫出來而已
fn print_number(num: i32) {
println!("{}", num);
}
// 所以其實 main 函式回傳的也是 unit
fn main() {
// 像這樣子就可以呼叫函式了
let num = add(1, 2);
print_number(num);
// 你也可以寫在一起
print_number(add(1, 2));
}
這篇我們很快的介紹了 Rust 的基本語法,下一篇要介紹的是 Rust 的參考,以及 Rust 中很重要的變數的所有權的觀念。