課題22: マルチクレートプロジェクト

マンダトリー要件

問題1: Cargo機能の理解(15点)

以下の質問に答えなさい。

  • ワークスペースの利点(5点)
- ワークスペースを使う3つの主な利点を説明しなさい。 - 単一クレートと比較した場合のメリット・デメリットを述べなさい。

  • Features(5点)
- Featuresが必要な理由を説明しなさい。 - #[cfg(feature = "...")]の使い方を例とともに示すこと。

  • ビルドプロファイル(5点)
- devreleaseプロファイルの違いを説明しなさい。 - カスタムプロファイルが有用な場面を2つ挙げなさい。

問題2: タスク管理システムの構築(40点)

ワークスペースを使ったタスク管理システムを構築しなさい。

2.1 ワークスペース構成(15点)

task-manager/
├── Cargo.toml              # ワークスペースルート
├── core/
│   ├── Cargo.toml
│   └── src/lib.rs          # コアロジック
├── cli/
│   ├── Cargo.toml
│   └── src/main.rs         # CLIアプリ
├── api/
│   ├── Cargo.toml
│   └── src/main.rs         # APIサーバー
└── storage/
    ├── Cargo.toml
    └── src/lib.rs          # ストレージ抽象化

ワークスペースルートのCargo.toml:

[workspace]
members = [
    "core",
    "cli",
    "api",
    "storage",
]

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
chrono = "0.4"

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]

要件:

  • 4つのクレートを持つワークスペース
  • 共通の依存関係をworkspace.dependenciesで管理
  • 各クレート間の依存関係を適切に設定

2.2 Coreクレート(10点)

// core/src/lib.rs

use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
    pub id: u64,
    pub title: String,
    pub description: String,
    pub completed: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Priority {
    Low,
    Medium,
    High,
}

pub trait TaskRepository {
    fn create(&mut self, task: Task) -> Result<Task, String>;
    fn get(&self, id: u64) -> Result<Option<Task>, String>;
    fn list(&self) -> Result<Vec<Task>, String>;
    fn update(&mut self, task: Task) -> Result<Task, String>;
    fn delete(&mut self, id: u64) -> Result<(), String>;
}

2.3 Storageクレート(10点)

// storage/src/lib.rs

use core::{Task, TaskRepository};
use std::collections::HashMap;

pub struct MemoryStorage {
    tasks: HashMap<u64, Task>,
    next_id: u64,
}

impl MemoryStorage {
    pub fn new() -> Self {
        MemoryStorage {
            tasks: HashMap::new(),
            next_id: 1,
        }
    }
}

impl TaskRepository for MemoryStorage {
    // 実装
}

#[cfg(feature = "json")]
pub struct JsonStorage {
    file_path: String,
    // 実装
}

#[cfg(feature = "sqlite")]
pub struct SqliteStorage {
    // 実装
}

Cargo.toml:

[package]
name = "storage"
version.workspace = true
edition.workspace = true

[dependencies]
core = { path = "../core" }
serde.workspace = true
serde_json = { version = "1.0", optional = true }
rusqlite = { version = "0.29", optional = true }

[features]
json = ["serde_json"]
sqlite = ["rusqlite"]

2.4 CLIアプリ(5点)

// cli/src/main.rs

use core::{Task, TaskRepository};
use storage::MemoryStorage;
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "task")]
#[command(about = "A simple task manager")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Add { title: String },
    List,
    Complete { id: u64 },
    Delete { id: u64 },
}

fn main() {
    // 実装
}

問題3: Features実装(25点)

3.1 複数ストレージバックエンド(15点)

以下のfeaturesを実装しなさい:

# storage/Cargo.toml

[features]
default = ["memory"]
memory = []
json = ["serde_json"]
sqlite = ["rusqlite"]
postgres = ["tokio-postgres"]

各バックエンドの実装要件:

  • Memory: HashMap を使ったインメモリストレージ
  • JSON: ファイルベースのJSONストレージ
  • SQLite: SQLiteデータベース
  • PostgreSQL: PostgreSQLデータベース(ボーナス)

3.2 条件付きコンパイル(10点)

// storage/src/lib.rs

#[cfg(feature = "memory")]
pub mod memory;

#[cfg(feature = "json")]
pub mod json;

#[cfg(feature = "sqlite")]
pub mod sqlite;

// デフォルトバックエンドの選択
#[cfg(feature = "memory")]
pub use memory::MemoryStorage as DefaultStorage;

#[cfg(all(feature = "json", not(feature = "memory")))]
pub use json::JsonStorage as DefaultStorage;

---

ボーナス課題

ボーナス1: カスタムプロファイル(10点)

以下のカスタムプロファイルを作成しなさい:

# Cargo.toml (ワークスペースルート)

[profile.production]
inherits = "release"
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true

[profile.fast-dev]
inherits = "dev"
opt-level = 1

[profile.size-optimized]
inherits = "release"
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true

分析レポート:

  • 各プロファイルでビルドし、バイナリサイズを比較
  • ビルド時間を測定
  • 実行パフォーマンスを比較
  • ボーナス2: クレート公開準備(10点)

    crates.ioに公開できる状態にしなさい:

  • メタデータ完備
   [package]
   name = "task-manager-core"
   version = "0.1.0"
   description = "Core library for task management"
   license = "MIT OR Apache-2.0"
   repository = "https://github.com/user/task-manager"
   keywords = ["task", "todo", "productivity"]
   categories = ["command-line-utilities"]
   

  • ドキュメント
- すべての公開APIにドキュメント - README.md with examples - CHANGELOG.md

  • テスト
- カバレッジ80%以上 - ドキュメントテスト

  • ライセンス
- LICENSE-MIT - LICENSE-APACHE

ボーナス3: ビルドスクリプト(10点)

ビルド時にバージョン情報を埋め込むビルドスクリプトを作成しなさい:

// build.rs

use std::process::Command;

fn main() {
    // Gitコミットハッシュ
    let output = Command::new("git")
        .args(&["rev-parse", "HEAD"])
        .output()
        .unwrap();
    let git_hash = String::from_utf8(output.stdout).unwrap();
    println!("cargo:rustc-env=GIT_HASH={}", git_hash);

    // ビルド日時
    let build_time = chrono::Utc::now().to_rfc3339();
    println!("cargo:rustc-env=BUILD_TIME={}", build_time);

    // Rustバージョン
    let rustc_version = rustc_version::version().unwrap();
    println!("cargo:rustc-env=RUSTC_VERSION={}", rustc_version);
}

使用例:

// cli/src/main.rs

fn version_info() {
    println!("Version: {}", env!("CARGO_PKG_VERSION"));
    println!("Git: {}", env!("GIT_HASH"));
    println!("Built: {}", env!("BUILD_TIME"));
    println!("Rustc: {}", env!("RUSTC_VERSION"));
}

ボーナス4: マルチプラットフォームビルド(10点)

複数のプラットフォーム向けにビルドしなさい:

# GitHub Actionsを使用

# .github/workflows/release.yml

name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
          - os: windows-latest
            target: x86_64-pc-windows-msvc
          - os: macos-latest
            target: x86_64-apple-darwin

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v2
      - name: Build
        run: cargo build --release --target ${{ matrix.target }}
      - name: Upload artifact
        uses: actions/upload-artifact@v2
        with:
          name: task-manager-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/cli

---

評価基準

マンダトリー部分(80点)

項目 配点 評価ポイント
問題1: 基礎理解 15点 正確な説明
問題2: ワークスペース 40点 適切な構成と実装
問題3: Features 25点 正しい条件付きコンパイル

ボーナス部分(20点)

項目 配点 評価ポイント
ボーナス1: プロファイル 10点 詳細な分析
ボーナス2: 公開準備 10点 完全なメタデータ
ボーナス3: ビルドスクリプト 10点 正しい実装
ボーナス4: マルチプラットフォーム 10点 CI/CD設定

: ボーナスは最大20点まで加算されます。

---

提出方法

ファイル構成

rust-foundations-22/
├── Cargo.toml
├── Cargo.lock
├── README.md
├── LICENSE-MIT
├── LICENSE-APACHE
├── CHANGELOG.md
├── answers.md              # 問題1の回答
├── build.rs                # ボーナス3
├── .github/
│   └── workflows/
│       └── release.yml     # ボーナス4
├── core/
├── cli/
├── api/
└── storage/

テストとビルド

# すべてテスト
cargo test --workspace

# すべてビルド
cargo build --workspace --release

# 特定のfeatureでビルド
cargo build -p storage --features json
cargo build -p storage --features sqlite

# カスタムプロファイル
cargo build --profile production

---

ヒント

問題2のヒント

依存関係の設定:

# cli/Cargo.toml
[dependencies]
core = { path = "../core" }
storage = { path = "../storage" }
clap = { version = "4.0", features = ["derive"] }

問題3のヒント

Feature の組み合わせテスト:

# すべてのfeature組み合わせをテスト
cargo test --features memory
cargo test --features json
cargo test --features sqlite
cargo test --all-features
cargo test --no-default-features

---

学習の確認

この課題を通じて、以下を理解できたか確認してください:

  • [ ] ワークスペースの構成と管理
  • [ ] Featuresの設計と実装
  • [ ] カスタムプロファイルの作成
  • [ ] クレート公開の準備
  • [ ] ビルドスクリプトの使用

次の章では、パフォーマンス最適化を学びます。