Rustプログラミング入門:安全性と速度を両立する現代言語

Rustの基本から実践的な使い方まで、WebAssemblyやバックエンド開発などの実用的なユースケースを交えて解説します。所有権システムや並行処理など、Rustの特徴的な機能も詳しく説明します。

技術ブログ

はじめに

Rustは、パフォーマンス、信頼性、生産性を重視した現代的なシステムプログラミング言語です。 メモリ安全性を型システムで保証しながら、C++並みの高速な実行を実現します。 この記事では、Rustの基本から実践的な使い方まで、段階的に解説します。

Rustの主な特徴

  • コンパイル時のメモリ安全性保証
  • ゼロコスト抽象化
  • 充実した型システム
  • 優れた並行処理サポート
  • クロスプラットフォーム対応

開発環境のセットアップ

1. Rustのインストール

# Windows (PowerShell)
winget install Rustlang.Rust

# macOS / Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

2. プロジェクトの作成

# 新規プロジェクトの作成
cargo new my_project
cd my_project

# 依存関係の追加(例:serdeを追加)
cargo add serde --features derive

# ビルドと実行
cargo build
cargo run

推奨開発ツール

  • rust-analyzer: 高度なIDE支援
  • clippy: 高度な静的解析
  • rustfmt: コードフォーマッタ
  • cargo-edit: 依存関係管理

基本文法

1. 変数と型

// 変数宣言
let x = 42; // 型推論
let y: i32 = 42; // 明示的な型指定
let mut z = 42; // ミュータブルな変数

// 基本的な型
let integer: i32 = 42;
let float: f64 = 3.14;
let boolean: bool = true;
let character: char = 'A';
let text: &str = "Hello, Rust!";
let string: String = String::from("Hello, Rust!");

// タプル
let tuple: (i32, f64, &str) = (42, 3.14, "hello");

// 配列
let array: [i32; 3] = [1, 2, 3];

// ベクター
let mut vector: Vec<i32> = vec![1, 2, 3];
vector.push(4);

2. 制御構文

// if式
let number = 42;
if number < 0 {
    println!("負の数です");
} else if number > 0 {
    println!("正の数です");
} else {
    println!("ゼロです");
}

// match式
let opt = Some(42);
match opt {
    Some(n) => println!("値は{}です", n),
    None => println!("値はありません"),
}

// for文
for i in 0..5 {
    println!("{}", i);
}

// while文
let mut counter = 0;
while counter < 5 {
    println!("{}", counter);
    counter += 1;
}

所有権システム

Rustの特徴的な機能である所有権システムについて説明します。 これにより、コンパイル時にメモリ安全性が保証されます。

1. 所有権の基本ルール

// 所有権の移動
let s1 = String::from("hello");
let s2 = s1; // s1の所有権がs2に移動
// println!("{}", s1); // エラー:s1は無効

// 参照と借用
let s1 = String::from("hello");
let len = calculate_length(&s1); // s1の参照を渡す
println!("{}の長さは{}です", s1, len); // s1はまだ有効

fn calculate_length(s: &String) -> usize {
    s.len()
}

// ミュータブルな参照
let mut s = String::from("hello");
change(&mut s);

fn change(s: &mut String) {
    s.push_str(", world");
}

所有権システムの利点

  • メモリ安全性の保証
  • データ競合の防止
  • 自動的なリソース解放
  • 並行処理の安全性

エラーハンドリング

1. Result型の使用

use std::fs::File;
use std::io::Error;

fn read_file() -> Result<String, Error> {
    let file = File::open("hello.txt")?;
    // ファイルの内容を読み込む処理
    Ok(String::from("ファイルの内容"))
}

// エラーハンドリングの例
match read_file() {
    Ok(content) => println!("ファイルの内容: {}", content),
    Err(e) => println!("エラー: {}", e),
}

// ?演算子を使用した簡潔な記述
async fn process_file() -> Result<(), Error> {
    let content = read_file()?;
    println!("ファイルの内容: {}", content);
    Ok(())
}

2. カスタムエラー型の定義

use std::error::Error;
use std::fmt;

#[derive(Debug)]
enum MyError {
    IoError(std::io::Error),
    ParseError(String),
}

impl Error for MyError {}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::IoError(e) => write!(f, "IO error: {}", e),
            MyError::ParseError(s) => write!(f, "Parse error: {}", s),
        }
    }
}

並行処理

1. スレッドの基本

use std::thread;
use std::time::Duration;

// 基本的なスレッド生成
let handle = thread::spawn(|| {
    for i in 1..10 {
        println!("スレッドから数字 {}", i);
        thread::sleep(Duration::from_millis(1));
    }
});

handle.join().unwrap();

// メッセージパッシング
use std::sync::mpsc;

let (tx, rx) = mpsc::channel();

thread::spawn(move || {
    let val = String::from("hello");
    tx.send(val).unwrap();
});

let received = rx.recv().unwrap();
println!("受信: {}", received);

2. 排他制御

use std::sync::{Arc, Mutex};
use std::thread;

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

WebAssembly開発

1. 環境設定

# wasm-packのインストール
cargo install wasm-pack

# プロジェクトの作成
cargo new --lib wasm-project
cd wasm-project

# Cargo.tomlの設定
[package]
name = "wasm-project"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

2. WebAssemblyモジュールの実装

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Calculator {
    value: i32,
}

#[wasm_bindgen]
impl Calculator {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Calculator {
        Calculator { value: 0 }
    }

    pub fn add(&mut self, x: i32) {
        self.value += x;
    }

    pub fn get_value(&self) -> i32 {
        self.value
    }
}

// JavaScriptからの使用例
// const calc = new Calculator();
// calc.add(5);
// console.log(calc.get_value());

Webバックエンド開発

1. Actixフレームワークの使用

use actix_web::{web, App, HttpResponse, HttpServer};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    name: String,
    email: String,
}

async fn create_user(user: web::Json<User>) -> HttpResponse {
    // ユーザー作成のロジック
    HttpResponse::Ok().json(user.0)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/users", web::post().to(create_user))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

2. データベース連携

use sqlx::postgres::PgPool;

#[derive(sqlx::FromRow)]
struct User {
    id: i32,
    name: String,
    email: String,
}

async fn get_user(pool: &PgPool, user_id: i32) -> Result<User, sqlx::Error> {
    sqlx::query_as!(
        User,
        "SELECT id, name, email FROM users WHERE id = $1",
        user_id
    )
    .fetch_one(pool)
    .await
}

ベストプラクティス

コーディング規約

  • 命名規則の遵守
  • 適切なエラーハンドリング
  • ドキュメンテーションの充実
  • テストの作成

パフォーマンス最適化

  • 適切なデータ構造の選択
  • メモリアロケーションの最小化
  • 並行処理の活用
  • ゼロコストアブストラクションの活用

よくある間違い

  • 不適切な所有権管理
  • 過度な可変性の使用
  • 非効率なクローンの使用
  • 不要なボックス化