iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1
自我挑戰組

從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用系列 第 4

[Day 4] 建構任務管理工具:Rust 變數與資料類別的實戰應用

  • 分享至 

  • xImage
  •  

在這篇文章中,我們將模擬開發一個任務管理工具,並透過這個過程深入了解 Rust 的變數與資料類別的使用方式。我們將處理任務的新增、修改、刪除等功能,並在過程中展示如何定義變數、使用各種資料類別,以及解決開發過程中的常見問題。

情境描述

假設我們正在開發一個基本的任務管理工具,這個工具需要滿足以下功能需求:

  1. 新增任務:可以將新任務加入清單,每個任務包含標題、描述、優先級、狀態。
  2. 修改任務:可以修改現有任務的資料,像是標題、描述、或是更改任務狀態。
  3. 刪除任務:可以將某一個任務從清單中移除。
  4. 查詢任務:可以根據優先級或狀態篩選出相關任務。

在此情境下,我們會使用 Rust 來處理各種資料類別,如字串、數字、布林值、結構體等,同時探討 Rust 的變數管理方法,如可變性、所有權與借用等。


一、變數與基本資料類別

定義一個任務

每個任務都有標題、描述、優先級與狀態。我們可以使用 struct 來定義一個任務的結構,並使用不同的資料類別來代表這些欄位。

struct Task {
    title: String,
    description: String,
    priority: u8,  // 優先級:1 到 5,u8為uint8 0-255的無符號整數格式
    is_completed: bool,  // 任務是否完成
}

新增任務

我們可以定義一個函數來新增任務,並將其加入到一個任務清單(向量)中。在這裡,Vec 是一個可變長度的集合,類似 Python 的 list,但它需要明確定義元素的類別。

fn add_task(tasks: &mut Vec<Task>, title: String, description: String, priority: u8) {
    let new_task = Task {
        title,
        description,
        priority,
        is_completed: false,
    };
    tasks.push(new_task);
}

讓我們用 Python 開發者能夠理解的方式來詳細說明 Rust 中這個 add_task 函數的參數:

  1. tasks: &mut Vec<Task>
    • 在 Rust 中,tasks 是一個可變的借用 (&mut) 指向一個任務的向量 (Vec<Task>)。
    • Python 中沒有「所有權」和「借用」這樣的概念,因此可以類比成 Python 的函數接收一個「可變」的 list。例如,當 Python 函數接收一個 list 並在函數內進行修改時,這些修改會影響外部傳入的 list,這和 Rust 中的 &mut 概念類似,在 Python 中,tasks 是一個 list,可以直接傳入並修改,而 Rust 中需要使用 &mut 明確告訴編譯器我們希望修改這個集合,否則 Rust 的預設是所有東西都是不可變的(immutable)。。

Python 類比

def add_task(tasks: list, title: str, description: str, priority: int):
   new_task = {
       "title": title,
       "description": description,
       "priority": priority,
       "is_completed": False
   }
   tasks.append(new_task)
  1. title: Stringdescription: String

    • 在 Rust 中,String 表示一個動態分配的字串,它類似於 Python 的 str。Rust 的 String 是一個可變的字串類型。
  2. priority: u8

    • u8 是 Rust 中一個 8 位元的無符號整數,範圍是從 0255。在 Python 中,我們可以將其類比為 Python 的 int 類型,但 Python 的 int 沒有固定的位元長度,數字的大小只有記憶體限制。

使用範例

fn main() {
    let mut task_list: Vec<Task> = Vec::new();  // 建立一個空的任務清單
    
    // 新增兩個任務
    add_task(&mut task_list, String::from("撰寫報告"), String::from("完成年度報告"), 3);
    add_task(&mut task_list, String::from("準備會議"), String::from("準備週會的簡報"), 2);
    
    println!("目前任務清單:{} 項", task_list.len());
}

這段程式碼的作用是展示如何使用之前定義的 add_task 函數來新增任務,並將它們加入到一個任務清單中。讓我們逐步說明這段程式碼的運作方式:

1. 建立任務清單

let mut task_list: Vec<Task> = Vec::new();
  • 這一行宣告了一個名為 task_list 的變數,類別是 Vec<Task>,也就是一個可變的向量,儲存 Task 結構。它一開始是空的。
  • Vec::new() 創建了一個空的向量。
  • 使用 mut 表明這個向量是可變的,因為稍後要使用 add_task 函數來修改它。

2. 新增兩個任務

add_task(&mut task_list, String::from("撰寫報告"), String::from("完成年度報告"), 3);
add_task(&mut task_list, String::from("準備會議"), String::from("準備週會的簡報"), 2);
  • 使用之前定義的 add_task 函數來將新任務加入 task_list
  • &mut task_list 是將 task_list可變借用&mut)的方式傳入,這允許函數修改這個向量。
  • String::from("撰寫報告")String::from("完成年度報告") 用來建立 Tasktitledescription,分別是「撰寫報告」和「完成年度報告」。
  • 3 表示優先級是 3(數值在 u8 類別的範圍內)。
  • 第二個 add_task 則是建立一個任務,標題為「準備會議」,描述為「準備週會的簡報」,優先級是 2。

3. 輸出目前任務清單中的項目數

println!("目前任務清單:{} 項", task_list.len());
  • println! 是 Rust 用來在控制台輸出的巨集。
  • task_list.len() 是取得向量 task_list 中的元素個數,表示目前任務清單中有幾個任務。
  • 這一行會輸出字串 "目前任務清單:2 項",因為我們已經加入了兩個任務。

類比為 Python

如果將這段程式碼轉換為 Python,會類似於這樣的形式:

def add_task(tasks, title, description, priority):
    new_task = {
        "title": title,
        "description": description,
        "priority": priority,
        "is_completed": False
    }
    tasks.append(new_task)

def main():
    task_list = []  # 建立一個空的任務清單

    # 新增兩個任務
    add_task(task_list, "撰寫報告", "完成年度報告", 3)
    add_task(task_list, "準備會議", "準備週會的簡報", 2)

    print(f"目前任務清單:{len(task_list)} 項")

if __name__ == "__main__":
    main()

這段 Python 程式的功能與 Rust 程式是相同的,主要的差異在於 Rust 中需要明確處理借用與可變性,而 Python 不需要這樣的語法。


二、修改任務:可變變數與借用

當我們想修改任務時,必須處理 Rust 中的可變性借用。預設情況下,變數是不可變的,我們需要使用 mut 來標記可變的變數,並且在函數中借用它們進行修改。

修改任務狀態

假設我們想將某個任務標記為已完成,可以使用一個函數來修改任務的 is_completed 欄位:

fn complete_task(task: &mut Task) {
    task.is_completed = true;
}

在這裡,我們使用了可變借用 &mut 來讓函數可以修改任務的狀態。

使用範例

fn main() {
    let mut task_list: Vec<Task> = Vec::new();
    add_task(&mut task_list, String::from("撰寫報告"), String::from("完成年度報告"), 3);
    
    // 將第一個任務標記為已完成
    if let Some(task) = task_list.get_mut(0) {
        complete_task(task);
    }
    
    println!("任務:{},已完成? {}", task_list[0].title, task_list[0].is_completed);
}

這段程式碼的目的是展示如何在 Rust 中使用可變借用來修改結構中的欄位值,特別是修改任務的 is_completed 狀態。讓我們逐步解析這段程式碼的運作:

1. 函數 complete_task

fn complete_task(task: &mut Task) {
    task.is_completed = true;
}
  • 這個函數接受一個 Task 結構的可變借用&mut Task),這意味著函數可以在不取得任務所有權的情況下,修改任務的欄位值。
  • task.is_completed = true; 是將傳入的任務的 is_completed 欄位設置為 true,表示該任務已完成。

2. main 函數中的任務清單

fn main() {
    let mut task_list: Vec<Task> = Vec::new();
    add_task(&mut task_list, String::from("撰寫報告"), String::from("完成年度報告"), 3);
  • let mut task_list: Vec<Task> = Vec::new(); 創建了一個空的 Vec<Task> 任務清單,它是可變的(mut)。
  • add_task 函數被呼叫,將一個新任務「撰寫報告」添加到 task_list 中,這與之前的範例相同。這個任務的優先級為 3,並且初始時 is_completed 被設置為 false

以下是將關於 Some 的說明加入到你指定的段落中:


3. 將第一個任務標記為已完成

if let Some(task) = task_list.get_mut(0) {
    complete_task(task);
}
  • task_list.get_mut(0) 是用來取得向量中索引 0 的任務的可變借用get_mut 返回一個 Option<&mut Task>,也就是說這個操作可能會返回 None(如果向量中沒有第 0 個元素),或是返回一個可變借用 &mut Task(如果存在第 0 個元素)。
  • if let Some(task) = task_list.get_mut(0) 是一種模式匹配,檢查是否有第 0 個任務存在並取得其可變借用。如果有這個任務,則執行 complete_task(task) 函數。
  • Some 表示 Option 中存在值,與 None 相對應,類似於 Python 中檢查變數是否為 None 的邏輯。當 Some 後面的task_list.get_mut(0)包含值時,將會將其定義為task,並繼續執行我們後續{}內的程序。
  • complete_task(task) 調用前面的函數,將該任務標記為已完成,設置 task.is_completed = true

4. 輸出修改後的任務狀態

println!("任務:{},已完成? {}", task_list[0].title, task_list[0].is_completed);
  • println! 用來輸出第一個任務的 title(標題)和 is_completed(是否完成)的狀態。
  • 這裡透過索引 task_list[0] 直接訪問向量中的第 0 個任務,並輸出其 titleis_completed 狀態。由於上面已經使用 complete_task 將該任務標記為已完成,因此這裡會輸出:
任務:撰寫報告,已完成? true

三、移除任務:所有權與 Vec 操作

當我們想要從清單中刪除任務時,可以使用 Vecremove 方法。這涉及到 Rust 的所有權系統,我們需要注意變數的所有權轉移與借用。

刪除任務

fn remove_task(tasks: &mut Vec<Task>, index: usize) {
    if index < tasks.len() {
        tasks.remove(index);
    }
}

在這裡,remove 會將指定索引的任務從清單中移除,並轉移其所有權。

使用範例

fn main() {
    let mut task_list: Vec<Task> = Vec::new();
    add_task(&mut task_list, String::from("撰寫報告"), String::from("完成年度報告"), 3);
    add_task(&mut task_list, String::from("準備會議"), String::from("準備週會的簡報"), 2);
    
    // 刪除第一個任務
    remove_task(&mut task_list, 0);
    
    println!("剩餘任務數量:{}", task_list.len());
}

這段程式碼的目的在於展示如何從任務清單(向量 Vec<Task>)中刪除指定索引的任務,並且正確處理 Rust 的所有權系統。讓我們逐步解析這段程式碼的運作方式:

1. remove_task 函數

fn remove_task(tasks: &mut Vec<Task>, index: usize) {
    if index < tasks.len() {
        tasks.remove(index);
    }
}
  • 函數參數

    • tasks: &mut Vec<Task>:表示傳入的任務清單是以可變借用&mut)的方式傳遞。這讓函數能夠修改傳入的向量,而不轉移其所有權。
    • index: usize:表示我們希望刪除的任務索引,類別是 usize,這是 Rust 中用來表示非負整數的類別,通常用於表達向量的索引值。
  • 邏輯

    • if index < tasks.len():這個條件檢查索引是否有效,即確保要刪除的索引沒有超出向量的範圍。如果條件為真,則呼叫 remove 方法。
    • tasks.remove(index)Vecremove 方法會從向量中刪除指定索引的元素,並將該元素的所有權轉移出來。在這裡我們只關注刪除操作,並不需要處理轉移後的值,所以直接移除即可。

四、查詢與篩選任務

當我們想根據任務的優先級或狀態進行篩選時,可以使用 Rust 的迭代器與閉包。

根據優先級篩選的函數

fn filter_by_priority(tasks: &Vec<Task>, min_priority: u8) -> Vec<&Task> {
    tasks.iter().filter(|task| task.priority >= min_priority).collect()
}

使用範例

fn main() {
    let mut task_list: Vec<Task> = Vec::new();
    add_task(&mut task_list, String::from("撰寫報告"), String::from("完成年度報告"), 3);
    add_task(&mut task_list, String::from("準備會議"), String::from("準備週會的簡報"), 2);
    
    // 篩選優先級大於等於 2 的任務
    let high_priority_tasks = filter_by_priority(&task_list, 2);
    
    for task in high_priority_tasks {
        println!("高優先級任務:{}", task.title);
    }
}

這段程式碼展示了如何使用 Rust 的迭代器閉包來根據任務的優先級進行篩選,並將篩選後的結果回傳。程式碼的重點是透過迭代器遍歷 task_list,過濾出符合條件的任務,並最終輸出這些篩選結果。讓我們逐步解析這段程式碼的邏輯:

  • 函數參數

    • tasks: &Vec<Task>:這裡傳入一個任務清單的不可變借用(&Vec<Task>)。因為我們只需要查詢(篩選)任務,並不會修改清單,因此用不可變借用即可。
    • min_priority: u8:指定要篩選的最小優先級。這個值將用來判斷哪些任務應該被篩選出來。
  • 邏輯

    • tasks.iter():這是一個迭代器,會遍歷 tasks 向量中的每個任務,類似於 Python 的 for task in tasks
    • filter(|task| task.priority >= min_priority):這段程式碼使用迭代器的 filter 方法,filter 接受一個閉包(即匿名函數)。閉包會對每個任務執行判斷,如果任務的 priority 大於等於 min_priority,則保留這個任務,否則略過。
      • |task| 是閉包的語法,代表接受一個參數 task,並返回一個布林值(是否滿足篩選條件)。
    • .collect():這個方法將篩選後的任務收集到一個新向量中。因為我們要篩選的是任務的參考(&Task),而不是任務本身,因此回傳類別是 Vec<&Task>,表示一個裝有任務參考的向量。

五、總結

透過這個任務管理工具的範例,我們涵蓋了 Rust 中的變數與資料類別的多種使用情境,包括不可變變數、可變變數、結構體、向量與所有權等。這樣的實際應用不僅能幫助你更好地理解 Rust 的核心概念,還能讓你在實際開發中運用自如。

在這篇文章中,我們探索了:

  • 如何定義與使用變數
  • 借用與所有權的核心概念
  • 如何在函數中處理可變資料
  • 使用 Vec 處理可變長度的集合

完整程式碼

這裡是一個完整的可執行程式碼範例,讀者可以直接複製貼上並執行,來檢視任務管理工具的運作情形。

struct Task {
    title: String,
    description: String,
    priority: u8,       // 優先級:1 到 5
    is_completed: bool, // 任務是否完成
}

fn add_task(tasks: &mut Vec<Task>, title: String, description: String, priority: u8) {
    let new_task = Task {
        title,
        description,
        priority,
        is_completed: false,
    };
    tasks.push(new_task);
}

fn complete_task(task: &mut Task) {
    task.is_completed = true;
}

fn remove_task(tasks: &mut Vec<Task>, index: usize) {
    if index < tasks.len() {
        tasks.remove(index);
    }
}

fn filter_by_priority(tasks: &Vec<Task>, min_priority: u8) -> Vec<&Task> {
    tasks.iter().filter(|task| task.priority >= min_priority).collect()
}

fn main() {
    let mut task_list: Vec<Task> = Vec::new();

    // 新增任務
    add_task(
        &mut task_list,
        String::from("撰寫報告"),
        String::from("完成年度報告"),
        3,
    );
    add_task(
        &mut task_list,
        String::from("準備會議"),
        String::from("準備週會的簡報"),
        2,
    );

    // 完成第一個任務
    if let Some(task) = task_list.get_mut(0) {
        complete_task(task);
    }

    // 顯示任務清單
    println!("目前任務清單:");
    for task in &task_list {
        println!(
            "任務:{},描述:{},優先級:{},已完成? {}",
            task.title, task.description, task.priority, task.is_completed
        );
    }

    // 刪除第二個任務
    remove_task(&mut task_list, 1);

    // 篩選優先級大於等於 2 的任務
    println!("\n高優先級任務清單:");
    let high_priority_tasks = filter_by_priority(&task_list, 2);
    for task in high_priority_tasks {
        println!("高優先級任務:{}", task.title);
    }
}

上一篇
[Day 3] Rust 語法速覽:與 Python 的基本語法比較
下一篇
[Day 5] 深入理解所有權與借用:Rust 的記憶體安全之鑰
系列文
從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言