Rust 語言的設計目標是在不犧牲效能的前提下,提供更安全的系統程式設計體驗。C/C++ 等傳統系統程式語言雖然效能強大,但容易出現未定義行為和資料競爭等問題,Rust 則透過型別安全和所有權機制有效解決了這些痛點。Rust 的所有權系統在編譯時期就能有效預防資料競爭,讓開發者更輕鬆地撰寫安全的平行程式碼。此外,Rust 還提供 Cargo 套件管理器,簡化了專案建置和依賴管理流程,提升開發效率。本文將以 GCD 演算法和網頁伺服器例項,展示 Rust 語言在實務應用中的優勢和便捷性,並探討如何結合第三方函式庫,快速構建高效能應用。
為什麼是Rust?
C和C++這類別系統程式語言在效能和控制資源方面提供了無與倫比的靈活性,但它們的設計哲學卻使得安全性成為一個巨大的挑戰。這些語言並未內建防止未定義行為(undefined behavior)的機制,而是將責任推給程式設計師,要求他們確保程式在任何情況下都不會出現未定義行為。實際上,這樣的要求對大多數程式設計師來說是難以達到的。
研究人員Peng Li曾經修改了C和C++編譯器,使其在執行某些形式的未定義行為時進行報告。結果顯示,幾乎所有程式都存在未定義行為,包括那些由聲譽良好的專案所開發的程式。更糟糕的是,未定義行為往往會導致可被利用的安全漏洞。
型別安全的重要性
如果一個程式在任何可能的執行情況下都不會出現未定義行為,我們稱之為良定義(well-defined)。如果一門語言的安全檢查機制能夠確保每一個程式都是良定義的,那麼我們稱這門語言是型別安全(type safe)的。C和C++並不是型別安全的語言,因為儘管一個精心撰寫的C或C++程式可能是良定義的,但這些語言本身並不能保證所有程式都是良定義的。
相對地,Python是一種型別安全的語言。當嘗試存取超出範圍的陣列索引時,Python會丟擲一個異常,而不是出現未定義行為。這種行為是Python語言規範所明確定義的。
Rust的解決方案
Rust旨在解決C和C++所面臨的挑戰:它既是型別安全的,又是一門系統程式語言。Rust被設計用於實作需要效能和對資源進行精細控制的系統底層,同時保證型別安全所提供的基本可預測性。
Rust的型別安全特性對於多執行緒程式設計具有令人驚訝的影響。在C和C++中,平行程式設計是非常困難的,因為這些語言無法保證平行程式碼的安全性。Rust則保證了平行程式碼是沒有資料競爭(data races)的,任何對mutex或其他同步原語的誤用都會在編譯時被捕捉。
Rust中的不安全程式碼
Rust提供了一種逃脫安全規則的機制,稱為不安全程式碼(unsafe code)。當絕對需要使用原始指標(raw pointer)時,可以使用不安全程式碼。大多數Rust程式不需要使用不安全程式碼,但我們將在後面的章節中介紹如何使用它以及它如何融入Rust的整體安全機制中。
Rust之旅
在本章中,我們將透過幾個簡短的程式來瞭解Rust的語法、型別和語義是如何結合起來支援安全、平行和高效的程式碼。我們將介紹下載和安裝Rust的過程,展示一些簡單的數學運算程式碼,嘗試根據第三方函式庫的網頁伺服器,並使用多個執行緒來加速繪製Mandelbrot集合的過程。
下載和安裝Rust
安裝Rust的最佳方式是使用rustup,這是Rust的安裝工具。可以透過存取https://rustup.rs並按照指示進行安裝。
安裝完成後,您應該可以在命令列中使用以下三個新的命令:
$ cargo --version
cargo 0.18.0 (fe7b0cdcf 2017-04-24)
$ rustc --version
rustc 1.17.0 (56124baa9 2017-04-24)
$ rustdoc --version
版本檢查說明:
cargo是用於管理Rust專案的工具,其版本為0.18.0。rustc是Rust編譯器,其版本為1.17.0。rustdoc是用於產生檔案化的工具。
詳細解說:
cargo --version:顯示Cargo工具的版本,用於管理Rust專案,包括依賴管理和建置。rustc --version:顯示Rust編譯器的版本,用於將Rust原始碼編譯成可執行檔。rustdoc --version:顯示Rust檔案產生工具的版本,用於根據原始碼註解產生檔案。
Rust 程式語言快速入門
Rust 是一種系統程式語言,注重安全、速度和平行性。本將介紹 Rust 的基本工具、語法和功能。
安裝 Rust 工具
Rust 提供了一套完整的工具鏈,包括 Cargo、rustc 和 rustdoc。這些工具可以用來建立、編譯和記錄 Rust 專案。
- Cargo:Rust 的套件管理器和建置工具。可以用來建立新專案、建置和執行程式,以及管理依賴項。
- rustc:Rust 編譯器。通常由 Cargo 呼叫,但也可以直接執行。
- rustdoc:Rust 檔案工具。可以根據原始碼中的註解產生 HTML 檔案。
要驗證安裝是否成功,可以執行以下命令:
$ cargo --version
$ rustc --version
$ rustdoc --version
建立新 Rust 專案
Cargo 可以用來建立新的 Rust 專案。執行以下命令:
$ cargo new --bin hello
這將建立一個名為 hello 的新專案,並準備好作為可執行檔。
專案結構
Cargo 建立的專案結構如下:
hello/
Cargo.toml
src/
main.rs
.git/
.gitignore
- Cargo.toml:包含專案的元資料,如名稱、版本和作者。
- src/main.rs:包含專案的原始碼。
編譯和執行 Rust 程式
可以使用 Cargo 編譯和執行 Rust 程式。執行以下命令:
$ cargo run
這將編譯 hello 專案並執行產生的可執行檔。
程式輸出
Compiling hello v0.1.0 (file:///home/jimb/rust/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Running `/home/jimb/rust/hello/target/debug/hello`
Hello, world!
簡單函式範例
以下是一個計算兩個整數的最大公約數(GCD)的函式範例,使用歐幾裡得演算法:
fn gcd(mut n: u64, mut m: u64) -> u64 {
assert!(n != 0 && m != 0);
while m != 0 {
if m < n {
let t = m;
m = n;
n = t;
}
m = m % n;
}
n
}
內容解密:
fn gcd(mut n: u64, mut m: u64) -> u64:定義一個名為gcd的函式,接受兩個u64型別的引數n和m,並傳回一個u64型別的值。mut關鍵字表示n和m是可變的。assert!(n != 0 && m != 0):使用assert!巨集檢查n和m是否都不為零。如果條件不成立,程式將終止。while m != 0:進入迴圈,直到m為零。if m < n { ... }:如果m小於n,則交換m和n的值。m = m % n:計算m除以n的餘數,並將結果指定給m。n:傳回最終的n值,即最大公約數。
Rust 程式語言基礎與 GCD 演算法實作
Rust 語言是一種強調安全、效能與平行性的系統程式語言,其設計理念在於提供開發者更安全、更有效率的程式開發體驗。本文將探討 Rust 語言的基本語法、單元測試的撰寫,以及如何處理命令列引數,並實作最大公約數(GCD)演算法。
GCD 演算法實作與解析
以下是一個使用 Rust 實作 GCD 演算法的範例:
fn gcd(mut n: u64, mut m: u64) -> u64 {
assert!(n != 0 && m != 0);
while m != 0 {
if m < n {
let t = m;
m = n;
n = t;
}
m = m % n;
}
n
}
內容解密:
gcd函式接收兩個u64型別的引數n和m,並傳回它們的最大公約數。assert!巨集用於檢查n和m是否均不為零,若檢查失敗,程式將終止並顯示錯誤訊息。while迴圈持續執行直到m為零,在每次迭代中,若m小於n,則交換m和n的值,以確保m總是大於或等於n。- 使用歐幾裡得演算法計算 GCD,透過
m = m % n更新m的值。 - 當
m為零時,n即為 GCD,並作為函式的傳回值。
單元測試的撰寫與執行
Rust 語言內建對測試的支援,以下是針對 gcd 函式撰寫的單元測試:
#[test]
fn test_gcd() {
assert_eq!(gcd(14, 15), 1);
assert_eq!(gcd(2 * 3 * 5 * 11 * 17, 3 * 7 * 11 * 13 * 19), 3 * 11);
}
內容解密:
#[test]屬性標記test_gcd函式為測試函式,該函式在正常編譯時會被忽略,但在執行cargo test命令時會被自動呼叫。- 使用
assert_eq!巨集檢查gcd函式的傳回值是否符合預期,若不符合,測試將失敗。
處理命令列引數
以下範例展示如何處理命令列引數並計算輸入數字的 GCD:
use std::io::Write;
use std::str::FromStr;
fn main() {
let mut numbers = Vec::new();
for arg in std::env::args().skip(1) {
numbers.push(u64::from_str(&arg).expect("error parsing argument"));
}
if numbers.len() == 0 {
writeln!(std::io::stderr(), "Usage: gcd NUMBER ...").unwrap();
std::process::exit(1);
}
let mut d = numbers[0];
for m in &numbers[1..] {
d = gcd(d, *m);
}
println!("The greatest common divisor of {:?} is {}", numbers, d);
}
內容解密:
- 使用
std::env::args()取得命令列引數,並透過skip(1)跳過程式名稱。 - 將每個引數解析為
u64型別,並存入numbers向量中。 - 若無輸入數字,則輸出用法提示並離開程式。
- 使用
gcd函式計算輸入數字的 GCD,並輸出結果。
處理命令列引數
在 Rust 程式中,處理命令列引數是一項基本功能。以下程式碼展示瞭如何使用 Rust 來處理命令列引數,並計算多個數字的最大公約數(GCD)。
程式碼範例
use std::env;
fn main() {
let mut numbers = Vec::new();
for arg in env::args().skip(1) {
numbers.push(u64::from_str(&arg).expect("解析引數錯誤"));
}
if numbers.len() == 0 {
writeln!(std::io::stderr(), "用法:gcd 數字 ...").unwrap();
std::process::exit(1);
}
let mut d = numbers[0];
for m in &numbers[1..] {
d = gcd(d, *m);
}
println!("最大公約數:{}", d);
}
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
內容解密:
let mut numbers = Vec::new();:建立一個空的可變向量numbers,用於儲存從命令列引數解析出的數字。for arg in env::args().skip(1):遍歷命令列引數,跳過第一個引數(程式名稱)。env::args()傳回一個迭代器,產生每個命令列引數。numbers.push(u64::from_str(&arg).expect("解析引數錯誤"));:嘗試將每個引數解析為u64數字。如果解析失敗,則輸出錯誤訊息並離開程式。if numbers.len() == 0:檢查是否提供了至少一個數字。如果沒有,則輸出用法訊息並離開程式。let mut d = numbers[0];:初始化d為向量中的第一個數字。for m in &numbers[1..]:遍歷向量中剩餘的數字,計算它們與d的最大公約數,並更新d。d = gcd(d, *m);:呼叫gcd函式計算d和當前數字m的最大公約數,並將結果指定給d。*m用於解參照m,取得其指向的u64值。
圖表說明
此圖示展示了命令列引數處理流程:
@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
此圖示清晰地展示了程式的流程,從遍歷命令列引數到計算最大公約數的整個過程。
重點分析
- 命令列引數處理:Rust 使用
std::env::args()函式來取得命令列引數。該函式傳回一個迭代器,可以用於遍歷每個引數。 - 錯誤處理:使用
Result和expect方法來處理可能的錯誤,例如解析失敗。 - 所有權和借用:Rust 的所有權系統確保了記憶體的安全管理。在遍歷向量時,使用
&numbers[1..]來借用向量的元素,避免了所有權的轉移。
Rust 程式語言簡介與 GCD 計算器實作
Rust 語言以其記憶體安全性和高效能著稱,本章節將介紹 Rust 的基本語法和功能,並實作一個簡單的 GCD(最大公約數)計算器和一個基礎的網頁伺服器。
GCD 計算器實作
首先,我們使用 Cargo 建立一個新的 Rust 專案:
$ cargo new gcd
$ cd gcd
接著,在 src/main.rs 中撰寫 GCD 計算器的程式碼:
use std::io;
fn main() {
println!("請輸入兩個數字,以空白分隔:");
let mut input = String::new();
io::stdin().read_line(&mut input)
.expect("無法讀取輸入");
let numbers: Vec<i32> = input.trim().split_whitespace()
.map(|x| x.parse().expect("請輸入有效的數字"))
.collect();
let gcd = gcd(&numbers);
println!("GCD 為:{}", gcd);
}
fn gcd(numbers: &[i32]) -> i32 {
if numbers.len() == 1 {
numbers[0].abs()
} else {
numbers.iter().fold(numbers[0], |a, b| {
let mut x = a.abs();
let mut y = b.abs();
while y != 0 {
let temp = y;
y = x % y;
x = temp;
}
x
})
}
}
程式碼解析:
- 我們使用
std::io模組來讀取使用者的輸入。 main函式中,我們讀取使用者輸入的數字,並將其轉換為i32型別的向量。gcd函式計算輸入數字的最大公約數,使用了 Euclidean 演算法。- 最後,我們輸出計算出的 GCD。
簡易網頁伺服器實作
接下來,我們建立一個簡易的網頁伺服器,使用 Iron 框架來處理 HTTP 請求。
首先,建立一個新的 Cargo 專案:
$ cargo new --bin iron-gcd
$ cd iron-gcd
然後,在 Cargo.toml 中加入必要的相依套件:
[package]
name = "iron-gcd"
version = "0.1.0"
authors = ["You <[email protected]>"]
[dependencies]
iron = "0.5.1"
mime = "0.2.3"
router = "0.5.1"
urlencoded = "0.5.0"
在 src/main.rs 中撰寫網頁伺服器的程式碼:
extern crate iron;
#[macro_use] extern crate mime;
use iron::prelude::*;
use iron::status;
fn main() {
println!("Serving on http://localhost:3000...");
Iron::new(get_form).http("localhost:3000").unwrap();
}
fn get_form(_request: &mut Request) -> IronResult<Response> {
let mut response = Response::new();
response.set_mut(status::Ok);
response.set_mut(mime!(Text/Html; Charset=Utf8));
response.set_mut(r#"
<title>GCD Calculator</title>
<form action="/gcd" method="post">
<input type="text" name="n"/>
<input type="text" name="n"/>
<button type="submit">Compute GCD</button>
</form>
"#);
Ok(response)
}
程式碼解析:
- 我們使用 Iron 框架建立了一個 HTTP 伺服器,監聽在
localhost:3000。 get_form函式處理對根 URL 的 GET 請求,傳回一個包含表單的 HTML 頁面。- 使用者可以在表單中輸入兩個數字,並提交給伺服器計算 GCD。