はじめに
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
}
ベストプラクティス
コーディング規約
- 命名規則の遵守
- 適切なエラーハンドリング
- ドキュメンテーションの充実
- テストの作成
パフォーマンス最適化
- 適切なデータ構造の選択
- メモリアロケーションの最小化
- 並行処理の活用
- ゼロコストアブストラクションの活用
よくある間違い
- 不適切な所有権管理
- 過度な可変性の使用
- 非効率なクローンの使用
- 不要なボックス化