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));
}

學習資源

官方資源

推薦課程

社群

工具