Rust 作為一門系統程式語言,提供強大的函式與控制流程機制,讓開發者能有效組織程式碼邏輯。函式定義包含引數型別宣告與傳回值型別,並可透過隱式傳回簡化程式碼。控制流程包含條件判斷式 ifelse ifelse 以及迴圈 loopwhilefor,實作程式流程的多樣化控制。Rust 的所有權機制是其核心特性,透過嚴格的規則管理記憶體,避免常見的記憶體錯誤,例如 dangling pointers 和 data races。所有權規則確保每個值只有一個所有者,並在變數超出作用域時自動釋放記憶體。堆積疊與堆積的記憶體分配機制,配合所有權規則,讓 Rust 在編譯時期就能有效管理記憶體,提升程式效能與安全性。

Rust 程式語言基礎:函式與控制流程

Rust 語言是一種系統程式語言,其註解方式與其他系統程式語言相似,使用 /**/ 包圍多行註解:

/* 這是一個多行註解
   跨越多行。 */

函式

函式用於將相關的程式碼組織成模組,以便重複使用。我們已經看過 main() 函式,這是每個 Rust 程式都必須有的特殊函式,作為程式執行的入口點。在 Rust 中,使用 fn 關鍵字宣告函式,後面跟著函式名稱和括號內的引數。引數的型別必須明確指定。函式的核心邏輯被包圍在一對大括號內。

函式定義語法

fn 函式名稱(引數1: 型別, 引數2: 型別, ...) {
    // 函式主體
    // 核心邏輯在此
}

內容解密:

  • fn 是宣告函式的關鍵字。
  • 函式名稱 是自定義的函式名,建議使用小寫英文字母並以下劃線分隔多個單詞。
  • 引數1: 型別, 引數2: 型別, ... 是函式的引數列表,必須指定每個引數的型別。
  • 函式的主體被包圍在大括號 {} 內,包含具體的程式邏輯。

一旦定義了函式,就可以在其他地方呼叫它多次,並傳入相同或不同的引數值。呼叫函式的方式是寫出函式名稱後跟括號內的引數值。

範例:定義和呼叫函式

// func_a:無引數
fn func_a(){
    println!("func_a 被呼叫...");
}

// func_b:兩個引數
fn func_b(a: u8, b: f64){
    println!("func_b 被呼叫... {}, {}", a, b);
}

fn main() {
    func_a();
    func_b(5, 1.5);
    func_b(3, 5.0);
}

輸出結果:

func_a 被呼叫...
func_b 被呼叫... 5, 1.5
func_b 被呼叫... 3, 5

內容解密:

  • func_afunc_b 是兩個不同的函式,分別具有不同的引數列表。
  • main 函式中,我們依序呼叫了 func_afunc_b,並傳入不同的引數。

函式主體:陳述式與表示式

函式主體由指令組成,可以是陳述式或表示式。陳述式執行某些動作但不傳回任何值,而表示式則評估出一個結果值,可用於程式的其他部分。

陳述式 vs. 表示式

let x = 5; // 陳述式,因為它不傳回任何值
let a = x + y; // 這裡,x + y 是表示式,而整個指令是陳述式

內容解密:

  • 陳述式(如 let x = 5;)執行動作但不傳回任何值,因此不能將其指定給其他變數。
  • 表示式(如 x + y)評估出一個結果值,可以用於指定或其他操作。

傳回值的函式

函式也可以傳回值給呼叫它的程式碼。如果一個函式預期傳回一個值,則必須在函式宣告中指定傳回值的型別,使用 -> 符號。

範例:具有傳回值的函式

fn sum(x: i16, y: i16) -> i16 {
    return x + y;
}

fn main() {
    let result = sum(5, 10);
    println!("5 和 10 的總和 = {}", result);
}

輸出結果:

5 和 10 的總和 = 15

內容解密:

  • sum 函式接受兩個 i16 型別的引數,並傳回它們的總和,型別也是 i16
  • main 函式中,我們呼叫了 sum 函式並將結果指定給變數 result

Rust 也允許使用隱式傳回,即省略 return 關鍵字,直接將最後一個表示式的值作為傳回值。

範例:隱式傳回

fn sum(x: i16, y: i16) -> i16 {
    x + y // 省略 return,直接傳回值
}

控制流程陳述式與迴圈

Rust 使用控制流程工具,如 if 表示式和迴圈,來控制程式碼的執行流程。

if 表示式

fn main() {
    let a = 10;
    if a == 10 {
        println!("a 等於 10");
    }
}

輸出結果:

a 等於 10

內容解密:

  • if 表示式用於根據條件執行特定的程式碼區塊。
  • 在這個例子中,變數 a 的值為 10,因此條件成立,印出對應的訊息。

if…else 表示式

當有多個互斥條件需要處理時,可以使用 if...else 表示式。

fn main() {
    let a = 10;
    if a == 10 {
        println!("a 等於 10");
    } else {
        println!("a 不等於 10");
    }
}

輸出結果:

a 等於 10

if…else if 表示式

對於多個非重疊條件,可以使用 if...else if 結構。

fn find_grade(marks: u8){
    if marks >= 85{
        println!("獲得的等級:A");
    } else if marks >= 70{
        println!("獲得的等級:B");
    }
}

圖表翻譯:

此圖示呈現了一個簡單的流程圖,用於展示根據分數評定等級的邏輯流程。 圖表翻譯: 此圖表顯示了一個簡單的決策樹,用於根據學生的分數評定等級。首先檢查分數是否大於或等於85,如果是,則評為A級;如果分數在70到84之間,則評為B級;否則,可能會有其他處理邏輯。

Rust 程式語言中的控制流程與迴圈

在前面的章節中,我們探討了 Rust 中的變數宣告、資料型別和函式。在本章節中,我們將深入瞭解 Rust 中的控制流程,包括條件判斷和迴圈。

條件判斷:if 表示式

Rust 中的條件判斷使用 if 表示式來實作。if 表示式根據條件的真假來執行不同的程式碼區塊。

fn find_grade(marks: i32) {
    if marks >= 85 {
        println!("Grade obtained: A");
    } else if marks >= 70 {
        println!("Grade obtained: B");
    } else if marks >= 55 {
        println!("Grade obtained: C");
    } else if marks >= 40 {
        println!("Grade obtained: D");
    } else {
        println!("Grade obtained: F");
    }
}

fn main() {
    let marks = 78;
    find_grade(marks);
}

內容解密:

  • find_grade 函式接受一個 i32 型別的引數 marks,用於判斷學生的成績等級。
  • 使用 ifelse if 來檢查 marks 的值,並根據不同的範圍輸出相應的成績等級。
  • marks 為 78 時,程式輸出 “Grade obtained: B”,因為 78 大於或等於 70 但小於 85。

迴圈

Rust 提供了多種迴圈機制,包括 loopwhilefor

loop 迴圈

loop 迴圈會無限執行,直到遇到 break 陳述式。

fn main() {
    let mut a = 5;
    loop {
        a -= 1;
        println!("a = {}", a);
        if a == 2 {
            break;
        }
    }
}

內容解密:

  • 使用 loop 建立一個無限迴圈。
  • 在迴圈內,每次迭代都將 a 的值減 1,並列印出來。
  • a 等於 2 時,使用 break 陳述式離開迴圈。

while 迴圈

while 迴圈會在條件為真時持續執行。

fn main() {
    let mut a = 5;
    while a > 2 {
        a -= 1;
        println!("a = {}", a);
    }
}

內容解密:

  • 使用 while 建立一個條件迴圈,只要 a > 2 就會持續執行。
  • 在迴圈內,每次迭代都將 a 的值減 1,並列印出來。
  • a 不再大於 2 時,迴圈結束。

for 迴圈

for 迴圈用於遍歷集合中的元素。

fn main() {
    let values = [1, 2, 3, 4];
    for val in values.iter() {
        println!("value = {}", val);
    }
}

內容解密:

  • 使用 for 建立一個遍歷迴圈,遍歷陣列 values 中的每個元素。
  • 在迴圈內,列印每個元素的值。

Rust 的所有權原理與記憶體管理

在閱讀本章節後,您應該能夠理解 Rust 的所有權原理以及如何在 Rust 中管理程式的記憶體。您將學習到 Rust 如何透過其獨特的所有權方法確保記憶體安全。最後,您應該能夠理解參考、借用和切片的概念。

Rust 的所有權原理

所有的程式語言都有某種機制來管理程式所使用的電腦記憶體。像 C 和 C++ 這樣的語言將記憶體的分配和釋放交給程式設計師處理。雖然這種方法賦予了程式設計師很大的權力和控制力,但也增加了引入記憶體錯誤(如無效的記憶體存取和記憶體洩漏)的風險。像 Python 和 Java 這樣的語言具有垃圾收集器,它會定期尋找未被使用的記憶體並將其清理。然而,這種方法可能相當低效,並且無法控制垃圾收集器何時執行。Rust 採取了一種不同且獨特的記憶體管理方法,稱為所有權。在這種方法中,變數根據某些規則負責釋放它們所使用的資源。這些規則可以在編譯時由 Rust 編譯器檢查,並且不會在執行時降低程式的速度。

所有權規則

  1. 一個值只能有一個所有者(變數)。
    let a = 2; // 值 2 的所有者是變數 'a'
    
  2. 當擁有某個值的變數超出範圍時,記憶體會被釋放,該值將被丟棄,如下面的程式碼片段所示:
    fn main() {
        { // 範圍開始
            let x = String::from("abcd"); // x 進入範圍
            println!("x = {}", x);
        } // 範圍結束。x 的值被丟棄
    }
    

變數範圍

變數範圍是指變數有效的程式碼部分。當變數進入某個範圍時,它變得有效,並且在該範圍結束之前保持有效。

堆積疊與堆積記憶體分配

您的程式碼在執行時使用稱為堆積疊和堆積的記憶體段。這些記憶體段中值的儲存方式是不同的。在堆積疊中,值按照到達的順序儲存,並以相反的順序移除,即最後到達的值最先被移除。這種方法稱為後進先出(LIFO)。讓我們從現實生活中舉個例子來理解堆積疊操作的行為。當您將一個盤子放在一疊盤子上時,很自然地把它放在頂部,而當您需要一個盤子時,您會從頂部拿取。堆積疊的工作方式類別似。將專案新增到堆積疊和從堆積疊中移除專案的操作分別稱為推入和彈出操作。

堆積疊操作示意圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Rust 函式控制流程與所有權

package "安全架構" {
    package "網路安全" {
        component [防火牆] as firewall
        component [WAF] as waf
        component [DDoS 防護] as ddos
    }

    package "身份認證" {
        component [OAuth 2.0] as oauth
        component [JWT Token] as jwt
        component [MFA] as mfa
    }

    package "資料安全" {
        component [加密傳輸 TLS] as tls
        component [資料加密] as encrypt
        component [金鑰管理] as kms
    }

    package "監控審計" {
        component [日誌收集] as log
        component [威脅偵測] as threat
        component [合規審計] as audit
    }
}

firewall --> waf : 過濾流量
waf --> oauth : 驗證身份
oauth --> jwt : 簽發憑證
jwt --> tls : 加密傳輸
tls --> encrypt : 資料保護
log --> threat : 異常分析
threat --> audit : 報告生成

@enduml

圖表翻譯: 此圖示展示了堆積疊的基本操作,包括推入和彈出。

String 型別

到目前為止,我們一直在處理具有固定大小的資料型別,這些大小是編譯器已知的。這些值儲存在堆積疊中,並在超出範圍時被彈出。為了理解所有權原理,我們需要研究一種大小無法被編譯器知道並儲存在堆積上的資料型別。像向量、字串、盒子或使用者定義的自定義型別都屬於這一類別。在本章中,我們將使用 String 資料型別來瞭解所有權是如何工作的。字串是一系列字元,Rust 提供了兩種處理它們的方式。我們已經在一些示例中使用過字串文字,它們是一系列字元,被雙引號包圍,如 “rust”。這些是由編譯器硬編碼到可執行檔中的,並在程式執行時載入到記憶體中。

使用 String::from() 建立字串

fn main() {
    let mut x = String::from("Hello");
    x.push_str(" Rust");
    println!("string = {}", x);
}

輸出結果:string = Hello Rust

內容解密:

  1. String::from() 函式用於建立一個新的字串。
  2. push_str() 方法用於將一個字串追加到現有的字串後面。
  3. println! 宏用於列印最終的字串結果。