RustでCLIツール開発入門:clapからクロスプラットフォーム配布まで完全解説

はじめに CLIツールの開発言語として、Rustは非常に優れた選択肢です。高速な実行速度、シングルバイナリでの配布、強力な型システムによる安全性、そしてクロスプラットフォーム対応が容易という特徴があります。 本記事では、Rustを使って実用的なCLIツールを開発する手順を、プロジェクトのセットアップからクロスプラットフォーム配布まで一貫して解説します。 プロジェクトのセットアップ Cargoプロジェクトの作成 1 2 3 4 5 6 # 新規プロジェクト作成 cargo new mytools --name mytools cd mytools # または既存ディレクトリで初期化 cargo init 基本的な依存関係 Cargo.tomlに必要なクレートを追加します: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [package] name = "mytools" version = "0.1.0" edition = "2021" authors = ["Your Name <[email protected]>"] description = "A collection of useful CLI tools" license = "MIT" repository = "https://github.com/yourname/mytools" keywords = ["cli", "tools", "utility"] categories = ["command-line-utilities"] [dependencies] # 引数パース clap = { version = "4.5", features = ["derive", "env", "wrap_help"] } # 非同期ランタイム tokio = { version = "1.36", features = ["full"] } # HTTP クライアント reqwest = { version = "0.12", features = ["json", "rustls-tls"] } # JSON処理 serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # エラーハンドリング anyhow = "1.0" thiserror = "1.0" # ログ出力 tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } # 設定ファイル config = "0.14" # プログレスバー indicatif = "0.17" # カラー出力 colored = "2.1" # ファイルシステム操作 walkdir = "2.4" # 日時処理 chrono = { version = "0.4", features = ["serde"] } [dev-dependencies] assert_cmd = "2.0" predicates = "3.1" tempfile = "3.10" [profile.release] lto = true codegen-units = 1 panic = "abort" strip = true clapを使った引数パース 基本的なCLI構造 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 // src/main.rs use clap::{Parser, Subcommand, Args}; use anyhow::Result; /// A collection of useful CLI tools #[derive(Parser)] #[command(name = "mytools")] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] struct Cli { /// Enable verbose output #[arg(short, long, global = true)] verbose: bool, /// Configuration file path #[arg(short, long, global = true, env = "MYTOOLS_CONFIG")] config: Option<std::path::PathBuf>, #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Fetch data from API Fetch(FetchArgs), /// Process files Process(ProcessArgs), /// Show configuration Config(ConfigArgs), } #[derive(Args)] struct FetchArgs { /// API endpoint URL #[arg(short, long)] url: String, /// Output file path #[arg(short, long)] output: Option<std::path::PathBuf>, /// Request timeout in seconds #[arg(short, long, default_value = "30")] timeout: u64, /// Number of retry attempts #[arg(long, default_value = "3")] retries: u32, } #[derive(Args)] struct ProcessArgs { /// Input files or directories #[arg(required = true)] inputs: Vec<std::path::PathBuf>, /// Output directory #[arg(short, long, default_value = ".")] output_dir: std::path::PathBuf, /// Process files in parallel #[arg(short, long)] parallel: bool, /// Number of worker threads #[arg(short = 'j', long, default_value = "4")] jobs: usize, } #[derive(Args)] struct ConfigArgs { /// Show current configuration #[arg(long)] show: bool, /// Initialize configuration file #[arg(long)] init: bool, } #[tokio::main] async fn main() -> Result<()> { let cli = Cli::parse(); // ログの初期化 init_logging(cli.verbose)?; // 設定の読み込み let config = load_config(cli.config.as_deref())?; match cli.command { Commands::Fetch(args) => cmd_fetch(args, &config).await, Commands::Process(args) => cmd_process(args, &config).await, Commands::Config(args) => cmd_config(args, &config), } } ログ出力の設定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // src/logging.rs use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use anyhow::Result; pub fn init_logging(verbose: bool) -> Result<()> { let filter = if verbose { EnvFilter::new("debug") } else { EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info")) }; tracing_subscriber::registry() .with(filter) .with(fmt::layer().with_target(false).with_thread_ids(false)) .init(); Ok(()) } 設定ファイルの扱い 設定構造体の定義 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 // src/config.rs use serde::{Deserialize, Serialize}; use std::path::PathBuf; use anyhow::{Context, Result}; #[derive(Debug, Deserialize, Serialize, Default)] pub struct AppConfig { #[serde(default)] pub api: ApiConfig, #[serde(default)] pub processing: ProcessingConfig, #[serde(default)] pub output: OutputConfig, } #[derive(Debug, Deserialize, Serialize)] pub struct ApiConfig { pub base_url: String, pub api_key: Option<String>, pub timeout_seconds: u64, pub max_retries: u32, } impl Default for ApiConfig { fn default() -> Self { Self { base_url: "https://api.example.com".to_string(), api_key: None, timeout_seconds: 30, max_retries: 3, } } } #[derive(Debug, Deserialize, Serialize)] pub struct ProcessingConfig { pub parallel: bool, pub max_workers: usize, pub chunk_size: usize, } impl Default for ProcessingConfig { fn default() -> Self { Self { parallel: true, max_workers: num_cpus::get(), chunk_size: 1024 * 1024, // 1MB } } } #[derive(Debug, Deserialize, Serialize)] pub struct OutputConfig { pub format: OutputFormat, pub color: bool, pub quiet: bool, } #[derive(Debug, Deserialize, Serialize, Default)] #[serde(rename_all = "lowercase")] pub enum OutputFormat { #[default] Text, Json, Csv, } impl Default for OutputConfig { fn default() -> Self { Self { format: OutputFormat::Text, color: true, quiet: false, } } } pub fn load_config(path: Option<&std::path::Path>) -> Result<AppConfig> { let builder = config::Config::builder(); // デフォルト設定 let builder = builder.add_source(config::File::from_str( include_str!("../config/default.toml"), config::FileFormat::Toml, )); // ユーザー設定ファイル let config_path = path .map(PathBuf::from) .or_else(|| { dirs::config_dir().map(|p| p.join("mytools").join("config.toml")) }); let builder = if let Some(ref path) = config_path { if path.exists() { builder.add_source(config::File::from(path.as_path())) } else { builder } } else { builder }; // 環境変数からの上書き let builder = builder.add_source( config::Environment::with_prefix("MYTOOLS") .separator("__") .try_parsing(true), ); let config = builder .build() .context("Failed to build configuration")? .try_deserialize() .context("Failed to deserialize configuration")?; Ok(config) } エラーハンドリング カスタムエラー型の定義 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // src/error.rs use thiserror::Error; #[derive(Error, Debug)] pub enum AppError { #[error("API error: {message} (status: {status})")] Api { status: u16, message: String, }, #[error("File not found: {path}")] FileNotFound { path: std::path::PathBuf, }, #[error("Invalid input: {0}")] InvalidInput(String), #[error("Configuration error: {0}")] Config(String), #[error("Network error: {0}")] Network(#[from] reqwest::Error), #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("JSON error: {0}")] Json(#[from] serde_json::Error), } pub type AppResult<T> = Result<T, AppError>; エラーハンドリングの実践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 // src/commands/fetch.rs use crate::config::AppConfig; use crate::error::{AppError, AppResult}; use anyhow::{Context, Result}; use indicatif::{ProgressBar, ProgressStyle}; use std::time::Duration; use tracing::{debug, info, warn}; pub async fn cmd_fetch(args: FetchArgs, config: &AppConfig) -> Result<()> { info!("Fetching data from: {}", args.url); let client = reqwest::Client::builder() .timeout(Duration::from_secs(args.timeout)) .build() .context("Failed to create HTTP client")?; let mut last_error = None; for attempt in 1..=args.retries { debug!("Attempt {}/{}", attempt, args.retries); match fetch_with_progress(&client, &args.url).await { Ok(data) => { // 出力処理 if let Some(ref output_path) = args.output { std::fs::write(output_path, &data) .with_context(|| format!("Failed to write to {:?}", output_path))?; info!("Data saved to {:?}", output_path); } else { println!("{}", data); } return Ok(()); } Err(e) => { warn!("Attempt {} failed: {}", attempt, e); last_error = Some(e); if attempt < args.retries { let delay = Duration::from_secs(2u64.pow(attempt)); debug!("Retrying in {:?}...", delay); tokio::time::sleep(delay).await; } } } } Err(last_error.unwrap().into()) } async fn fetch_with_progress(client: &reqwest::Client, url: &str) -> AppResult<String> { let response = client.get(url).send().await?; if !response.status().is_success() { return Err(AppError::Api { status: response.status().as_u16(), message: response.text().await.unwrap_or_default(), }); } let total_size = response.content_length().unwrap_or(0); let pb = ProgressBar::new(total_size); pb.set_style( ProgressStyle::default_bar() .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")? .progress_chars("#>-"), ); let mut downloaded = 0u64; let mut content = Vec::new(); let mut stream = response.bytes_stream(); use futures_util::StreamExt; while let Some(chunk) = stream.next().await { let chunk = chunk?; downloaded += chunk.len() as u64; pb.set_position(downloaded); content.extend_from_slice(&chunk); } pb.finish_with_message("Download complete"); String::from_utf8(content) .map_err(|e| AppError::InvalidInput(format!("Invalid UTF-8: {}", e))) } ファイル処理コマンドの実装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 // src/commands/process.rs use crate::config::AppConfig; use anyhow::{Context, Result}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use rayon::prelude::*; use std::path::PathBuf; use tracing::info; use walkdir::WalkDir; pub async fn cmd_process(args: ProcessArgs, config: &AppConfig) -> Result<()> { // 処理対象ファイルの収集 let files = collect_files(&args.inputs)?; info!("Found {} files to process", files.len()); if files.is_empty() { println!("No files to process"); return Ok(()); } // 出力ディレクトリの作成 std::fs::create_dir_all(&args.output_dir) .with_context(|| format!("Failed to create output directory: {:?}", args.output_dir))?; if args.parallel { process_parallel(&files, &args.output_dir, args.jobs)?; } else { process_sequential(&files, &args.output_dir)?; } Ok(()) } fn collect_files(inputs: &[PathBuf]) -> Result<Vec<PathBuf>> { let mut files = Vec::new(); for input in inputs { if input.is_file() { files.push(input.clone()); } else if input.is_dir() { for entry in WalkDir::new(input) .follow_links(true) .into_iter() .filter_map(|e| e.ok()) { if entry.file_type().is_file() { files.push(entry.into_path()); } } } } Ok(files) } fn process_parallel(files: &[PathBuf], output_dir: &PathBuf, jobs: usize) -> Result<()> { let pool = rayon::ThreadPoolBuilder::new() .num_threads(jobs) .build() .context("Failed to create thread pool")?; let mp = MultiProgress::new(); let pb = mp.add(ProgressBar::new(files.len() as u64)); pb.set_style( ProgressStyle::default_bar() .template("{spinner:.green} Processing [{bar:40}] {pos}/{len} ({percent}%)")? .progress_chars("=> "), ); let results: Vec<Result<(), anyhow::Error>> = pool.install(|| { files .par_iter() .map(|file| { let result = process_single_file(file, output_dir); pb.inc(1); result }) .collect() }); pb.finish_with_message("Processing complete"); // エラーの集計 let errors: Vec<_> = results.into_iter().filter_map(|r| r.err()).collect(); if !errors.is_empty() { eprintln!("Errors occurred during processing:"); for err in &errors { eprintln!(" - {}", err); } } Ok(()) } fn process_sequential(files: &[PathBuf], output_dir: &PathBuf) -> Result<()> { let pb = ProgressBar::new(files.len() as u64); pb.set_style( ProgressStyle::default_bar() .template("{spinner:.green} Processing [{bar:40}] {pos}/{len}")? .progress_chars("=> "), ); for file in files { process_single_file(file, output_dir)?; pb.inc(1); } pb.finish_with_message("Processing complete"); Ok(()) } fn process_single_file(input: &PathBuf, output_dir: &PathBuf) -> Result<()> { // ファイル処理のロジック let content = std::fs::read_to_string(input) .with_context(|| format!("Failed to read: {:?}", input))?; // 何らかの処理(例:行数カウント、変換など) let processed = content.lines().count(); let output_name = input .file_name() .map(|n| n.to_string_lossy().to_string()) .unwrap_or_else(|| "output".to_string()); let output_path = output_dir.join(format!("{}.processed", output_name)); std::fs::write(&output_path, format!("Lines: {}", processed)) .with_context(|| format!("Failed to write: {:?}", output_path))?; Ok(()) } テストの実装 単体テスト 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // src/lib.rs #[cfg(test)] mod tests { use super::*; #[test] fn test_config_default() { let config = AppConfig::default(); assert_eq!(config.api.timeout_seconds, 30); assert!(config.processing.parallel); } #[test] fn test_collect_files_single() { let temp = tempfile::tempdir().unwrap(); let file_path = temp.path().join("test.txt"); std::fs::write(&file_path, "content").unwrap(); let files = collect_files(&[file_path.clone()]).unwrap(); assert_eq!(files.len(), 1); assert_eq!(files[0], file_path); } } 統合テスト(CLIテスト) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 // tests/cli.rs use assert_cmd::Command; use predicates::prelude::*; use tempfile::tempdir; #[test] fn test_help() { let mut cmd = Command::cargo_bin("mytools").unwrap(); cmd.arg("--help") .assert() .success() .stdout(predicate::str::contains("A collection of useful CLI tools")); } #[test] fn test_version() { let mut cmd = Command::cargo_bin("mytools").unwrap(); cmd.arg("--version") .assert() .success() .stdout(predicate::str::contains(env!("CARGO_PKG_VERSION"))); } #[test] fn test_process_command() { let input_dir = tempdir().unwrap(); let output_dir = tempdir().unwrap(); // テストファイルの作成 std::fs::write(input_dir.path().join("test1.txt"), "line1\nline2\nline3").unwrap(); std::fs::write(input_dir.path().join("test2.txt"), "single line").unwrap(); let mut cmd = Command::cargo_bin("mytools").unwrap(); cmd.args([ "process", input_dir.path().to_str().unwrap(), "-o", output_dir.path().to_str().unwrap(), ]) .assert() .success(); // 出力ファイルの確認 assert!(output_dir.path().join("test1.txt.processed").exists()); assert!(output_dir.path().join("test2.txt.processed").exists()); } #[test] fn test_fetch_invalid_url() { let mut cmd = Command::cargo_bin("mytools").unwrap(); cmd.args(["fetch", "--url", "invalid-url"]) .assert() .failure(); } クロスプラットフォームビルド GitHub Actionsでの自動ビルド 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 # .github/workflows/release.yml name: Release on: push: tags: - 'v*' permissions: contents: write jobs: build: strategy: matrix: include: - target: x86_64-unknown-linux-gnu os: ubuntu-latest name: linux-x64 - target: x86_64-unknown-linux-musl os: ubuntu-latest name: linux-x64-musl - target: aarch64-unknown-linux-gnu os: ubuntu-latest name: linux-arm64 - target: x86_64-apple-darwin os: macos-latest name: macos-x64 - target: aarch64-apple-darwin os: macos-latest name: macos-arm64 - target: x86_64-pc-windows-msvc os: windows-latest name: windows-x64 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-action@stable with: targets: ${{ matrix.target }} - name: Install cross-compilation tools if: matrix.target == 'aarch64-unknown-linux-gnu' run: | sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu - name: Install musl tools if: matrix.target == 'x86_64-unknown-linux-musl' run: | sudo apt-get update sudo apt-get install -y musl-tools - name: Build run: cargo build --release --target ${{ matrix.target }} - name: Package (Unix) if: runner.os != 'Windows' run: | cd target/${{ matrix.target }}/release tar czf ../../../mytools-${{ matrix.name }}.tar.gz mytools cd ../../.. - name: Package (Windows) if: runner.os == 'Windows' run: | cd target/${{ matrix.target }}/release 7z a ../../../mytools-${{ matrix.name }}.zip mytools.exe cd ../../.. - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: mytools-${{ matrix.name }} path: mytools-${{ matrix.name }}.* release: needs: build runs-on: ubuntu-latest steps: - name: Download all artifacts uses: actions/download-artifact@v4 - name: Create Release uses: softprops/action-gh-release@v1 with: files: | mytools-*/mytools-* draft: false prerelease: false ローカルでのクロスコンパイル 1 2 3 4 5 6 7 8 # クロスツールのインストール cargo install cross # Linux ARM64向けビルド cross build --release --target aarch64-unknown-linux-gnu # Windows向けビルド(Linux/macOSから) cross build --release --target x86_64-pc-windows-gnu まとめ RustでCLIツールを開発する際の重要なポイントをまとめます: ...

March 8, 2026 · 12 min · AI2CORE 編集部

Rust言語がLinuxカーネル開発の標準に?現状と課題

はい、承知いたしました。プロの技術ブロガーとして、「Rust言語がLinuxカーネル開発の標準に?現状と課題」というテーマで、非常に読み応えのある高品質な技術ブログ記事を執筆します。以下が記事本文です。 Rust言語がLinuxカーネル開発の標準に?現状と課題 「Linuxカーネルの次の30年を支える言語は何か?」 もしあなたがシステムプログラミングやOSの動向に少しでも関心があるなら、この問いの答えとして「Rust」という名前を耳にする機会が急増しているはずです。30年以上にわたりC言語という“不動の王”が君臨してきたこの世界に、なぜ今、Rustという新しい言語が大きな注目を集めているのでしょうか。 「C言語で書かれた3000万行以上の巨大なコードベースに、本当に新しい言語を導入できるのか?」 「Rustのメモリ安全性は魅力的だが、パフォーマンスや既存コードとの連携はどうなっているのか?」 「これは一部の先進的な開発者のお遊びで、結局はC言語が使われ続けるのではないか?」 この記事は、そんな疑問を抱えるすべてのエンジニアに向けて執筆しました。本記事を読めば、LinuxカーネルにおけるRust導入の最新動向、その背景にある根深い課題、具体的なコードレベルでの違い、そして開発現場が直面しているリアルな課題までを体系的に理解することができます。これは単なる技術トレンドの話ではありません。私たちが日々利用しているコンピューティングの根幹を、より安全で堅牢なものへと進化させるための壮大な挑戦の物語です。 なぜ今、LinuxカーネルにRustが必要なのか? C言語の栄光と限界 Linuxカーネル開発の歴史は、C言語と共にありました。ハードウェアを直接制御できる低レベルな操作性、他の言語を圧倒する実行速度、そして豊富な開発者コミュニティ。C言語は、カーネルという複雑怪奇なソフトウェアを記述するための最適なツールとして、30年以上にわたりその地位を確立してきました。 しかし、その輝かしい歴史の裏で、開発者たちは長年一つの大きな問題と戦い続けてきました。それがメモリ安全性の問題です。 Googleの調査によれば、Chromeブラウザで見つかった深刻なセキュリティ脆弱性の約70%がメモリ安全性の問題に起因するものでした。Microsoftも同様に、自社製品の脆弱性の約70%が同じ原因であると報告しています。この傾向はLinuxカーネルも例外ではありません。 C言語では、プログラマがメモリの確保 (malloc) と解放 (free) を手動で管理する必要があります。この自由度の高さがパフォーマンスの源泉である一方、以下のような典型的なバグ、すなわちセキュリティ脆弱性の温床となります。 バッファオーバーフロー: 配列の境界を超えてデータを書き込んでしまう問題。意図しないコードを実行される可能性があります。 Use-after-free: 解放済みのメモリ領域にアクセスしてしまう問題。予測不能な動作や情報漏洩につながります。 ダングリングポインタ: 解放されたメモリ領域を指し続けるポインタ。Use-after-freeの原因となります。 データ競合: 複数のスレッドが同時に同じメモリ領域にアクセスし、少なくとも一つのスレッドが書き込みを行うことで発生する問題。 これらの問題は、コードレビューや静的解析ツール、ファジングなど、様々な手法で検出しようと試みられてきましたが、完全に防ぐことは極めて困難です。どんなに経験豊富な開発者であっても、人間である以上ミスを犯します。そして、カーネルにおけるたった一つのメモリ関連バグが、システム全体を危険に晒す致命的な脆弱性となり得るのです。 この根深い問題を解決するため、Linuxカーネルコミュニティは新しいアプローチを模索し始めました。そして、その最有力候補として白羽の矢が立ったのが、Rustだったのです。 Rust for Linux: カーネル開発の新時代 Rustは、C/C++に匹敵するパフォーマンスと、モダンな高水準言語の安全性を両立させることを目指して設計された言語です。その最大の特徴は、**「所有権」「借用」「ライフタイム」**という独自の仕組みによって、コンパイル時にメモリ安全性を保証する点にあります。 所有権 (Ownership): 全てのデータには「所有者」となる変数がただ一つだけ存在する。所有者がスコープを抜けると、データは自動的に解放される。これにより、メモリの二重解放や解放忘れが原理的に発生しません。 借用 (Borrowing): データの所有権を移動させずに、そのデータへの参照(ポインタのようなもの)を貸し出すことができる。借用には、不変参照(&T、複数可)と可変参照(&mut T、一つだけ)のルールがあり、データ競合を防ぎます。 ライフタイム (Lifetime): コンパイラが全ての参照の有効期間を追跡し、ダングリングポインタ(無効なメモリを指す参照)が存在しないことを保証します。 これらの仕組みにより、**「コンパイルが通れば、メモリ関連のバグの大部分は存在しない」**という驚異的な安全性を実現します。これが、Linuxカーネル開発者たちがRustに強く惹かれた理由です。 プロジェクトの歩みと現状 「Rust for Linux」プロジェクトは、2020年頃から本格的な議論が始まり、多くの実験と議論を経て、2022年10月、ついに歴史的な瞬間を迎えます。Linus Torvalds氏自身がRustサポートの初期コードをメインラインカーネルにマージし、Linux 6.1から正式にカーネル内でのRust利用が(実験的機能として)可能になりました。 現在、Rustは主に新しいデバイスドライバやサブシステムの実装に利用されています。既存のCコードをRustで書き換えるのではなく、新規開発部分でRustを採用し、そのメリットを享受しようという現実的なアプローチが取られています。 具体的な導入例としては、以下のようなものがあります。 Android Binder: AndroidのIPC(プロセス間通信)メカニズムであるBinderのRust実装が進んでいます。 Asahi Linux: Apple Silicon (M1/M2) 搭載MacでLinuxを動作させるプロジェクトで、GPUドライバの一部がRustで書かれています。 ファイルシステム: NTFS3ドライバの一部機能や、新しいファイルシステムの実装実験など。 具体的なコードで見るCとRustの違い 百聞は一見にしかず。簡単なカーネルモジュールをC言語とRustで比較してみましょう。ここでは、モジュールロード時にメッセージを、アンロード時に別のメッセージをカーネルログに出力するだけのシンプルな例を見ていきます。 C言語での実装 (hello_c.c) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple C kernel module."); MODULE_VERSION("0.1"); static int __init hello_c_init(void) { printk(KERN_INFO "Hello, C world! Module loaded.\n"); return 0; } static void __exit hello_c_exit(void) { printk(KERN_INFO "Goodbye, C world! Module unloaded.\n"); } module_init(hello_c_init); module_exit(hello_c_exit); C言語でのカーネルモジュール開発者にはおなじみのコードです。module_initとmodule_exitマクロを使って初期化関数と終了関数を登録します。 ...

February 19, 2026 · 3 min · AI2CORE 編集部