---
# System prepended metadata

title: Rust 學習日誌
tags: [linux, code, rust]

---

此篇筆記主要以資訊工程研究所（在已經撰寫了幾個月的 C 語言之後）學習 [Rust](https://www.rust-lang.org/zh-TW) 的日誌

此外，所有學習紀錄都會放在我的 github 當中以供想要學習 rust-lang 的人觀看。並且[科技詞彙](https://hackmd.io/@sysprog/it-vocabulary)增進自身的漢語表達能力。
* [joshua0321 github 連結](https://github.com/JoshuaLee0321/rust-lang)
# Lesson-1 基礎語法以及工具

和所有程式語言一樣，Rust 也有一個主程式，通常叫做 [main](https://www.quora.com/Why-is-the-main-function-called-the-main-function-and-not-something-else)。而就像在學習所有新語言的朋友們，我們還是必須要形式上的跟世界說嗨！

首先可以定義一個新的檔案叫做 `hello.rs`，並且使用簡單的 command (注意！這邊假設已經安裝並且可以使用終端機來編譯 rust 語言，若不知道的人可以看[這裡](https://doc.rust-lang.org/book/ch01-01-installation.html)）

```rust=
fn main() {
    println!("Hello World");
}
```
* 第一個映入眼簾的就是 main 是使用 `fn` 來定義而不是和 C 語言一樣必須要求回傳的檔案格式。
* 另外一個值得注意的是 `println!` 最後面的驚嘆號，Rust 官方解釋是這是一個巨集(macro)的展開。（將在更後續詳細探討）

定義好了之後就可以使用 rustc (c for compile) 來編譯 rustfile
```bash
rustc .\hello.rs
```

:::info
Rust 有一些規範，例如不是用 `[TAB]` 來縮行，而是使用**四個空白**

另外，rust 語言把編譯以及執行分開，這樣一來不需要在電腦裡面安裝 rust 也可以執行相對的檔案。

* 眼尖的可能會在當前目錄下看到一個 `.pdb` 的檔案，rust 官方文件說這個檔案存在除錯資訊（目前沒有細說）
:::


## Cargo tools

當專案越來越大時，勢必需要有管理專案的工具。而 cargo 這個工具（在安裝時 rust 時即安裝好）可以幫忙下載函式庫以及協助專案管理。

使用以下指令即可利用 cargo 做出一個 rust 的專案
```bash
$ cargo new new_proj

>> Created binary (application) `new_proj` package
```
使用 `tree` 指令可以看到以下的空專案內容，可以看到專案使用 [TOML](https://toml.io/en/) 來管理，以及在 src 目錄下可以看到一個 `main.rs`（預設為先前的檔案）
```bash
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files
# 注意，cargo 會預設幫你建立 .git 檔，若已經存在有 .git 的專案底下則不會生成。
```

### `.toml` 檔
* 這個檔案其實就是這個專案的所有資訊（包括 dependency 以及版本等等）

先來打開檔案來瞧瞧
```toml
[package]
name = "new_proj"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

```
首先看到 `[package]`，也就是當前的專案， `[dependencies]` 也就是用到的套件都會登記在這邊

### 接續 `cargo`
:::warning
這邊要注意 cargo 專案中的程式碼都**必須**放在　`src` 目錄下
:::
而在專案當中也只需要簡單的一行，cargo 即可幫忙把所有檔案連結起來，以下為範例
(cargo 在沒有更改原始碼的狀態下不會重新編譯，基本上就是全部都已經撰寫好的 `Makefile`)
```bash
$ cargo build    # 編譯並且 link 
$ cargo run      # 編譯 + link + 跑程式
$ cargo check    # 可以常常使用這個來確定你寫的 rust 程式碼可不可以過編譯，因為不是編譯，所以快很多。
```
* [紀錄](https://users.rust-lang.org/t/cargo-build-where-the-executable-file-main/21818)，在 cargo build/run 之後找不到可執行檔，首先要看 toml 裡面的 name，接下來，通常可執行檔的位置會存放在 `<project>/target/debug/` 這個目錄底下。


# Lesson-2 實作猜數字遊戲以及補充語法說明

根據[此教程](https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html)實作出一個猜數字遊戲。我覺得直接看程式碼來理解 rust 會比較快

```rust=
/* src/main.rs */
use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}
```

上面這個程式碼最重要的地方無外乎為第 9 以及 11 至 13 行。以下來稍微解釋這幾行到底做了些甚麼
```rust
let mut guess = String::new();
```
 
Rust 中存在著==可變動以及不可變動==的變數，`let` 宣告一個變數，而 `mut` 讓這個變數可以在後續被修改，而 `=` 把後面的資訊綁定給這個關鍵字。廢話不多說，馬上來實驗看看，以下為例子：
```rust=
    let a = 1;
    a = 2;
```

可以看到第一行 `let` 定義了一個變數為 `a`，而這個變數在第二行的時候我嘗試賦值給他，接下來使用 `cargo check` 就會跑出以下錯誤訊息：
```bash
15 |     let a = 1;
   |         -
   |         |
   |         first assignment to `a`
   |         help: consider making this binding mutable: `mut a`
16 |     a = 2;
   |     ^^^^^ cannot assign twice to immutable variable
```
可以看到 rust-compiler 提示沒有 `mut` 的資料無法附值兩次。只需要改成 `let mut a = 1` 就可以更改值了。

再來注意到剛剛第 12 行的部分，`&mut guess`，跟 C 語言一樣，加入 `&` 字符就代表 pass by reference，也就是把記憶體位置送進 function 中，但和其他語言不一樣的是：送進去也需要考慮這個東西是不是可變的 `mut`，也就是說，如果把 `&guess` 替換成原本的引數（在這個情況下會出問題）
:::info
這個會在 Lesson-4 中提到
:::

再來看到最後的 `.except()`，手冊上提到這個是用來除錯使用的程序，當做完 `read_line` 之後，內部會有一個 Result，這個 Result 主要會有兩大項目，一個是 `OK`，另外一個是 `Err`(作用有點類似 [Errno](https://man7.org/linux/man-pages/man3/errno.3.html))

:::info
最後就是 std output，這邊不多敘述，因為 `{}` 的作用與 C 語言中針對型別的 `%d, %ld ...` 差不多。
:::

> 額外補充一點，`cargo` 這個工具還可以直接把當前 `dependency` 中的所有文件利用網頁顯示給你看（包括範例）只需要在當前目錄下使用 `cargo doc --open`，如此一來所有資訊都會在你面前顯示，這樣也不用擔心不知道要怎麼用。

接下來就是比較的時間了，以下程式碼為官方文件的程式碼。
```rust
    // after insert guessing number, time to compare
    match guess.cmp(&secret_number) {
        Ordering::Greater => println!("too big"),
        Ordering::Less => println!("too small"),
        Ordering::Equal => println!("you win")
    }
```

但以上的程式碼沒有辦法過編譯，由於先前輸入的 `guess` 是一個字串，而這邊放入的 secret_number 為整數，故無法做比較。值得在意的是這邊的 `Ordering` 以及 `match`，他是一個 `enum`(Lesson-6/18 會細講)，這個 `enum` 讓我們確保可以 handle 各種狀況。

### 要如何解決並且轉型別？
在不同程式語言中，解析文字並且得到自己想要的資料是一個非常重要的事情。如同 python 給予的簡易 `__call__` 讓我們可以在各種型別做不同的轉換，[例如](https://leetcode.com/problems/multiply-strings/)：

```python
def string_mutiply(str1: str, str2: str):
    return str(int(str1) * int(str2))
```
如此在 C 語言非常困難的型別轉換，在這邊可以直接利用以下程式碼的 `: <type> = ***.parse()` 來轉換
```rust
let guess: u32 = guess.trim().parse().expect("Please type a number");
```
以上示範了怎麼樣用字串做轉型，等等 `Lesson3` 會細說，這邊只需知道拿來做轉換即可。


最後，我們當然須要讓使用者不停輸入，這樣才可以讓使用者不停的猜，直到猜對。那勢必要把迴圈拿出來用了，以下為 `loop` 的範例


```rust
loop {
    // code here
}
```
作用與 `while(1)` 是相同的。再來只需要當猜中數字的時候跳脫迴圈即可

```rust=
loop{
    println!("You guessed {guess}");
    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(errno) => {
            println!("errno : {errno}");
            continue;},
    };
    
    match guess.cmp(&secret_number) {
        Ordering::Greater => println!("too big"),
        Ordering::Less => println!("too small"),
        Ordering::Equal => {
            println!("you win");
            break;
        }
    }
}
```
如此一來，當猜中數字之後就可以跳脫迴圈。

這邊可以注意到第 3 行的部分，把後面的 except （也就是錯誤時跳出錯誤並且停掉主程式） 而改成 `match` 讓後續回傳剛剛所說的 `enum`，如此一來我們就可以處理錯誤，而不是跳脫錯誤了。

至此，我們已經完成了 rust 的第一個猜數字專案。


# Lesson-3 variable types, function, control flow

這個章節會特別注重於型別以及相關規範

### Const v.s. immutables
在前一個章節我一直有一個疑問，為甚麼宣告變數不用型別，為甚麼要分可變動 (mutable) 跟不可變動 (immutable)，而且為甚麼存在不可變動變數時，又存在常數 (const)：原來是為了使編譯更快速，以及讓程式開發者更好的去調整
#### 常數 constants

在 Rust 的命名邏輯中，常數通常以 ==全大寫==、==中間以底線分開==，這兩個規則命名，當定義了常數例如：
```rust
const THREE_HOURS_IN_SECOND: u32 = 60 * 60 * 3;
```
編譯器會將這個結果 `10800` 給定義為常數，而執行時也不會真正執行三次乘法（浪費時間）

:::info
將常數定義在好的位置有助於解釋程式碼以及加速開發
:::
#### 變數 variables
變數又分為可變動以及不可變動（先前已經提過），而跟 const 最大的不同在於 *shadowing*，有點類似重新宣告。
我們可以用以下程式碼來測試看看 var 跟 const 的差別
```rust=
const THREE_HOURS_IN_SECOND: u32 = 60 * 60 * 3;
fn main() {
    println!("{THREE_HOURS_IN_SECOND}");
    let THREE_HOURS_IN_SECOND = THREE_HOURS_IN_SECOND * 3;
    println!("{THREE_HOURS_IN_SECOND}");
}
```
這樣沒有辦法編譯，原因就是因為這個 `THREE_HOURS_IN_SECOND` 並不是變數，他沒有辦法再這個 `scope` 下被 `shadowing`

讓我們用另外一個形式試試看
```rust=
fn main() {
    let THREE_HOURS_IN_SECOND = 60 * 60 * 3;
    println!("{THREE_HOURS_IN_SECOND}");
    let THREE_HOURS_IN_SECOND = THREE_HOURS_IN_SECOND * 3;
    println!("{THREE_HOURS_IN_SECOND}");
}

```
這樣就可以編譯成功了，但還是不建議將變數命名成常數的樣子

整體來說 `let` 讓我們可以重新定義這個變數而不會

### implicit and explicit
Rust 比 C 好的一點即利用 let 可以宣告所有類型的變數（看你怎麼樣賦值），而也可以和 compiler 宣告清楚這個型別：

```rust
let a = 1; // compiler 認定為有號 32 位元整數
let b = 3.12; // 有號 32 位元浮點數
let c: i32 = 1;
let d: f32 = 3.12;
```
上述的四行會做到一樣的事情，就看程式設計者想要怎麼設計了。


:::info
有很多寫法與 C 語言雷同，這邊不會贅述，以下只會給予不同型別宣告的範例
:::

```rust=
/* scalar type */
let a = 1;
let a: i8 i16 i32 i64 i128 = 1; /* 前面取一個用 */
let a: u8 u16 u32 u64 u128 = 1; /* 同理 */
let a: bool = false;
let a: char = '😻';
/* compound type */
// 1. tuple
let tup: (i32, u32, bool) = (1, 3, true);
    // 取出的方法如下：
println!("{tup.0}, {tup.1}, {tup.2}");
// 2. array 有幾種不同的宣告方法，後面會做說明
let a = [1, 2, 3, 4, 5];  // 直接賦值
let a = ['1', '2', '3'];  // 以此類推，可以在 array 內部宣告相同的 dtype
let a: [i32; 5];          // 代表這個 arr 中有 5 個 i32 的值
let a: [3; 5];            // 代表這個 arr 中有 5 個 3 的數值 = [3, 3, 3, 3, 3]
    // 取出的方法如下：
println!("{a[0]}, {a[1]}, {a[2]}");
```


## 函式 function

至此，我覺得最困難的就是 function，因為有非常多種宣告方法。這邊是語法最容易搞混的地方了。所以接下來我都會盡可能的用解釋帶例子讓讀者熟悉寫法。

* normal function declaration 
    * 一般來說，只需要在最前面放上 `fn` 的關鍵字就可以宣告一個 function
```rust
fn function () {}
```
到此為止應該會有人想問，為甚麼沒有回傳任何值，那是因為在 rust 當中並沒有 void function，在每一個 function 都必須要有一個箭頭定義他的回傳型別

==比較特別的就是在一個花括號的 `scope` 當中，預設最後一個 `expression` 為回傳值==
所以可以製作出下方的特殊函式

```rust=
fn zero () -> i32 {
    return 0;
}
fn zero_no_return () -> i32 {
    0
}
```
這兩個函式做了完全一樣的事情，回傳 0，但第五行為這個花括號的 `scope` 當中最後一個 expression，所以預設為回傳值

:::info
也可以用這個方法來定義變數
```rust
let y: i32 = {
    let x: i32 = 15;
    15 + 5
}
```
同理，花括號當中最後一個 expression 為回傳值，所以 y 理當為 20
:::

## Control Flow

終於來到怎麼樣撰寫 `if else` 了，接下來會介紹語法以及特殊寫法。

```rust
if expr {
    // do something    
} else {
    // do other thing
}
```
**和 C 語言最大的不同點為，expr 必須要布林值 (`bool`)，而不是大於等於 1 就為 `true`。**
> You must be explicit and always provide if with a Boolean as its condition.

到目前為止，我們學到了一個 scope 是可以有回傳值的，我們馬上來應用一下（這是到目前為止我覺得最刺激最好玩的部分）

```ruby
let condition = true;
let variable = if condition {5} else {6};
```
這邊應該可以猜到會出現什麼樣的東西了 (可以利用這種定義方法做出寫出很好玩的 code)
```bash
     Running `target/debug/Lesson_3_types`
5
```
### `loop`
`loop` 這個關鍵字定義了類似 `while true` 的行為，但又和後者不同，最特別的點在於可以回傳值，也就是在 break 後面接上一個要回傳的值，可以當作這個 `scope` 的 `return value`
```rust
let result = loop {
    counter += 1;

    if counter == 10 {
        break counter * 2;
    }
};
```

再來另外一個特別的地方就是，`break` 也可以當作多層迴圈中用來控制流程的關鍵字，以下為例子：
```rust=
fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}
```
第一次看到的人一定會覺得很奇怪，為甚麼第 `3` 行長成這樣？這邊有點類似 C 語言中的 [goto](https://hackmd.io/@sysprog/c-control-flow)，在其他語言若要跳脫多重迴圈，比較簡易的方法是使用 `goto, return` 或是用多個 `if else` 去管理，後者會導致寫出來的程式碼缺少了美感並且不容易懂。從這邊即可得知這個 `break` 以及 `label` 的設計意義了。並且從剛剛的教學可以看到，`break` 後面可以接一個回傳值，那在這種跳脫多層迴圈的狀況呢？

非常簡單，只需要在 `label` 後面放入回傳值即可
```diff
-   break 'counting_up;
+   break 'counting_up 777;
```

若把前面的第 13 行改為以上的樣子，就可以在這個 scope 當中回傳 777（不過還是記得要把它改成變數型式）
### `while` `for`

就與大部分程式語言一樣，當 `while` 後方的 condition 為真時，會不停的跑內部的行為

而 `for` 的語法反而與 `python` 雷同

```rust
let a = [10, 20, 30, 40, 50];

for element in a {
    println!("the value is: {element}");
}
```
輸出如下：
```bash
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
```
如此方便的語法，可以協助我們爾後在不確定 array 長度時遍歷整個 array

這個 section 的最後，我想要講一下 `..` 的功能
```rust
for i in (1..100) {
    println!("{i}");
}
```
這邊的 `..` 其實就是把頭尾之間的所有整數展開，幫我們更好定義了變數會走動的範圍。