Day 7:第十週總結練習

學習目標

  • 整合 thiserror / anyhow / tracing / 非同步
  • 完成一個具備完整錯誤處理與日誌的程式

今日重點

本週回顧

  1. thiserror:精簡定義錯誤、#[from]
  2. anyhow:快速傳播、context
  3. 錯誤處理模式(recovery / chain / error as value)
  4. tracing:event / span / structure field

綜合練習:非同步 HTTP 下載器(含完整錯誤與日誌)

use anyhow::{Result, Context};
use serde::Deserialize;
use thiserror::Error;
use tracing::{info, error, warn, instrument};

#[derive(Error, Debug)]
enum AppError {
    #[error("http request failed: {0}")]
    Http(String),
    #[error("json parse failed: {0}")]
    Json(#[from] serde_json::Error),
}

#[derive(Deserialize, Debug)]
struct Todo {
    id: u32,
    title: String,
}

#[instrument]
async fn fetch_todo(id: u32) -> Result<Todo> {
    let url = format!("https://jsonplaceholder.typicode.com/todos/{}", id);
    info!(id, "fetching todo");

    let body = reqwest::get(&url)
        .await
        .map_err(|e| AppError::Http(e.to_string()))
        .context("request todo")?;

    let todo = body.json::<Todo>().await?;   // serde_json 自動轉 AppError::Json
    Ok(todo)
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt().init();

    for id in 1..=3 {
        match fetch_todo(id).await {
            Ok(todo) => info!("todo {}: {}", todo.id, todo.title),
            Err(e) => error!("failed: {:?}", e),   // anyhow 錯誤鏈
        }
    }
    Ok(())
}

練習換成並行版本(spawn + join)並觀察 tracing span。

挑戰

  • 加上 timeout 讓請求限時。
  • #[instrument] 觀察每個 span 的耗時。
  • 思考 library vs application 的錯誤責任。

自我檢查

  • 能整合 thiserror + anyhow + tracing + async
  • 會設計內外層錯誤策略
  • 能寫結構化、可診斷的日誌
  • 具備生產等級的錯誤處理能力

週總結

至此你已有「生產級」的 Rust 工程能力。 下一週進入 12 週的最終寫照:實戰專案!