Rust 學習教材(由淺入深)
學習路徑總覽
第1-2週:基礎語法 → 第3-4週:所有權與生命週期 → 第5-6週:結構與模式
→ 第7-8週:錯誤處理與泛型 → 第9-10週:並發與非同步 → 第11-12週:實戰專案
第一週:環境設定與基礎
Day 1-2:認識 Rust
Rust 是什麼: - Mozilla 開發的系統程式語言 - 注重安全性、效能、並發 - 沒有垃圾回收(GC) - 適合系統程式、WebAssembly、CLI 工具
為什麼學 Rust:
| 優勢 | 說明 |
|---|---|
| 記憶體安全 | 編譯時保證無悬垂指標 |
| 零成本抽象 | 高階語法,底層效能 |
| 並發安全 | 無資料競爭 |
| 豐富生態 | Cargo 套件管理 |
| 跨平台 | Linux、macOS、Windows、WebAssembly |
Rust vs 其他語言:
| 語言 | 記憶體管理 | 並發模型 | 適用場景 |
|---|---|---|---|
| Rust | 所有權系統 | 型別安全並發 | 系統程式、效能關鍵 |
| C++ | 手動管理 | 執行緒 | 系統程式、遊戲引擎 |
| Go | 垃圾回收 | Goroutine | 伺服器、微服務 |
| Python | 垃圾回收 | GIL | 腳本、數據科學 |
Day 3-4:環境安裝
# 安裝 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 驗證安裝
rustc --version
cargo --version
# 建立第一個專案
cargo new hello-rust
cd hello-rust
cargo run
Cargo 常用指令:
| 指令 | 功能 |
|---|---|
cargo new |
建立新專案 |
cargo build |
建構專案 |
cargo run |
執行專案 |
cargo test |
執行測試 |
cargo check |
檢查語法 |
cargo clippy |
Lint 檢查 |
cargo fmt |
格式化程式碼 |
Day 5-7:專案結構
hello-rust/
├── Cargo.toml # 專案設定
├── Cargo.lock # 依賴鎖定
├── src/
│ ├── main.rs # 主程式進入點
│ └── lib.rs # 程式庫進入點
├── tests/ # 整合測試
├── benches/ # 基準測試
└── examples/ # 範例程式
Cargo.toml:
[package]
name = "hello-rust"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
[dev-dependencies]
assert_cmd = "2.0"
第二週:基本語法
Day 1-2:變數與型別
// 變數(預設不可變)
let x = 5;
// x = 6; // 錯誤!
// 可變變數
let mut y = 5;
y = 6; // 正確
// 型別推斷
let name = "John"; // &str
let age: u32 = 30; // 明確指定型別
// 常數
const MAX_POINTS: u32 = 100_000;
// 基本型別
let integer: i32 = 42;
let float: f64 = 3.14;
let boolean: bool = true;
let character: char = 'A';
let string: String = String::from("Hello");
let slice: &[i32] = &[1, 2, 3];
Day 3-4:函式
// 基本函式
fn add(a: i32, b: i32) -> i32 {
a + b // 最後一行不加分號 = 回傳值
}
// 多回傳值
fn divide(a: f64, b: f64) -> (f64, bool) {
if b == 0.0 {
(0.0, false)
} else {
(a / b, true)
}
}
// 提前回傳
fn find_even(numbers: &[i32]) -> Option<i32> {
for &num in numbers {
if num % 2 == 0 {
return Some(num);
}
}
None
}
fn main() {
let result = add(5, 3);
println!("5 + 3 = {}", result);
let (quotient, success) = divide(10.0, 3.0);
println!("10 / 3 = {} (success: {})", quotient, success);
let numbers = vec![1, 3, 5, 8, 9];
match find_even(&numbers) {
Some(even) => println!("Found even: {}", even),
None => println!("No even number found"),
}
}
Day 5-7:控制流程
// if 表達式
let number = 7;
let description = if number > 0 {
"positive"
} else if number < 0 {
"negative"
} else {
"zero"
};
println!("{} is {}", number, description);
// loop(無限迴圈)
let mut count = 0;
let result = loop {
count += 1;
if count == 10 {
break count * 2; // 回傳值
}
};
println!("Result: {}", result);
// while 迴圈
let mut n = 1;
while n < 1000 {
n *= 2;
}
println!("n = {}", n);
// for 迴圈
let numbers = vec![1, 2, 3, 4, 5];
for number in &numbers {
println!("Number: {}", number);
}
// 範圍
for i in 0..5 {
println!("i = {}", i);
}
// match 表達式
let grade = 'B';
let description = match grade {
'A' => "Excellent",
'B' => "Good",
'C' => "Average",
'D' => "Below Average",
'F' => "Failing",
_ => "Invalid grade", // 預設情況
};
println!("Grade {}: {}", grade, description);
第三週:所有權系統
Day 1-2:所有權規則
三大規則: 1. 每個值都有一個變數,稱為「擁有者」 2. 同一時間只能有一個擁有者 3. 當擁有者離開範圍,值會被丟棄
fn main() {
// 所有權轉移
let s1 = String::from("hello");
let s2 = s1; // s1 的所有權轉移到 s2
// println!("{}", s1); // 錯誤!s1 已無效
// 克隆(深拷貝)
let s3 = String::from("hello");
let s4 = s3.clone();
println!("s3 = {}, s4 = {}", s3, s4); // 正確
// 函式中的所有權
let s = String::from("hello");
print_string(s); // s 的所有權轉移到函式
// println!("{}", s); // 錯誤!s 已無效
}
fn print_string(s: String) {
println!("{}", s);
} // s 離開範圍,記憶體被釋放
Day 3-4:引用與借用
fn main() {
// 不可變引用
let s1 = String::from("hello");
let len = calculate_length(&s1); // 傳遞引用,不轉移所有權
println!("Length of '{}' is {}", s1, len); // s1 仍然有效
// 可變引用
let mut s = String::from("hello");
change(&mut s);
println!("s = {}", s); // "hello, world"
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s 離開範圍,但因為沒有所有權,什麼都不會發生
fn change(s: &mut String) {
s.push_str(", world");
}
// 引用規則
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 沒問題
let r2 = &s; // 沒問題
println!("{} and {}", r1, r2);
// r1 和 r2 在這之後不再使用
let r3 = &mut s; // 沒問題
println!("{}", r3);
}
Day 5-7:切片(Slices)
fn main() {
// 字串切片
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}", hello, world); // hello world
// 從頭開始
let slice = &s[..5]; // "hello"
// 到結尾
let slice = &s[6..]; // "world"
// 整個字串
let slice = &s[..]; // "hello world"
// 陣列切片
let numbers = vec![1, 2, 3, 4, 5];
let slice = &numbers[1..3]; // [2, 3]
println!("Slice: {:?}", slice);
// 使用切片的函式
let word = first_word(&s);
println!("First word: {}", word);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..]
}
第四週:結構與列舉
Day 1-2:結構體
// 基本結構
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 方法
impl User {
// 建構函式
fn new(username: String, email: String) -> User {
User {
username,
email,
sign_in_count: 1,
active: true,
}
}
// 方法
fn describe(&self) -> String {
format!("User: {} ({})", self.username, self.email)
}
// 可變方法
fn deactivate(&mut self) {
self.active = false;
}
}
// 元組結構
struct Color(u8, u8, u8);
// 單位結構
struct AlwaysEqual;
fn main() {
let mut user = User::new(
String::from("john"),
String::from("john@example.com")
);
println!("{}", user.describe());
user.deactivate();
println!("Active: {}", user.active);
let black = Color(0, 0, 0);
println!("Black: ({}, {}, {})", black.0, black.1, black.2);
}
Day 3-4:列舉與模式匹配
// 列舉
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
// 帶有方法的列舉
impl IpAddr {
fn display(&self) {
match self {
IpAddr::V4(a, b, c, d) => {
println!("{}.{}.{}.{}", a, b, c, d);
}
IpAddr::V6(addr) => {
println!("{}", addr);
}
}
}
}
// Message 列舉
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Change color to ({}, {}, {})", r, g, b);
}
}
}
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
home.display();
loopback.display();
let msg = Message::Write(String::from("hello"));
msg.call();
}
Day 5-7:Option 與錯誤處理
// Option<T> - 處理可能為空的值
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
// 使用 Option
fn main() {
let result = divide(10.0, 3.0);
// match
match result {
Some(value) => println!("Result: {}", value),
None => println!("Cannot divide by zero"),
}
// unwrap_or
let value = divide(10.0, 0.0).unwrap_or(0.0);
println!("Value: {}", value);
// map
let doubled = divide(10.0, 2.0).map(|x| x * 2.0);
println!("Doubled: {:?}", doubled);
// 鏈式操作
let result = divide(100.0, 4.0)
.map(|x| x * 2.0)
.filter(|&x| x > 10.0)
.unwrap_or(0.0);
println!("Final result: {}", result);
}
// 自訂錯誤
#[derive(Debug)]
enum AppError {
NotFound(String),
PermissionDenied,
InvalidInput(String),
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
AppError::PermissionDenied => write!(f, "Permission denied"),
AppError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
}
}
}
fn find_user(id: u32) -> Result<String, AppError> {
if id == 1 {
Ok(String::from("John"))
} else {
Err(AppError::NotFound(format!("User {} not found", id)))
}
}
第五週:泛型與特徵
Day 1-2:泛型
// 泛型函式
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest {
largest = item;
}
}
largest
}
// 泛型結構
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
}
// 只為特定型別實作方法
impl Point<f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("Largest number: {}", largest(&numbers));
let characters = vec!['y', 'm', 'a', 'q'];
println!("Largest character: {}", largest(&characters));
let integer_point = Point::new(5, 10);
let float_point = Point::new(1.0, 4.0);
println!("Distance: {}", float_point.distance_from_origin());
}
Day 3-4:特徵(Traits)
// 定義特徵
trait Summary {
fn summarize(&self) -> String;
// 預設實作
fn preview(&self) -> String {
format!("{}...", &self.summarize()[..50])
}
}
// 結構體
struct NewsArticle {
headline: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {}", self.headline, self.author)
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
// 使用特徵作為參數
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// 特徵約束
fn notify_generic<T: Summary + std::fmt::Display>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course as you probably already know"),
};
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup!"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team."),
};
notify(&tweet);
notify(&article);
}
Day 5-7:生命週期
// 生命週期標註
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 結構體中的生命週期
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("Longest: {}", result);
}
}
第六週:集合與迭代器
Day 1-2:向量與雜湊映射
use std::collections::HashMap;
fn main() {
// 向量
let mut v: Vec<i32> = Vec::new();
v.push(5);
v.push(6);
v.push(7);
// 使用巨集
let v2 = vec![1, 2, 3];
// 存取元素
let third: &i32 = &v[2];
println!("Third element: {}", third);
match v.get(2) {
Some(third) => println!("Third element: {}", third),
None => println!("No third element"),
}
// 迭代
for i in &v {
println!("{}", i);
}
// 雜湊映射
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// 取得值
let team_name = String::from("Blue");
let score = scores.get(&team_name);
println!("Blue's score: {:?}", score);
// 更新
scores.entry(String::from("Yellow")).or_insert(50);
let count = scores.entry(String::from("Blue")).or_insert(0);
*count += 1;
// 依賴前一個值
let text = "hello world wonderful world";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("Word count: {:?}", word_count);
}
Day 3-4:迭代器
fn main() {
let v = vec![1, 2, 3];
// 建立迭代器
let iter = v.iter();
// 使用迭代器
for val in iter {
println!("Got: {}", val);
}
// 迭代器適配器
let v = vec![1, 2, 3];
let v2: Vec<_> = v.iter()
.map(|x| x + 1)
.collect();
println!("v2: {:?}", v2);
// 過濾
let v = vec![1, 2, 3, 4, 5];
let v2: Vec<_> = v.iter()
.filter(|&&x| x % 2 == 0)
.collect();
println!("Even numbers: {:?}", v2);
// fold(reduce)
let v = vec![1, 2, 3, 4, 5];
let sum = v.iter()
.fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum);
// 自訂迭代器
struct Counter {
count: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Counter {
Counter { count: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
let counter = Counter::new(5);
let v: Vec<_> = counter.collect();
println!("Counter: {:?}", v);
}
Day 5-7:字串處理
fn main() {
// 字串
let mut s = String::from("Hello");
s.push_str(", world!");
s.push('!');
println!("{}", s);
// 字串切片
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}", hello, world);
// 迭代字元
for c in "नमस्ते".chars() {
println!("{}", c);
}
// 迭代位元組
for b in "hello".bytes() {
println!("{}", b);
}
// 分割字串
let csv = "apple,banana,cherry";
let fruits: Vec<&str> = csv.split(',').collect();
println!("Fruits: {:?}", fruits);
// 替換
let s = "Hello, World!";
let new_s = s.replace("World", "Rust");
println!("{}", new_s);
// 格式化
let name = "Alice";
let age = 30;
let greeting = format!("Hello, {}! You are {} years old.", name, age);
println!("{}", greeting);
}
第七週:錯誤處理
Day 1-2:Result 與錯誤傳播
use std::fs::File;
use std::io::{self, Read};
// 基本錯誤處理
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
// 使用 ? 運算子
fn read_file_short(path: &str) -> Result<String, io::Error> {
let mut contents = String::new();
File::open(path)?.read_to_string(&mut contents)?;
Ok(contents)
}
// 自訂錯誤類型
#[derive(Debug)]
enum AppError {
IoError(io::Error),
ParseError(String),
NotFound(String),
}
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::IoError(error)
}
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
AppError::IoError(e) => write!(f, "IO error: {}", e),
AppError::ParseError(msg) => write!(f, "Parse error: {}", msg),
AppError::NotFound(msg) => write!(f, "Not found: {}", msg),
}
}
}
fn parse_config(path: &str) -> Result<String, AppError> {
let contents = read_file(path)?;
if contents.is_empty() {
return Err(AppError::ParseError("Config is empty".to_string()));
}
Ok(contents)
}
Day 3-4:錯誤處理最佳實踐
use std::error::Error;
use std::fmt;
// 鏈式錯誤處理
#[derive(Debug)]
struct DatabaseError {
message: String,
code: u32,
}
impl fmt::Display for DatabaseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Database error {}: {}", self.code, self.message)
}
}
impl Error for DatabaseError {}
fn query_database(query: &str) -> Result<Vec<String>, DatabaseError> {
if query.is_empty() {
return Err(DatabaseError {
message: "Empty query".to_string(),
code: 1001,
});
}
// 模擬查詢
Ok(vec!["result1".to_string(), "result2".to_string()])
}
// 錯誤上下文
fn process_data(path: &str) -> Result<String, Box<dyn Error>> {
let contents = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path, e))?;
let result: Vec<&str> = contents.lines().collect();
Ok(result.join(", "))
}
Day 5-7:恐慌與unwrap
// unwrap 的使用時機
fn main() {
// 可以使用 unwrap 的情況(已知不會失敗)
let home: std::net::IpAddr = "127.0.0.1".parse().unwrap();
// 不應該使用 unwrap 的情況(可能失敗)
// let file = File::open("config.toml").unwrap(); // 不好!
// 應該這樣做
match File::open("config.toml") {
Ok(file) => println!("File opened"),
Err(e) => println!("Failed to open file: {}", e),
}
// 或者使用 expect(提供更好的錯誤訊息)
let file = File::open("config.toml")
.expect("Failed to open config.toml");
// 自訂 panic
fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("Cannot divide by zero!");
}
a / b
}
// 測試中的 panic
#[test]
#[should_panic(expected = "Cannot divide by zero")]
fn test_divide_by_zero() {
divide(10.0, 0.0);
}
}
第八週:並發
Day 1-2:執行緒
use std::thread;
use std::time::Duration;
fn main() {
// 建立執行緒
let handle = thread::spawn(|| {
for i in 1..10 {
println!("Spawned thread: {}", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("Main thread: {}", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
// 使用 move 關閉外部變數
let name = String::from("Alice");
let handle = thread::spawn(move || {
println!("Hello from thread: {}", name);
});
// println!("{}", name); // 錯誤!name 已移動
handle.join().unwrap();
}
Day 3-4:訊息傳遞
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// 建立通道
let (tx, rx) = mpsc::channel();
// 產生多個執行緒
for i in 0..5 {
let tx_clone = tx.clone();
thread::spawn(move || {
let message = format!("Message {}", i);
tx_clone.send(message).unwrap();
thread::sleep(Duration::from_millis(100));
});
}
drop(tx); // 關閉原始發送端
// 接收訊息
for received in rx {
println!("Received: {}", received);
}
}
Day 5-7:共享狀態
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 使用 Mutex 保護共享資料
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
第九週:非同步程式設計
Day 1-2:async/await 基礎
// 安裝 tokio
// tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};
// 非同步函式
async fn fetch_data(id: u32) -> String {
sleep(Duration::from_millis(100)).await;
format!("Data for id: {}", id)
}
#[tokio::main]
async fn main() {
// 顺序執行
let data1 = fetch_data(1).await;
let data2 = fetch_data(2).await;
println!("{} {}", data1, data2);
// 並行執行
let (data1, data2) = tokio::join!(
fetch_data(1),
fetch_data(2)
);
println!("{} {}", data1, data2);
}
Day 3-4:非同步 I/O
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
// 非同步檔案操作
let mut file = File::create("hello.txt").await?;
file.write_all(b"Hello, world!").await?;
let mut contents = String::new();
let mut file = File::open("hello.txt").await?;
file.read_to_string(&mut contents).await?;
println!("Contents: {}", contents);
// 非同步 HTTP(需要 reqwest)
// let resp = reqwest::get("https://httpbin.org/ip")
// .await?
// .text()
// .await?;
// println!("IP: {}", resp);
Ok(())
}
Day 5-7:非同步執行緒
use tokio::time::{sleep, Duration};
async fn task(id: u32) {
println!("Task {} started", id);
sleep(Duration::from_millis(100 * id as u64)).await;
println!("Task {} completed", id);
}
#[tokio::main]
async fn main() {
// 使用 spawn
let handle1 = tokio::spawn(task(1));
let handle2 = tokio::spawn(task(2));
let handle3 = tokio::spawn(task(3));
// 等待所有任務完成
let _ = tokio::join!(handle1, handle2, handle3);
// 使用 join! 同時執行
tokio::join!(
task(1),
task(2),
task(3)
);
}
第十週:錯誤處理進階
Day 1-2:thiserror 與 anyhow
// thiserror - 簡化錯誤類型定義
use thiserror::Error;
#[derive(Error, Debug)]
enum DatabaseError {
#[error("Connection failed: {0}")]
Connection(String),
#[error("Query failed: {0}")]
Query(String),
#[error("Not found: {0}")]
NotFound(String),
}
// anyhow - 簡化錯誤傳播
use anyhow::{Result, Context};
fn read_config() -> Result<Config> {
let contents = std::fs::read_to_string("config.toml")
.context("Failed to read config file")?;
let config: Config = toml::from_str(&contents)
.context("Failed to parse config")?;
Ok(config)
}
Day 3-4:錯誤處理模式
// 模式一:錯誤恢復
fn process_with_recovery(input: &str) -> Result<i32, String> {
let number: i32 = input.parse()
.map_err(|e| format!("Parse error: {}", e))?;
if number < 0 {
return Err("Number must be positive".to_string());
}
Ok(number * 2)
}
// 模式二:錯誤鏈
fn chain_errors() -> Result<(), Box<dyn std::error::Error>> {
let config = read_config()?;
let data = load_data(&config)?;
let result = process(data)?;
save(result)?;
Ok(())
}
// 模式三:錯誤作為值
enum OperationResult {
Success(String),
Retry(String),
Fail(String),
}
fn execute_operation() -> OperationResult {
match do_something() {
Ok(_) => OperationResult::Success("Done".to_string()),
Err(e) => {
if should_retry(&e) {
OperationResult::Retry(e.to_string())
} else {
OperationResult::Fail(e.to_string())
}
}
}
}
Day 5-7:日誌與追蹤
// 使用 tracing 而不是 println!
use tracing::{info, warn, error, debug, instrument};
#[instrument]
fn process_order(order_id: u32) -> Result<String, String> {
info!("Processing order {}", order_id);
let order = match fetch_order(order_id) {
Ok(o) => o,
Err(e) => {
error!("Failed to fetch order: {}", e);
return Err(e.to_string());
}
};
debug!("Order details: {:?}", order);
if order.total > 1000 {
warn!("Large order: {}", order.total);
}
info!("Order {} processed successfully", order_id);
Ok(format!("Order {} completed", order_id))
}
第十一週:實戰專案
Day 1-2:專案規劃
專案:CLI 任務管理工具
功能需求:
- 新增任務
- 列出任務
- 標記完成
- 刪除任務
- 儲存到檔案
技術架構:
- Rust 2021 edition
- clap(命令列參數)
- serde + serde_json(序列化)
- chrono(日期時間)
- colored(終端著色)
Day 3-7:實作
Day 3: 專案架構、資料模型、CLI 設定
Day 4: 新增/列出任務功能
Day 5: 完成/刪除任務功能
Day 6: 檔案儲存/讀取
Day 7: 測試、文件、打包
第十二週:進階主題
Day 1-2:巨集(Macros)
// 陳述式巨集
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
// 帶參數的巨集
macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("You called {:?}", stringify!($func_name));
}
};
}
// 重複模式
macro_rules! vec_of_enums {
($($variant:ident),*) => {
vec![$($variant),*]
};
}
fn main() {
say_hello!();
create_function!(foo);
create_function!(bar);
foo();
bar();
}
Day 3-4:unsafe Rust
// unsafe 允許:
// 1. 解引用裸指標
// 2. 呼叫 unsafe 函式
// 3. 存取或修改可變靜態變數
// 4. 實作 unsafe trait
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1: {}", *r1);
println!("r2: {}", *r2);
*r2 = 10;
println!("r2: {}", *r2);
}
}
// 安全的 unsafe 封裝
pub fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
Day 5-7:效能優化
// 使用迭代器而不是迴圈
fn sum_of_squares(n: u32) -> u32 {
(1..=n).map(|x| x * x).sum()
}
// 使用預分配
fn create_vec(size: usize) -> Vec<i32> {
let mut v = Vec::with_capacity(size);
for i in 0..size {
v.push(i as i32);
}
v
}
// 使用 str 而不是 String
fn process(input: &str) -> String {
// &str 是零成本抽象
input.to_uppercase()
}
// 基準測試
#[bench]
fn bench_sum(b: &mut Bencher) {
b.iter(|| sum_of_squares(1000));
}
學習資源
官方資源
推薦課程
社群
工具
- crates.io - 套件倉庫
- lib.rs - 套件搜尋
- Rust Playground - 線上編譯