Chuyển đến nội dung chính

レッスン 16: キャッシュ、CLI ツール、マクロ

Deadpool-Redis を使用した Redis キャッシュ。拍手を伴うコマンドラインツール。手続き型マクロ、派生マクロ。 Serdeカスタムシリアル化。構成管理。

💻 プログラミング — レッスン 16 レッスン 16: キャッシュ、CLI ツール、マクロ

Rust: 基本から上級まで

パート 4: 高度なバックエンド

xdev.asia

1. Redis キャッシュ

use deadpool_redis::{Config, Runtime, Pool};
use redis::AsyncCommands;

async fn setup_redis() -> Pool {
    let cfg = Config::from_url("redis://localhost:6379");
    cfg.create_pool(Some(Runtime::Tokio1)).unwrap()
}

async fn get_cached<T: serde::de::DeserializeOwned + serde::Serialize>(
    pool: &Pool,
    key: &str,
    ttl_secs: u64,
    fetch: impl std::future::Future<Output = Result<T, anyhow::Error>>,
) -> Result<T, anyhow::Error> {
    let mut conn = pool.get().await?;

    // Try cache
    let cached: Option<String> = conn.get(key).await?;
    if let Some(data) = cached {
        return Ok(serde_json::from_str(&data)?);
    }

    // Fetch & cache
    let value = fetch.await?;
    let serialized = serde_json::to_string(&value)?;
    conn.set_ex(key, &serialized, ttl_secs).await?;
    Ok(value)
}

2. Clap を使用した CLI ツール

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "myctl", version, about = "My CLI tool")]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    /// Enable verbose output
    #[arg(short, long, global = true)]
    verbose: bool,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new project
    Init {
        /// Project name
        #[arg(short, long)]
        name: String,
        /// Template to use
        #[arg(short, long, default_value = "default")]
        template: String,
    },
    /// Deploy to production
    Deploy {
        /// Environment
        #[arg(short, long)]
        env: String,
        /// Skip confirmation
        #[arg(long)]
        yes: bool,
    },
    /// Run database migrations
    Migrate {
        #[command(subcommand)]
        action: MigrateAction,
    },
}

#[derive(Subcommand)]
enum MigrateAction {
    Up,
    Down { steps: Option<u32> },
    Status,
}

fn main() {
    let cli = Cli::parse();
    match cli.command {
        Commands::Init { name, template } => {
            println!("Creating project '{}' with template '{}'", name, template);
        }
        Commands::Deploy { env, yes } => {
            println!("Deploying to {}", env);
        }
        Commands::Migrate { action } => match action {
            MigrateAction::Up => println!("Running migrations"),
            MigrateAction::Down { steps } => println!("Rolling back {:?}", steps),
            MigrateAction::Status => println!("Migration status"),
        },
    }
}

3. 宣言型マクロ

// macro_rules! — pattern-based
macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)?) => {
        {
            let mut map = std::collections::HashMap::new();
            $(map.insert($key, $value);)*
            map
        }
    };
}

let scores = hashmap! {
    "Alice" => 95,
    "Bob" => 87,
    "Charlie" => 92,
};

4. 構成管理

use config::{Config, Environment, File};

#[derive(Deserialize)]
struct AppConfig {
    server: ServerConfig,
    database: DatabaseConfig,
    redis: RedisConfig,
}

#[derive(Deserialize)]
struct ServerConfig {
    host: String,
    port: u16,
}

#[derive(Deserialize)]
struct DatabaseConfig {
    url: String,
    max_connections: u32,
}

fn load_config() -> Result<AppConfig, config::ConfigError> {
    let config = Config::builder()
        .add_source(File::with_name("config/default"))
        .add_source(File::with_name("config/local").required(false))
        .add_source(Environment::with_prefix("APP").separator("__"))
        .build()?;

    config.try_deserialize()
}
// APP__SERVER__PORT=3000 → server.port = 3000

次の記事: Rust を使用した WebAssembly。