來用 Rust 建立一個伺服器吧!Part II
昨天我們成功讀取了 request,今天就繼續接著做,首先來處理發送 response 的部分。
發送 response我們可以利用 TcpStream 的 write 方法來發送 response:
1234567891011fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 512]; let c = stream.read(&mut buffer).unwrap(); println!("請求內容: {}", String::from_utf8_lossy(&buffer[..c])); let response = "HTTP/1.1 200 OK\r\n\r\n"; stream.write(response.as_bytes()).unwrap(); stream.flush().unwrap();}
這裡我們先宣告了一個 response 變數,裡面放的是一個 ...
來用 Rust 建立一個伺服器吧!Part I
從今天開始,我們要開始實作一個簡單的 HTTP 伺服器,並且複習一下並且運用我們之前學到 Rust 的知識,然後視情況加入一些需要了解的部分,到時候再加入解說,那麼就開始吧!
建立一個新專案首先,我們要建立一個新的專案,這邊我們使用 cargo 來建立一個新的專案,使用 cargo new 指令,並且指定專案名稱為 server:
1$ cargo new server
監聽 TCP 連線接著,我們要開始實作一個 TCP 伺服器,並且監聽 TCP 連線。Rust 的標準函式庫中有一個 std::net 模組,裡面有一個 TcpListener 型態,可以用來監聽 TCP 連線,所以我們可以先在 src/main.rs 中 使用 TcpListener:
12345678910111213use std::net::TcpListener; const PORT: i32 = 3000; fn main() { let listener = TcpListener::bind(format!("127.0.0.1:{}", POR ...
Rust 的模組
因為我們明天將會開始試著建立一個專案來玩玩看,但在此之前我們要先了解一下模組的概念。
什麼是模組(module)?在使用 Cargo 建立的專案中,會像下方這樣,所有程式都需要經由 main 來做處理。但是我們當然不可能把所有邏輯都寫在同一個檔案中,這樣子會非常難維護。
1234project|__Cargo.toml|__src |__main.rs
所以我們可以透過模組化的方式來管理專案,最後再經由 main 來處理。我們來做一個示範,首先在 /src 路徑下新增一個 lib.rs,裡面我們建立一個函式 say_hi, 裡面我們寫了一段程式來印出 “Hi!”。
123fn say_hi() { println!("Hi!");}
接著在 main 裡面引入剛剛建立的 say_hi。寫法是先寫這個專案的名字(不是這個專案資料夾名字,而是指 Cargo.toml 裡面的 package name),這個專案的名稱是 hello_world,接著加上一個標識符 ::,最後再加上要引入的函式名稱即可,就像下方範例:
123fn main() ...
Rust 的生命週期
今天我們要來介紹其他程式語言中比較少見的機制,但是在 Rust 中是屬於和參考(reference)有關的機制,那就是生命週期(lifetime)。
一、生命週期的概念生命週期是 Rust 中的一個概念,它是一個變數的有效範圍,也就是它可以被使用的範圍。生命週期的概念是為了解決 Rust 中的參考的問題,因為參考是 Rust 中的一個重要機制,它可以讓開發者在不需要複製資料的情況下,就可以使用資料。但是參考也有一個問題,就是它的生命週期,也就是它的有效範圍,如果參考的資料已經被釋放了,那麼參考就會變成一個無效的參考,這樣就會造成程式的錯誤。
這裡有一個範例:
12345678910{ let r; { let x = 5; r = &x; } println!("r: {}", r);}
先說結論,這個範例是沒辦法通過編譯的,也就是會報錯。這是因為我們先宣告了一個變數 r,然後我們在一個新的區塊中宣告了一個變數 x,並且將 x 的參考賦值給 r。 ...
Rust 的閉包
什麼是閉包(closure)?Rust 的閉包是一種匿名函式,可以從其函式的作用域中捕獲數值,而且閉包是為了快速執行而設計的,也因為如此它們的性能比一般函式還要好。
閉包有以下幾個特性:
是一種匿名函式
可以保存為變數,或是作為參數傳遞給其他函式
可以在某處建立閉包,然後在不同的地方執行它
可以從定義的作用域中捕獲變數
如何建立閉包?閉包的建立方式:
使用 | 來分隔參數,| 後面是函式的主體
使用 {} 來包裹函式的主體
例如:
12let plus_one = |x: i32| -> i32 { x + 1 };plus_one(1); // 2
建立閉包的時候,有些特殊的地方可以注意:
可以不用參數,直接使用 || 來建立一個空閉包
可以不用 return 來回傳值,直接使用 -> 來指定回傳值的型別
可以不用指定型別,Rust 會自動推導型別
如何使用閉包?上面的範例中,我們建立了一個閉包,並且將它存到變數 plus_one 中,然後呼叫它。
閉包可以像函式一樣使用,例如:
12345fn main() ...
Rust 的枚舉
枚舉(Enums)枚舉是一種定義一組可能值的方法,這些值被稱為成員(variants)。枚舉的每個成員都是一個獨立的類型,並且可以是不同類型的值,包括整數、浮點數、字符串、元組、數組、結構體等。
123456enum PhoneSystem { Android, Ios, Windows, Linux,}
像上面的例子,首先使用 enum 關鍵字定義了一個名為 PhoneSystem 的枚舉,命名規則是大寫開頭的駝峰式命名法。
前面提到枚舉的每一個成員都可以是不同類型的值,這讓枚舉在使用上非常靈活,不過還是有一些限制,比如說,枚舉的成員不能重複,也不能是空的。
1234567enum PhoneSystem { Android, Ios, Windows, Linux, Android, // error: duplicate variant `Android`}
123enum PhoneSystem { // error: empty enum is not allow ...
Rust 的集合
Rust 的標準函式庫有一些非常實用的資料結構,稱之為集合(collections)。這些集合包含了一些常見的資料結構,例如 vector、hash map、linked list 等等。這些集合都是泛型的,所以可以用來存放任何型別的資料。
向量(Vector)vector 是一個可以動態增長的陣列。它的實作是用一個 buffer 來存放資料,當 buffer 不夠用時,會自動增長。vector 的實作是用 unsafe Rust 來實現的,所以它的效能比較好。
建立一個 vector建立一個空的 vector 可以這樣做:
1let mut v: Vec<i32> = Vec::new();
或者這樣:
1let mut v = vec![];
增加元素可以用 push 方法來增加元素:
123v.push(1);v.push(2);v.push(3);
讀取元素可以用索引的方法來讀取元素:
123let third: &i32 = &v[2];println!("The third element is {}" ...
特徵
特徵(Trait)是 Rust 的一個重要的特性,它可以讓我們在不同的型別上定義共用的行為,並且可以在不同的型別上使用相同的函式。特徵有點像是其他語言的介面(interface),目前我們可以先把它想成是一種定義共享行為的方式。
由於我在學特徵這部分的時候,覺得這個概念滿抽象的,所以我會盡可能的以不同的例子來展示,希望能讓大家更容易理解。(標題越來越懶的想XD)
定義特徵要怎麼定義一個特徵呢?
其實很簡單,只要在 trait 這個關鍵字後面加上特徵的名稱,然後在大括號裡面定義特徵的行為就可以了。
123trait Summary { fn summarize(&self) -> String;}
這是一個基本的特徵,在上面的程式碼中,我們定義了一個名為 Summary 的特徵,裡面有一個名為 summarize 的函式,它會回傳一個 String。
再講的仔細一點,定義一個特徵就是:
把方法的型態簽章放在一起,來定義實現某種目的所必須的一組行為。
分別有以下幾個重點:
關鍵字:trait
只有方法的型態簽章,沒有具體實現
方法的型態簽章:就是 ...
泛型
什麼是泛型?在寫程式中,可能會遇到不同需求,例如想要得到多種類型的結果,像是結果型別為 i32,又或是浮點數 f64 等等…。這對工程師來說根本是種折磨,因為工程師就是懶,要寫相同的過程,但結果只是不同型別的話,就像下面這樣:
123456789fn result_i8(x: i8, y: i8) -> i8 { x + y}fn result_i32(x: i32, y: i32) -> i32 { x + y}fn result_f32(x: f64, y: f64) -> f64 { x + y}
還好 Rust 提供了泛型(generics)。
泛型可以提高程式碼的重複使用,也就是如果程式碼中有很多重複的部分的話,泛型就可以處理重複程式碼的問題。
泛型函式定義在使用泛型定義函式時,只需要在函式名稱的後面加上 <T>,T 代表泛型參數,雖然也可以把 T 換成任一字母,但慣例會用 T 來表示 Type,就像這樣:
123fn result<T: std::ops::A ...
結構
結構(struct)是 Rust 提供的一種可以讓開發者建立資料型別相對複雜的一種方式,類似 JavaScript 的物件和 Python 的類別。
結構和元組類似,都是可以由不同不同型態組成,不過與元組不同地方在於必須要為每個組件命名,所以可以不必按照順序,就可以讀取或修改結構的個別資料。
Rust 提供了三種結構,分別是:
具名欄位(named-field):最常用的用法,每個組件都有一個名稱。
類元組(tuple-like):因為類似元組,所以也必須要按照順序使用。
類單元(unit-like):比較不常見,其結構完全沒有組件。
具名欄位直接看範例如何建立一個具名欄位的結構:
123456struct Hunter { name: String, nen_type: String, age: u8, hunter_license: bool,}
首先起手式先用關鍵字 struct 為整個結構命名,命名的方式為駝峰命名(Camel Case),然後在大括號中加入欄位,欄位的名稱則是使用蛇式命名(snake_case),接著就幫每個欄位 ...