返回文章列表

Rust 閉包實作路由系統與迭代器詳解

本文探討 Rust 中閉包的應用,示範如何使用閉包構建一個簡易的路由系統,並詳細介紹迭代器的使用方法、特性與應用場景,涵蓋 iter、iter_mut、IntoIterator、drain 等方法,以及 map 和 filter 等常用迭代器介面卡。文章同時也討論了 Rust 中 MVC

Web 開發 Rust

Rust 的閉包特性使其成為構建路由系統的理想工具。透過將不同路由的處理邏輯封裝在閉包中,可以實作程式碼的模組化和靈活性。本文提供的路由系統範例展示瞭如何使用 HashMap 儲存路由規則,並利用閉包實作動態的請求處理。迭代器是 Rust 中另一個重要的概念,它提供了一種高效且安全的方式來遍歷集合。iteriter_mutinto_iter 方法分別提供了不同層級的資料存取控制,而 drain 方法則允許在迭代的同時修改集合。迭代器介面卡,例如 mapfilter,則進一步增強了迭代器的功能,使其能夠進行更複雜的資料處理。理解和運用這些特性,能有效提升 Rust 開發效率。

使用閉包實作簡單的路由系統

在前面的章節中,我們探討了閉包的基本概念及其在 Rust 中的應用。現在,讓我們嘗試撰寫一個簡單的路由系統,以展示閉包在實際開發中的價值。

定義 HTTP 請求與回應結構

首先,我們需要定義用於表示 HTTP 請求和回應的結構:

struct Request {
    method: String,
    url: String,
    headers: HashMap<String, String>,
    body: Vec<u8>
}

struct Response {
    code: u32,
    headers: HashMap<String, String>,
    body: Vec<u8>
}

內容解密:

  • Request 結構用於表示 HTTP 請求,包含請求方法、URL、標頭和請求主體。
  • Response 結構用於表示 HTTP 回應,包含狀態碼、標頭和回應主體。
  • 使用 HashMap 儲存標頭資訊,允許動態新增和查詢標頭。
  • Vec<u8> 用於儲存請求或回應的主體,適用於二進位資料。

實作基本路由系統

接下來,我們將實作一個簡單的路由系統,用於將 URL 對映到對應的處理函式:

type BoxedCallback = Box<dyn Fn(&Request) -> Response>;

struct BasicRouter {
    routes: HashMap<String, BoxedCallback>
}

impl BasicRouter {
    /// 建立一個空的路由系統。
    fn new() -> BasicRouter {
        BasicRouter { routes: HashMap::new() }
    }

    /// 新增一個路由規則。
    fn add_route<C>(&mut self, url: &str, callback: C)
    where
        C: Fn(&Request) -> Response + 'static
    {
        self.routes.insert(url.to_string(), Box::new(callback));
    }

    /// 處理傳入的 HTTP 請求。
    fn handle_request(&self, request: &Request) -> Response {
        match self.routes.get(&request.url) {
            None => not_found_response(),
            Some(callback) => callback(request)
        }
    }
}

內容解密:

  • BasicRouter 結構使用 HashMap 儲存 URL 與對應的處理函式。
  • add_route 方法允許新增路由規則,並將處理函式包裝為 BoxedCallback 型別。
  • handle_request 方法根據請求的 URL 查詢對應的處理函式並執行,若找不到則傳回 404 回應。
  • 使用 Box<dyn Fn(&Request) -> Response> 儲存不同型別的閉包,實作靈活的回呼機制。

設計模式的考量

在許多程式語言中,常見的設計模式如 MVC(Model-View-Controller)被廣泛應用。然而,在 Rust 中,由於所有權和生命週期的限制,直接實作 MVC 模式可能會遇到困難。Rust 要求明確的所有權和避免參考環,因此需要調整設計。

內容解密:

  • MVC 模式涉及多個物件之間的相互參考,但在 Rust 中,這種設計會導致所有權問題。
  • Rust 的解決方案包括透過引數傳遞必要的參考、使用 ID 代替直接參考,或採用其他設計模式如 Flux 架構。

迭代器(Iterators)

迭代器是一種能夠產生一系列值的物件,通常用於迴圈操作。Rust 的標準函式庫提供了多種迭代器,用於遍歷向量、字串、雜湊表等集合,同時也提供了從輸入串流讀取文字行、網路伺服器接收連線、在通訊通道上接收其他執行緒傳送的值等迭代器。當然,您也可以根據自己的需求實作迭代器。Rust 的 for 迴圈為使用迭代器提供了自然的語法,而迭代器本身也提供了豐富的方法,用於對值進行對映、過濾、連線、收集等操作。

Rust 的迭代器具有靈活性、表達力和高效性。考慮以下函式,該函式傳回前 n 個正整數的總和(通常稱為第 n 個三角形數):

fn triangle(n: i32) -> i32 {
    let mut sum = 0;
    for i in 1..n+1 {
        sum += i;
    }
    sum
}

表示式 1..n+1 是一個 Range<i32> 值。Range<i32> 是一種迭代器,它產生從起始值(包含)到結束值(不包含)之間的一系列整數。因此,您可以將其用作 for 迴圈的操作物件,以計算從 1 到 n 的值的總和。

內容解密:

  • 1..n+1:這是一個範圍(Range)表示式,產生從 1 到 n 的整數序列。
  • for i in 1..n+1:使用 for 迴圈遍歷該序列,將每個值賦給 i,並累加到 sum

然而,迭代器也具有 fold 方法,可以用於實作相同的功能:

fn triangle(n: i32) -> i32 {
    (1..n+1).fold(0, |sum, item| sum + item)
}

從初始值 0 開始,fold 方法對 1..n+1 產生的每個值應用閉包 |sum, item| sum + item,並將結果作為新的累積值。最後傳回的累積值即為整個序列的總和。

內容解密:

  • (1..n+1).fold(0, |sum, item| sum + item):使用 fold 方法計算序列的總和。
  • |sum, item| sum + item:這是一個閉包,將累積值 sum 和當前值 item 相加,傳回新的累積值。

在 Rust 的發行版本中,編譯器能夠將上述迭代器的使用轉化為高效的機器碼。Rust 的迭代器設計旨在提供高效的抽象,且在典型使用場景下幾乎沒有額外開銷。

本章節將分為五個部分進行介紹:

  • 首先,我們將闡述 IteratorIntoIterator 特徵,這是 Rust 迭代器的基礎。
  • 然後,我們將介紹典型的迭代器流程的三個階段:從某種值來源建立迭代器;透過選擇或處理值將一種迭代器轉換為另一種;以及消費迭代器產生的值。
  • 最後,我們將展示如何為自定義型別實作迭代器。

Iterator 和 IntoIterator 特徵

迭代器是任何實作了 std::iter::Iterator 特徵的值:

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ... 其他預設方法
}

Item 是迭代器產生的值的型別。next 方法要麼傳回 Some(v),其中 v 是迭代器的下一個值,要麼傳回 None 以表示序列的結束。

如果某種型別具有自然的遍歷方式,則可以實作 std::iter::IntoIterator 特徵,其 into_iter 方法接受一個值並傳回一個對其進行迭代的迭代器:

trait IntoIterator {
    type Item;
    type IntoIter: Iterator;
    fn into_iter(self) -> Self::IntoIter;
}

任何實作了 IntoIterator 的型別都稱為可迭代型別,因為您可以透過呼叫其方法來對其進行迭代。

Rust 的 for 迴圈將這些部分很好地結合在一起。要遍歷向量的元素,您可以寫:

let v = vec!["antimony", "arsenic", "aluminum", "selenium"];
for element in &v {
    println!("{}", element);
}

在底層,每個 for 迴圈只是對 IntoIteratorIterator 方法呼叫的簡寫:

let mut iterator = (&v).into_iter();
while let Some(element) = iterator.next() {
    println!("{}", element);
}

內容解密:

  • (&v).into_iter():呼叫 into_iter 方法將 &v 轉換為迭代器。
  • iterator.next():呼叫 next 方法取得迭代器的下一個值,如果存在則傳回 Some(element),否則傳回 None

迭代器(Iterators)詳解

迭代器是 Rust 程式語言中一個非常重要的概念,它允許程式設計師以高效且安全的方式遍歷集合中的元素。在本章中,我們將探討迭代器的基本原理、使用方法以及相關的最佳實踐。

迭代器的基本概念

迭代器是一種特殊的 trait,它定義了一種可以產生一系列值的型別。任何實作了 Iterator trait 的型別都可以被視為一個迭代器。迭代器的主要功能是透過 next 方法逐一產生值,直到沒有更多的值可以產生為止。

迭代器的術語

在討論迭代器時,會遇到幾個重要的術語:

  • 迭代器(Iterator):任何實作了 Iterator trait 的型別。
  • 可迭代型別(Iterable):任何實作了 IntoIterator trait 的型別,可以透過呼叫 into_iter 方法獲得一個迭代器。
  • 專案(Items):迭代器產生的值。
  • 消費者(Consumer):接收迭代器產生的專案的程式碼。

建立迭代器

Rust 標準函式庫為多種集合型別提供了迭代器的實作。最常見的兩種方法是 iteriter_mut,它們分別傳回分享參照和可變參照的迭代器。

iteriter_mut 方法

大多數集合型別都提供了 iteriter_mut 方法,用於傳回該型別的自然迭代器。

let v = vec![4, 20, 12, 8, 6];
let mut iterator = v.iter();
assert_eq!(iterator.next(), Some(&4));
assert_eq!(iterator.next(), Some(&20));
assert_eq!(iterator.next(), Some(&12));
assert_eq!(iterator.next(), Some(&8));
assert_eq!(iterator.next(), Some(&6));
assert_eq!(iterator.next(), None);

IntoIterator 實作

當一個型別實作了 IntoIterator 時,你可以呼叫它的 into_iter 方法來獲得一個迭代器。這正是 for 迴圈所做的事情。

use std::collections::BTreeSet;

let mut favorites = BTreeSet::new();
favorites.insert("Lucy in the Sky With Diamonds".to_string());
favorites.insert("Liebesträume No. 3".to_string());

let mut it = favorites.into_iter();
assert_eq!(it.next(), Some("Liebesträume No. 3".to_string()));
assert_eq!(it.next(), Some("Lucy in the Sky With Diamonds".to_string()));
assert_eq!(it.next(), None);

不同型別的 IntoIterator 實作

大多數集合型別為分享參照、可變參照和移動提供了多個 IntoIterator 的實作。

  • 對於集合的分享參照,into_iter 傳回一個產生分享參照的迭代器。
  • 對於集合的可變參照,into_iter 傳回一個產生可變參照的迭代器。
  • 當傳遞集合本身(按值),into_iter 傳回一個取得集合所有權並按值傳回專案的迭代器。

不同 IntoIterator 實作的範例

// 使用分享參照
for element in &collection { ... }

// 使用可變參照
for element in &mut collection { ... }

// 使用集合本身(按值)
for element in collection { ... }

為什麼 Rust 同時提供 iter/iter_mutIntoIterator

Rust 同時提供了 iter/iter_mut 方法和 IntoIterator trait 的實作,是因為這兩者在某些情況下提供了不同的便利性。雖然對於大多數集合型別,呼叫 iteriter_mut 與呼叫 into_iter 具有相同的效果,但後者在泛型程式設計中尤其有用。

Rust 中的迭代器(Iterator)詳解

在 Rust 程式語言中,迭代器(Iterator)是一種用於遍歷集合(如向量、雜湊表等)或其他可迭代資料結構的重要工具。迭代器提供了一種靈活且高效的方式來處理集合中的元素。本篇文章將探討 Rust 中的迭代器,包括其基本概念、建立方法、常用介面卡以及其他相關功能。

IntoIterator 特質與 for 迴圈

IntoIterator 是使得 for 迴圈能夠正常運作的關鍵特質。當你使用 for 迴圈遍歷一個集合時,Rust 會自動呼叫該集合的 into_iter 方法,將其轉換為一個迭代器。因此,實作 IntoIterator 特質是使自定義型別能夠被 for 迴圈遍歷的必要條件。

let favorites = vec![1, 2, 3];
for value in favorites {
    println!("{}", value);
}

在上述範例中,favorites 向量被 for 迴圈遍歷,這得益於 Vec 型別實作了 IntoIterator 特質。

iter 和 iter_mut 方法

除了 into_iter 方法外,許多集合型別還提供了 iteriter_mut 方法,分別用於建立分享參照迭代器和可變參照迭代器。當你不需要取得集合的所有權時,使用 iteriter_mut 方法更為清晰和方便。

let favorites = vec![1, 2, 3];
for value in &favorites {
    println!("{}", value);
}
// 等價於
for value in favorites.iter() {
    println!("{}", value);
}

IntoIterator 的泛型應用

在泛型程式設計中,IntoIterator 特質非常有用。你可以約束型別引數 T 必須實作 IntoIterator,以確保該型別可以被迭代。

use std::fmt::Debug;

fn dump<T, U>(t: T)
where
    T: IntoIterator<Item = U>,
    U: Debug,
{
    for u in t {
        println!("{:?}", u);
    }
}

上述 dump 函式接受任何可迭代的型別 T,並印出其元素。這個函式展示了 IntoIterator 在泛型程式設計中的靈活性。

drain 方法

許多集合型別提供了 drain 方法,該方法接受一個可變參照並傳回一個迭代器,該迭代器會將集合中的元素逐一傳出並最終清空集合。

let mut outer = "Earth".to_string();
let inner = String::from_iter(outer.drain(1..4));
assert_eq!(outer, "Eh");
assert_eq!(inner, "art");

在上述範例中,drain 方法被用來提取字串的一部分,並將剩餘部分留給原始字串。

其他迭代器來源

除了集合型別外,Rust 的標準函式庫中還有許多其他型別支援迭代。以下是一些範例:

範圍(Range)

for i in 1..10 {
    println!("{}", i);
}

Option 和 Result

let opt = Some(10);
for value in opt {
    println!("{}", value);
}

let res = Ok("blah");
for value in res {
    println!("{}", value);
}

字串和切片

let s = "hello";
for c in s.chars() {
    println!("{}", c);
}

let v = vec![1, 2, 3];
for slice in v.windows(2) {
    println!("{:?}", slice);
}

迭代器介面卡(Iterator Adapters)

Rust 提供了多種迭代器介面卡,用於轉換或過濾迭代器中的元素。最常用的介面卡包括 mapfilter

map 介面卡

map 介面卡用於將一個閉包套用到迭代器的每個元素上,並傳回一個新的迭代器。

let text = " ponies \n giraffes\niguanas \nsquid".to_string();
let v: Vec<&str> = text.lines()
                        .map(str::trim)
                        .collect();
assert_eq!(v, ["ponies", "giraffes", "iguanas", "squid"]);

filter 介面卡

filter 介面卡用於根據一個閉包的結果過濾迭代器中的元素。

let text = " ponies \n giraffes\niguanas \nsquid".to_string();
let v: Vec<&str> = text.lines()
                        .map(str::trim)
                        .filter(|s| *s != "iguanas")
                        .collect();
assert_eq!(v, ["ponies", "giraffes", "squid"]);