Rust
Rust 完全ガイド — 安全性とパフォーマンスを両立するシステムプログラミング言語
1. Rust とは何か
1.1 概要
Rust は、安全性(Safety)、速度(Speed)、並行性(Concurrency) の3つを同時に実現することを目指して設計されたシステムプログラミング言語である。C/C++ と同等のパフォーマンスを持ちながら、コンパイル時にメモリ安全性を保証するという、従来のシステム言語では困難だった課題を解決している。
Rust の最大の特徴は 所有権システム(Ownership System) であり、ガベージコレクタ(GC)なしにメモリ安全性を実現する。これにより、ランタイムオーバーヘッドなしで、ダングリングポインタ、二重解放、データ競合といった問題をコンパイル時に検出・防止できる。
fn main() {
println!("Hello, Rust!");
// 変数はデフォルトで不変(immutable)
let x = 5;
// x = 6; // コンパイルエラー!
// mut キーワードで可変にする
let mut y = 10;
y += 5;
println!("y = {}", y); // y = 15
}
1.2 歴史
Rust の歴史は、Mozilla のエンジニアである Graydon Hoare が2006年に個人プロジェクトとして開始したことに始まる。
| 年 | 出来事 |
|---|---|
| 2006 | Graydon Hoare が個人プロジェクトとして Rust の開発を開始 |
| 2009 | Mozilla が Rust の開発を公式にスポンサー |
| 2010 | Rust が初めて公に発表される |
| 2012 | 最初のアルファ版リリース(0.1) |
| 2015年5月15日 | Rust 1.0 安定版リリース — 後方互換性の保証開始 |
| 2018 | Rust 2018 Edition リリース(async/await の基礎、NLL) |
| 2020 | Mozilla の大規模レイオフ、Rust チームに影響 |
| 2021年2月 | Rust Foundation 設立(AWS, Google, Huawei, Microsoft, Mozilla が創設メンバー) |
| 2021 | Rust 2021 Edition リリース |
| 2022 | Linux カーネルが Rust を公式にサポート(6.1 から) |
| 2023 | Windows カーネルでの Rust 採用が発表 |
| 2024 | Rust 2024 Edition リリース |
1.3 設計哲学
1. ゼロコスト抽象化(Zero-Cost Abstractions) — 高レベルの抽象化を使用しても、手書きの低レベルコードと同等のパフォーマンスを発揮する。
let sum: i32 = (1..=100).filter(|x| x % 2 == 0).map(|x| x * x).sum();
2. メモリ安全性の保証(Memory Safety without GC) — 所有権システムにより、GC なしにメモリ安全性を保証する。
3. データ競合の防止(Fearless Concurrency) — 型システムと所有権システムにより、データ競合をコンパイル時に検出する。
4. 明示性(Explicitness) — 暗黙の動作を最小限にし、プログラマが意図を明確に表現することを促す。
1.4 Mozilla からの独立と Rust Foundation
2021年2月8日、Rust Foundation が正式に設立された。創設メンバーは AWS、Google、Huawei、Microsoft、Mozilla の5社である。Rust Foundation は、Rust 言語の開発、エコシステムの維持、コミュニティの支援を担う非営利団体として機能している。
1.5 Rust が選ばれる理由
| 企業/プロジェクト | 用途 |
|---|---|
| Linux カーネル | デバイスドライバ、カーネルモジュール |
| Android | OS コンポーネント、Bluetooth スタック |
| AWS | Firecracker(マイクロVM)、Bottlerocket(コンテナOS) |
| Cloudflare | Pingora(HTTP プロキシ) |
| Discord | メッセージング基盤の高性能化 |
| Dropbox | ファイル同期エンジン |
| Meta | ソースコード管理(Mononoke) |
| Microsoft | Windows カーネルコンポーネント |
2. 所有権システム(Ownership System)
Rust の最も革新的な特徴は 所有権システム である。コンパイル時にメモリの安全性を保証する仕組みであり、GC のランタイムコストなしにダングリングポインタ、二重解放、メモリリークを防止する。
2.1 所有権の基本ルール
- Rust の各値は、所有者(owner)と呼ばれる変数を持つ
- 各値の所有者は同時に1つだけ
- 所有者がスコープを抜けると、値は破棄(drop)される
fn main() {
let s = String::from("hello"); // ルール1: s が所有者
let s2 = s; // ルール2: 所有権が s2 に移動(move)
// println!("{}", s); // コンパイルエラー!s はもう無効
println!("{}", s2); // OK
} // ルール3: s2 がスコープを抜け、メモリが解放される
2.2 ムーブセマンティクス(Move Semantics)
fn take_ownership(s: String) {
println!("受け取った文字列: {}", s);
}
fn main() {
let my_string = String::from("hello");
take_ownership(my_string);
// println!("{}", my_string); // コンパイルエラー!所有権はすでに移動済み
// Copy トレイトを実装する型はコピーされる
let x = 42;
let y = x; // コピーが発生
println!("x = {}, y = {}", x, y); // 両方使用可能
}
2.3 参照と借用(References and Borrowing)
fn calculate_length(s: &String) -> usize { s.len() }
fn append_world(s: &mut String) { s.push_str(", world!"); }
fn main() {
let mut my_string = String::from("hello");
let len = calculate_length(&my_string); // 不変参照
println!("'{}' の長さは {} です", my_string, len);
append_world(&mut my_string); // 可変参照
println!("{}", my_string);
}
2.4 借用のルール
- 任意の時点で、1つの可変参照 OR 任意の数の不変参照のいずれか一方のみ存在できる
- 参照は常に有効でなければならない(ダングリング参照の禁止)
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1, r2 はここ以降使用されないため、可変参照が可能
let r3 = &mut s;
r3.push_str(", world!");
println!("{}", r3);
}
2.5 ライフタイム(Lifetimes)
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 構造体のライフタイム
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("お知らせ: {}", announcement);
self.part
}
}
2.6 ライフタイムの省略規則
コンパイラは3つの規則でライフタイムを自動推論する:
- 各参照パラメータに個別のライフタイムが割り当てられる
- 入力ライフタイムが1つだけの場合、全出力に適用
- メソッドの場合、
selfのライフタイムが全出力に適用
2.7 'static ライフタイム
let s: &'static str = "この文字列はプログラム全体で有効";
fn make_static_string() -> &'static str {
let s = String::from("leaked string");
Box::leak(s.into_boxed_str())
}
3. 型システム
3.1 基本型(Primitive Types)
fn main() {
let i32_val: i32 = 42; // 32ビット符号付き(デフォルト)
let f64_val: f64 = 2.71828; // 64ビット浮動小数点(デフォルト)
let is_active: bool = true;
let c: char = '🦀'; // Unicode スカラー値、4バイト
let unit: () = (); // ユニット型
let tuple: (i32, f64, &str) = (42, 3.14, "hello");
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3];
}
3.2 構造体(Structs)
#[derive(Debug, Clone)]
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
impl User {
fn new(username: String, email: String) -> Self {
Self { username, email, sign_in_count: 0, active: true }
}
fn full_info(&self) -> String {
format!("{} <{}> (ログイン回数: {})", self.username, self.email, self.sign_in_count)
}
fn increment_sign_in(&mut self) { self.sign_in_count += 1; }
fn deactivate(self) -> User { User { active: false, ..self } }
}
struct Color(u8, u8, u8); // タプル構造体
struct Marker; // ユニット構造体
3.3 列挙型(Enums)
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
impl Message {
fn process(&self) {
match self {
Message::Quit => println!("終了"),
Message::Move { x, y } => println!("移動: ({}, {})", x, y),
Message::Write(text) => println!("書き込み: {}", text),
Message::ChangeColor(r, g, b) => println!("色変更: RGB({}, {}, {})", r, g, b),
}
}
}
fn find_user(id: u64) -> Option<String> {
if id == 1 { Some(String::from("taro")) } else { None }
}
3.4 ジェネリクス(Generics)
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] { if item > largest { largest = item; } }
largest
}
#[derive(Debug)]
struct Point<T> { x: T, y: T }
impl<T> Point<T> { fn x(&self) -> &T { &self.x } }
impl Point<f64> {
fn distance_from_origin(&self) -> f64 { (self.x.powi(2) + self.y.powi(2)).sqrt() }
}
3.5 トレイト(Traits)
trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("({}さんの記事をもっと読む...)", self.summarize_author())
}
}
struct Tweet { username: String, content: String }
impl Summary for Tweet {
fn summarize_author(&self) -> String { format!("@{}", self.username) }
}
fn notify(item: &impl Summary) { println!("速報! {}", item.summarize()); }
fn process_items<T, U>(t: &T, u: &U) -> String
where T: Summary + Clone, U: Summary + std::fmt::Debug,
{ format!("{} / {:?}", t.summarize(), u) }
fn create_summarizable() -> impl Summary {
Tweet { username: String::from("rustlang"), content: String::from("Rust!") }
}
3.6 関連型(Associated Types)
struct Counter { count: u32, max: u32 }
impl Counter { fn new(max: u32) -> Self { Counter { count: 0, max } } }
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max { self.count += 1; Some(self.count) } else { None }
}
}
3.7 トレイトオブジェクト(動的ディスパッチ)
trait Animal {
fn name(&self) -> &str;
fn sound(&self) -> &str;
fn info(&self) -> String { format!("{} は「{}」と鳴きます", self.name(), self.sound()) }
}
struct Dog { name: String }
struct Cat { name: String }
impl Animal for Dog { fn name(&self) -> &str { &self.name } fn sound(&self) -> &str { "ワン" } }
impl Animal for Cat { fn name(&self) -> &str { &self.name } fn sound(&self) -> &str { "ニャー" } }
fn get_animals() -> Vec<Box<dyn Animal>> {
vec![
Box::new(Dog { name: String::from("ポチ") }),
Box::new(Cat { name: String::from("タマ") }),
]
}
3.8 型エイリアスとニュータイプパターン
type Kilometers = i32;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
struct Wrapper(Vec<String>);
impl std::fmt::Display for Wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
4. エラーハンドリング
Rust は例外機構を持たず、Result<T, E> と Option<T> 型を使った明示的なエラーハンドリングを採用している。
4.1 panic! と Result 型
use std::fs::File;
use std::io::{self, Read};
fn read_file_content(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_file_content("config.txt") {
Ok(content) => println!("内容: {}", content),
Err(e) => eprintln!("エラー: {}", e),
}
let content = read_file_content("config.txt")
.unwrap_or_else(|_| String::from("デフォルト設定"));
}
4.2 ? 演算子の詳細
use std::num::ParseIntError;
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
let n = s.parse::<i32>()?;
Ok(n * 2)
}
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(ParseIntError),
}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
}
fn complex_operation(path: &str) -> Result<i32, AppError> {
let content = std::fs::read_to_string(path)?;
let number = content.trim().parse::<i32>()?;
Ok(number * 2)
}
4.3 カスタムエラー型
use std::fmt;
#[derive(Debug)]
enum DatabaseError {
ConnectionFailed(String),
QueryFailed { query: String, reason: String },
RecordNotFound(u64),
Timeout(std::time::Duration),
}
impl fmt::Display for DatabaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DatabaseError::ConnectionFailed(url) => write!(f, "接続失敗: {}", url),
DatabaseError::QueryFailed { query, reason } => write!(f, "クエリ '{}' 失敗: {}", query, reason),
DatabaseError::RecordNotFound(id) => write!(f, "ID {} が見つかりません", id),
DatabaseError::Timeout(d) => write!(f, "タイムアウト: {:?}", d),
}
}
}
impl std::error::Error for DatabaseError {}
4.4 thiserror と anyhow クレート
[dependencies]
thiserror = "2"
anyhow = "1"
use thiserror::Error;
#[derive(Error, Debug)]
enum ApiError {
#[error("HTTP リクエスト失敗: {0}")]
HttpError(#[from] reqwest::Error),
#[error("JSON パースエラー: {0}")]
JsonError(#[from] serde_json::Error),
#[error("認証エラー: ユーザー '{username}' は権限がありません")]
Unauthorized { username: String },
}
// anyhow: アプリケーション向け
use anyhow::{Context, Result, bail, ensure};
fn load_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.context(format!("設定ファイル '{}' の読み込みに失敗", path))?;
let config: Config = serde_json::from_str(&content)
.context("JSON パースに失敗")?;
ensure!(!config.name.is_empty(), "name は空にできません");
if config.port == 0 { bail!("ポート番号が無効です"); }
Ok(config)
}
4.5 Option 型の活用
fn main() {
let some_value: Option<i32> = Some(42);
let doubled = some_value.map(|x| x * 2); // Some(84)
let result = some_value.and_then(|x| if x > 0 { Some(x) } else { None });
let filtered = some_value.filter(|&x| x > 100); // None
let val = some_value.unwrap_or(0);
let zipped = Some(1).zip(Some("hello")); // Some((1, "hello"))
let flat = Some(Some(42)).flatten(); // Some(42)
let result: Result<i32, &str> = some_value.ok_or("値がありません");
}
5. パターンマッチング
5.1 match 式
#[derive(Debug)]
enum Coin { Penny, Nickel, Dime, Quarter(UsState) }
#[derive(Debug)]
enum UsState { Alabama, Alaska, California }
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => { println!("{:?} 州", state); 25 }
}
}
5.2 パターンの種類
fn pattern_examples() {
let x = 5;
match x {
1..=5 => println!("1から5"),
6..=10 => println!("6から10"),
_ => println!("範囲外"),
}
match x {
1 | 3 | 5 | 7 | 9 => println!("奇数"),
2 | 4 | 6 | 8 | 10 => println!("偶数"),
_ => println!("範囲外"),
}
struct Point { x: i32, y: i32 }
let point = Point { x: 0, y: 7 };
match point {
Point { x: 0, y } => println!("x軸上: y={}", y),
Point { x, y: 0 } => println!("y軸上: x={}", x),
Point { x, y } => println!("({}, {})", x, y),
}
// ガード条件
let num = Some(4);
match num {
Some(x) if x < 5 => println!("5未満: {}", x),
Some(x) => println!("5以上: {}", x),
None => println!("なし"),
}
// @ バインディング
match x {
n @ 1..=5 => println!("1〜5: {}", n),
n => println!("範囲外: {}", n),
}
}
5.3 if let, while let, let else
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max { println!("最大値: {}", max); }
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() { println!("{}", top); }
// let ... else(Rust 1.65+)
fn process_input(input: &str) -> Result<u32, String> {
let Ok(number) = input.parse::<u32>() else {
return Err(format!("'{}' は数値ではありません", input));
};
Ok(number * 2)
}
}
5.4 網羅性チェック
enum TrafficLight { Red, Yellow, Green }
fn action(light: TrafficLight) -> &'static str {
match light {
TrafficLight::Red => "止まれ",
TrafficLight::Yellow => "注意",
TrafficLight::Green => "進め",
}
}
6. コレクション
6.1 Vec
fn main() {
let mut v = Vec::new();
v.push(1); v.push(2); v.push(3);
let v2 = vec![1, 2, 3, 4, 5];
let third = v2.get(2); // Option<&T>
for val in &v2 { println!("{}", val); }
let mut numbers = vec![1, 2, 3];
for val in &mut numbers { *val *= 2; }
let v = vec![3, 1, 4, 1, 5, 9, 2, 6];
let mut sorted = v.clone();
sorted.sort();
sorted.dedup();
let mut evens = vec![1, 2, 3, 4, 5, 6];
evens.retain(|&x| x % 2 == 0); // [2, 4, 6]
}
6.2 HashMap<K, V>
use std::collections::HashMap;
fn main() {
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 100);
scores.entry(String::from("Bob")).or_insert(85);
// ワードカウント
let text = "hello world wonderful world";
let mut word_count: HashMap<&str, u32> = HashMap::new();
for word in text.split_whitespace() {
*word_count.entry(word).or_insert(0) += 1;
}
}
6.3 BTreeMap, HashSet, BTreeSet
use std::collections::{BTreeMap, HashSet, BTreeSet};
fn main() {
let mut btree: BTreeMap<&str, i32> = BTreeMap::new();
btree.insert("banana", 3);
btree.insert("apple", 5);
// キー順でイテレーション: apple, banana
let a: HashSet<i32> = [1, 2, 3, 4, 5].into();
let b: HashSet<i32> = [3, 4, 5, 6, 7].into();
let union: HashSet<_> = a.union(&b).collect();
let intersection: HashSet<_> = a.intersection(&b).collect();
let difference: HashSet<_> = a.difference(&b).collect();
let sorted_set: BTreeSet<i32> = [5, 3, 1, 4, 2].into(); // {1, 2, 3, 4, 5}
}
7. イテレータとクロージャ
7.1 クロージャ(Closures)
fn main() {
let add = |a, b| a + b;
println!("3 + 4 = {}", add(3, 4));
let name = String::from("Rust");
let greet = || println!("Hello, {}!", name); // Fn: 不変借用
greet();
let mut count = 0;
let mut increment = || { count += 1; count }; // FnMut: 可変借用
println!("{}", increment());
let name = String::from("World");
let consume = move || println!("Consumed: {}", name); // FnOnce: 所有権移動
consume();
fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y }
let add_five = make_adder(5);
println!("5 + 3 = {}", add_five(3));
}
7.2 イテレータ
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
let parsed: Vec<i32> = vec!["1", "abc", "3"]
.into_iter().filter_map(|s| s.parse().ok()).collect();
let words: Vec<&str> = vec!["hello world", "foo bar"]
.iter().flat_map(|s| s.split_whitespace()).collect();
let (evens, odds): (Vec<_>, Vec<_>) = numbers.iter().partition(|&&x| x % 2 == 0);
println!("sum: {}", numbers.iter().sum::<i32>());
println!("any even: {}", numbers.iter().any(|&x| x % 2 == 0));
println!("find >5: {:?}", numbers.iter().find(|&&x| x > 5));
}
7.3 カスタムイテレータ
struct Fibonacci { a: u64, b: u64 }
impl Fibonacci { fn new() -> Self { Fibonacci { a: 0, b: 1 } } }
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let result = self.a;
let new_b = self.a + self.b;
self.a = self.b;
self.b = new_b;
Some(result)
}
}
fn main() {
let fibs: Vec<u64> = Fibonacci::new().take(10).collect();
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
}
8. スマートポインタ
8.1 Box
fn main() {
let b = Box::new(5);
#[derive(Debug)]
enum List { Cons(i32, Box<List>), Nil }
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
}
8.2 Rc(参照カウント)
use std::rc::Rc;
fn main() {
let shared = Rc::new(String::from("共有データ"));
let clone1 = Rc::clone(&shared);
println!("参照カウント: {}", Rc::strong_count(&shared)); // 2
}
8.3 RefCell(内部可変性)
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4);
println!("{:?}", data.borrow());
// Rc<RefCell<T>> パターン: 共有かつ可変
let shared = Rc::new(RefCell::new(String::from("hello")));
Rc::clone(&shared).borrow_mut().push_str(" world");
}
8.4 Arc と Mutex
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
*counter.lock().unwrap() += 1;
}));
}
for handle in handles { handle.join().unwrap(); }
println!("結果: {}", *counter.lock().unwrap()); // 10
}
8.5 Cow(Clone on Write)
use std::borrow::Cow;
fn remove_whitespace(input: &str) -> Cow<str> {
if input.contains(' ') { Cow::Owned(input.replace(' ', "")) }
else { Cow::Borrowed(input) }
}
9. 並行処理
9.1 スレッド
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..=5 { println!("スレッド: {}", i); thread::sleep(Duration::from_millis(100)); }
42
});
let result = handle.join().unwrap(); // 42
// スコープ付きスレッド(Rust 1.63+)
let mut data = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| println!("読み取り: {:?}", &data));
});
data.push(4);
}
9.2 チャネル
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for msg in vec!["hello", "from", "thread"] { tx.send(msg).unwrap(); }
});
for received in rx { println!("受信: {}", received); }
}
9.3 async/await と tokio
[dependencies]
tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};
async fn fetch_data(id: u32) -> String {
sleep(Duration::from_millis(100)).await;
format!("データ #{}", id)
}
async fn process_all() {
let (d1, d2, d3) = tokio::join!(fetch_data(1), fetch_data(2), fetch_data(3));
println!("{}, {}, {}", d1, d2, d3);
tokio::select! {
val = fetch_data(1) => println!("最初に完了: {}", val),
val = fetch_data(2) => println!("最初に完了: {}", val),
}
}
#[tokio::main]
async fn main() { process_all().await; }
10. unsafe Rust
10.1 unsafe で可能になる操作
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1: {}", *r1);
*r2 = 10;
}
static mut COUNTER: u32 = 0;
unsafe { COUNTER += 1; }
}
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
let ptr = values.as_mut_ptr();
assert!(mid <= len);
unsafe {
(std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid))
}
}
10.2 FFI(Foreign Function Interface)
extern "C" { fn abs(input: i32) -> i32; }
fn main() { unsafe { println!("abs(-3) = {}", abs(-3)); } }
#[no_mangle]
pub extern "C" fn rust_function(x: i32) -> i32 { x * 2 }
10.3 unsafe トレイト
unsafe trait Trustworthy { fn validate(&self) -> bool; }
struct SafeData { value: i32 }
unsafe impl Trustworthy for SafeData {
fn validate(&self) -> bool { self.value >= 0 }
}
// Send, Sync は unsafe トレイト。ほとんどの型は自動実装。
// Rc<T> は Send でも Sync でもない(Arc<T> を使用)。
11. マクロシステム
11.1 宣言的マクロ
macro_rules! vec_of_strings {
($($element:expr),* $(,)?) => {{
let mut v = Vec::new();
$(v.push(String::from($element));)*
v
}};
}
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = std::collections::HashMap::new();
$(map.insert($key, $value);)*
map
}};
}
fn main() {
let names = vec_of_strings!["Alice", "Bob"];
let scores = hashmap! { "Alice" => 100, "Bob" => 85 };
}
11.2 手続き的マクロ
[lib]
proc-macro = true
[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let expanded = quote! {
impl HelloMacro for #name {
fn hello_macro() { println!("Hello, {}!", stringify!(#name)); }
}
};
TokenStream::from(expanded)
}
11.3 よく使われる derive マクロ
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Point { x: i32, y: i32 }
// serde の derive
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
struct Config {
#[serde(rename = "serverName")]
server_name: String,
#[serde(default = "default_port")]
port: u16,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
}
fn default_port() -> u16 { 8080 }
12. Cargo の詳細設定
12.1 Cargo.toml
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
license = "MIT OR Apache-2.0"
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
my-lib = { git = "https://github.com/user/my-lib", branch = "main" }
shared = { path = "../shared" }
fancy-logging = { version = "0.1", optional = true }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser"] }
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
mockall = "0.12"
tempfile = "3"
[build-dependencies]
cc = "1"
[features]
default = ["json", "logging"]
json = ["dep:serde", "dep:serde_json"]
logging = ["dep:fancy-logging"]
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true
12.2 ワークスペース
[workspace]
resolver = "2"
members = ["crates/core", "crates/api", "crates/cli"]
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
thiserror = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
# crates/core/Cargo.toml
[package]
name = "my-core"
version.workspace = true
edition.workspace = true
[dependencies]
serde.workspace = true
thiserror.workspace = true
12.3 ビルドスクリプト
// build.rs
fn main() {
println!("cargo:rustc-env=GIT_HASH={}", get_git_hash());
println!("cargo:rerun-if-changed=src/native/helper.c");
cc::Build::new().file("src/native/helper.c").compile("helper");
}
fn get_git_hash() -> String {
std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output().ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_else(|| "unknown".to_string())
.trim().to_string()
}
12.4 主要な Cargo コマンド
cargo new my-project # バイナリプロジェクト作成
cargo build --release # リリースビルド
cargo run -- arg1 arg2 # 引数付き実行
cargo test # テスト実行
cargo check # コンパイルチェック
cargo clippy # リント
cargo fmt # フォーマット
cargo doc --open # ドキュメント生成
cargo tree # 依存関係ツリー
cargo audit # セキュリティ脆弱性チェック
13. テスト
13.1 単体テスト
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err("ゼロ除算".to_string()) } else { Ok(a / b) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() { assert_eq!(add(2, 3), 5); }
#[test]
fn test_divide_by_zero() {
assert!(divide(10.0, 0.0).is_err());
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_panic() { let v = vec![1, 2, 3]; let _ = v[10]; }
#[test]
fn test_result() -> Result<(), String> {
let result = divide(10.0, 2.0)?;
assert_eq!(result, 5.0);
Ok(())
}
#[test]
#[ignore = "長時間かかるテスト"]
fn expensive_test() { std::thread::sleep(std::time::Duration::from_secs(10)); }
}
13.2 統合テスト
// tests/integration_test.rs
use my_project::{add, Calculator};
#[test]
fn test_full_workflow() {
let mut calc = Calculator::new();
assert_eq!(calc.add(10.0, 20.0), 30.0);
assert_eq!(calc.history().len(), 1);
}
13.3 ドキュメントテスト
/// 2つの数値を加算します。
/// ```
/// assert_eq!(my_project::add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
13.4 プロパティベーステスト
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn test_double_reverse(ref v in prop::collection::vec(any::<i32>(), 0..100)) {
let reversed_twice: Vec<_> = v.iter().rev().rev().cloned().collect();
prop_assert_eq!(v, &reversed_twice);
}
}
}
13.5 モック
use mockall::{automock, predicate::*};
#[automock]
trait Database {
fn get_user(&self, id: u64) -> Option<String>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock() {
let mut mock = MockDatabase::new();
mock.expect_get_user().with(eq(1)).returning(|_| Some("Alice".to_string()));
assert_eq!(mock.get_user(1), Some("Alice".to_string()));
}
}
14. クレートエコシステム
| カテゴリ | クレート | 説明 |
|---|---|---|
| シリアライズ | serde + serde_json | デファクトスタンダードのシリアライズフレームワーク |
| 非同期ランタイム | tokio | 最も広く使われる非同期ランタイム |
| Web フレームワーク | axum | tokio ベースの Web フレームワーク |
| Web フレームワーク | actix-web | 高性能 Web フレームワーク |
| HTTP クライアント | reqwest | 高レベル HTTP クライアント |
| CLI | clap | コマンドライン引数パーサー |
| エラー(ライブラリ) | thiserror | カスタムエラー型の derive マクロ |
| エラー(アプリ) | anyhow | 柔軟なエラーハンドリング |
| ログ | tracing | 構造化ログ・分散トレーシング |
| データベース | sqlx | コンパイル時 SQL チェック付き非同期 DB ドライバ |
| ORM | diesel / sea-orm | 型安全な ORM |
| 正規表現 | regex | 高性能正規表現エンジン |
| 日付・時刻 | chrono / time | 日付・時刻ライブラリ |
14.1 serde の活用
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", content = "payload")]
enum Event {
#[serde(rename = "user_created")]
UserCreated { id: u64, name: String },
#[serde(rename = "order_placed")]
OrderPlaced { order_id: String, amount: f64 },
}
14.2 clap による CLI
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "filetool", about = "ファイル操作 CLI")]
struct Cli {
#[arg(short, long)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Search { pattern: String, #[arg(short, long, default_value = ".")] directory: String },
Stats { path: String },
}
15. Web アプリケーション開発
15.1 Axum
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
tower-http = { version = "0.5", features = ["cors", "trace"] }
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres"] }
use axum::{extract::{Json, Path, State}, http::StatusCode, response::IntoResponse, routing::{get, post}, Router};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
struct AppState { db: sqlx::PgPool }
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct User { id: uuid::Uuid, name: String, email: String }
#[derive(Deserialize)]
struct CreateUserRequest { name: String, email: String }
async fn list_users(State(state): State<AppState>) -> impl IntoResponse {
let users = sqlx::query_as::<_, User>("SELECT * FROM users")
.fetch_all(&state.db).await.unwrap();
Json(users)
}
async fn create_user(State(state): State<AppState>, Json(payload): Json<CreateUserRequest>) -> impl IntoResponse {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) RETURNING *")
.bind(uuid::Uuid::new_v4()).bind(&payload.name).bind(&payload.email)
.fetch_one(&state.db).await.unwrap();
(StatusCode::CREATED, Json(user))
}
fn create_router(state: AppState) -> Router {
Router::new()
.route("/api/users", get(list_users).post(create_user))
.with_state(state)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let db = sqlx::PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let app = create_router(AppState { db });
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}
15.2 Actix-web
use actix_web::{web, App, HttpServer, HttpResponse};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Item { id: u64, name: String }
async fn get_items() -> HttpResponse {
HttpResponse::Ok().json(vec![Item { id: 1, name: "Widget".into() }])
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/items", web::get().to(get_items)))
.bind("0.0.0.0:8080")?.run().await
}
16. 組み込み開発
16.1 no_std 環境
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! { loop {} }
#[no_mangle]
pub extern "C" fn _start() -> ! { loop {} }
16.2 embedded-hal
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
embedded-hal = "1.0"
stm32f4xx-hal = { version = "0.21", features = ["stm32f411"] }
panic-halt = "0.2"
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
let gpioa = dp.GPIOA.split();
let mut led = gpioa.pa5.into_push_pull_output();
let cp = cortex_m::Peripherals::take().unwrap();
let mut delay = cp.SYST.delay(&clocks);
loop { led.set_high(); delay.delay_ms(500u32); led.set_low(); delay.delay_ms(500u32); }
}
17. WebAssembly
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["console", "Document", "Element", "Window"] }
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String { format!("Hello, {}! (from Rust/WASM)", name) }
#[wasm_bindgen]
pub struct Calculator { history: Vec<f64> }
#[wasm_bindgen]
impl Calculator {
#[wasm_bindgen(constructor)]
pub fn new() -> Self { Calculator { history: vec![] } }
pub fn add(&mut self, a: f64, b: f64) -> f64 {
let r = a + b; self.history.push(r); r
}
}
wasm-pack build --target web
import init, { greet, Calculator } from './pkg/my_wasm_lib.js';
async function main() {
await init();
console.log(greet("World"));
const calc = new Calculator();
console.log(calc.add(1, 2)); // 3
}
main();
18. メモリレイアウトとパフォーマンス最適化
18.1 型のメモリレイアウト
use std::mem;
fn main() {
println!("i32: {} bytes", mem::size_of::<i32>()); // 4
println!("&str: {} bytes", mem::size_of::<&str>()); // 16
println!("String: {} bytes", mem::size_of::<String>()); // 24
println!("Vec<i32>: {} bytes", mem::size_of::<Vec<i32>>()); // 24
// Option のニッチ最適化
println!("Option<&i32>: {} bytes", mem::size_of::<Option<&i32>>()); // 8(追加コストなし!)
println!("Option<Box<i32>>: {} bytes", mem::size_of::<Option<Box<i32>>>()); // 8
// 構造体のパディング
struct A { a: u8, b: u64, c: u8 } // 24 bytes
struct B { b: u64, a: u8, c: u8 } // 16 bytes(フィールド順最適化)
}
18.2 パフォーマンス最適化テクニック
// 事前にキャパシティを確保
let mut s = String::with_capacity(4000);
let mut v = Vec::with_capacity(1000);
// Cow で不要なクローンを回避
use std::borrow::Cow;
fn process_name(name: &str) -> Cow<str> {
if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) }
else { Cow::Borrowed(name) }
}
18.3 ベンチマーク(criterion)
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_benchmark"
harness = false
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
19. Rust のコンパイラアーキテクチャ
19.1 コンパイルパイプライン
ソースコード (.rs) → 字句解析 → AST → マクロ展開 → HIR → 借用チェック
→ MIR → MIR最適化 → 単相化 → LLVM IR → LLVM バックエンド → ネイティブコード → リンキング
19.2 MIR
cargo rustc -- --emit=mir # MIR の出力
cargo rustc -- --emit=llvm-ir # LLVM IR の出力
cargo rustc -- --emit=asm # アセンブリ出力
19.3 クロスコンパイル
rustup target add aarch64-unknown-linux-gnu
cargo build --target aarch64-unknown-linux-gnu
20. ツールチェーン
20.1 rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update
rustup default stable
rustup target add wasm32-unknown-unknown
rustup component add clippy rustfmt rust-analyzer miri
# rust-toolchain.toml
[toolchain]
channel = "1.78.0"
components = ["rustfmt", "clippy", "rust-analyzer"]
targets = ["wasm32-unknown-unknown"]
20.2 clippy
cargo clippy --all-targets --all-features
cargo clippy --fix
#![warn(clippy::pedantic)]
#![deny(clippy::unwrap_used)]
20.3 rustfmt
# rustfmt.toml
max_width = 100
tab_spaces = 4
edition = "2021"
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
20.4 miri
rustup +nightly component add miri
cargo +nightly miri test
21. CI/CD 設定
21.1 GitHub Actions
name: CI
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-Dwarnings"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo check --all-targets --all-features
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-features
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { components: rustfmt }
- run: cargo fmt --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { components: clippy }
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets -- -D warnings
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [test, fmt, clippy]
strategy:
matrix:
include:
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
- { target: x86_64-apple-darwin, os: macos-latest }
- { target: aarch64-apple-darwin, os: macos-latest }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { targets: "${{ matrix.target }}" }
- run: cargo build --release --target ${{ matrix.target }}
21.2 Docker マルチステージビルド
FROM rust:1.78-slim as builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src
COPY src/ src/
RUN touch src/main.rs && cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/my-app /usr/local/bin/
EXPOSE 3000
CMD ["my-app"]
22. 実践例
22.1 CLI ツール
[dependencies]
clap = { version = "4", features = ["derive"] }
csv = "1"
serde = { version = "1", features = ["derive"] }
anyhow = "1"
comfy-table = "7"
use anyhow::{Context, Result};
use clap::Parser;
use comfy_table::Table;
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "csv-analyzer")]
struct Args {
file: PathBuf,
#[arg(short, long, default_value_t = 10)]
limit: usize,
#[arg(short, long)]
stats: bool,
}
fn main() -> Result<()> {
let args = Args::parse();
let mut reader = csv::Reader::from_path(&args.file)
.context("ファイルを開けません")?;
let headers: Vec<String> = reader.headers()?.iter().map(|h| h.to_string()).collect();
let records: Vec<csv::StringRecord> = reader.records().collect::<Result<_, _>>()?;
let mut table = Table::new();
table.set_header(&headers);
for record in records.iter().take(args.limit) {
table.add_row(record.iter().collect::<Vec<_>>());
}
println!("{}", table);
Ok(())
}
22.2 ライブラリ
use parking_lot::Mutex;
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub struct RateLimiter {
max_requests: usize,
window: Duration,
state: Mutex<HashMap<String, Vec<Instant>>>,
}
impl RateLimiter {
pub fn new(max_requests: usize, window: Duration) -> Self {
RateLimiter { max_requests, window, state: Mutex::new(HashMap::new()) }
}
pub fn try_acquire(&self, key: &str) -> bool {
let now = Instant::now();
let mut state = self.state.lock();
let timestamps = state.entry(key.to_string()).or_default();
timestamps.retain(|&t| now.duration_since(t) < self.window);
if timestamps.len() < self.max_requests { timestamps.push(now); true } else { false }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_limiting() {
let limiter = RateLimiter::new(3, Duration::from_secs(60));
assert!(limiter.try_acquire("user1"));
assert!(limiter.try_acquire("user1"));
assert!(limiter.try_acquire("user1"));
assert!(!limiter.try_acquire("user1")); // 4回目は拒否
}
}
22.3 Todo API サーバー
use axum::{extract::{Json, Path, State}, http::StatusCode, response::IntoResponse, routing::{get, post, put, delete}, Router};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Todo { id: String, title: String, completed: bool }
type Db = Arc<RwLock<HashMap<String, Todo>>>;
async fn list_todos(State(db): State<Db>) -> impl IntoResponse {
Json(db.read().await.values().cloned().collect::<Vec<_>>())
}
async fn create_todo(State(db): State<Db>, Json(input): Json<serde_json::Value>) -> impl IntoResponse {
let todo = Todo {
id: uuid::Uuid::new_v4().to_string(),
title: input["title"].as_str().unwrap_or("").to_string(),
completed: false,
};
db.write().await.insert(todo.id.clone(), todo.clone());
(StatusCode::CREATED, Json(todo))
}
#[tokio::main]
async fn main() {
let db: Db = Arc::new(RwLock::new(HashMap::new()));
let app = Router::new()
.route("/todos", get(list_todos).post(create_todo))
.with_state(db);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
23. まとめ
Rust は、メモリ安全性とパフォーマンスを両立する革新的なプログラミング言語である。所有権システム、強力な型システム、ゼロコスト抽象化により、安全で効率的なソフトウェアを構築できる。
Rust の強み
- コンパイル時のメモリ安全性 — GC なしでダングリングポインタ、二重解放、データ競合を防止
- ゼロコスト抽象化 — 高レベルな記述でも低レベルと同等のパフォーマンス
- 恐れなき並行性 — 型システムがデータ競合を防止
- 豊富なエコシステム — Cargo と crates.io による優れたパッケージ管理
- クロスプラットフォーム — 組み込みから Web まで幅広い対応
- 優れたツールチェーン — rustfmt、clippy、rust-analyzer による開発体験
学習の指針
- まず所有権と借用を理解する(Rust の根幹)
ResultとOptionを使ったエラーハンドリングに慣れる- トレイトとジェネリクスで抽象化を学ぶ
- イテレータとクロージャで関数型プログラミングの手法を習得
- async/await で非同期プログラミングに進む
- 実際のプロジェクト(CLI ツール、Web API など)で実践
Rust は学習曲線が急と言われるが、コンパイラのエラーメッセージが非常に親切であり、「コンパイルが通れば正しく動く」という高い信頼性を提供する。初期の学習コストを超えれば、安全で高性能なソフトウェアを効率的に開発できる強力なツールとなる。