Rust チートシート
Rust 1.98・Edition 2024をベースにしたチートシートです。
インストール・更新
Section titled “インストール・更新”Rustのツールチェーンは、公式の rustup で管理する方法が推奨されています。macOS、Linux、WSLなどのUnix系環境では、公式サイトに掲載されている次のコマンドでインストーラを起動します。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shWindowsでは公式インストールページから rustup-init.exe を取得して実行します。環境によってはVisual StudioのC++ Build Toolsも必要です。
インストール後はターミナルを開き直し、バージョンを確認します。
rustc --versioncargo --versionrustup --version安定版ツールチェーンの更新と、フォーマッタ・linterの追加は次のように行います。
rustup update stablerustup component add rustfmt clippy既存プロジェクトに rust-toolchain.toml や rust-toolchain がある場合は、その指定を優先します。ライブラリでは Cargo.toml の rust-version でサポートする最小Rustバージョン(MSRV)を示せます。
fn main() { println!("Hello, Rust 1.98!");}- 実行可能バイナリのエントリポイントは
main関数 - 文は通常
;で終える println!のように!が付くものはマクロ
| 構文 | 用途 |
|---|---|
// ... |
行末までの通常コメント |
/* ... */ |
複数行にまたがる通常コメント。入れ子にできる |
/// ..., /** ... */ |
直後の項目を説明する外部ドキュメントコメント |
//! ..., /*! ... */ |
crateやmodule自身を説明する内部ドキュメントコメント |
// 行コメントlet value = 42;
/* ブロックコメントは 複数行に記述できる */assert_eq!(value, 42);ドキュメントコメントはMarkdownとして処理され、cargo doc で生成されるAPIドキュメントに反映されます。コードブロックは通常 cargo test でドキュメントテストとして実行されます。
mod math { //! 基本的な計算機能を提供します。
/// 2つの整数を加算します。 /// /// # Examples /// /// ``` /// assert_eq!(2 + 3, 5); /// ``` pub fn add(left: i32, right: i32) -> i32 { left + right }}
assert_eq!(math::add(2, 3), 5);Cargoでプロジェクトを作成・実行します。
cargo new hello-rustcd hello-rustcargo runcargo new で作成したプロジェクトは、Cargo.toml に edition = "2024" が設定されます。
Rustのキーワードは、常に特別な意味を持つ厳密キーワード、将来のために確保された予約キーワード、特定の文脈だけで意味を持つ弱いキーワードに分類されます。
厳密キーワード
Section titled “厳密キーワード”| キーワード | 主な用途 |
|---|---|
_ |
パターンで値を無視する、または型・値などを推論させるプレースホルダー |
as |
プリミティブ型のキャスト、useでの別名、完全修飾構文 |
async |
Futureを返す非同期関数・ブロック・クロージャを定義 |
await |
Futureの完了まで現在の非同期処理を一時停止 |
break |
ループを終了し、必要に応じて値を返す |
const |
コンパイル時定数、const関数・ブロック・ジェネリクスなどを指定 |
continue |
現在のループ反復の残りを飛ばして次へ進む |
crate |
現在のcrate、またはcrateルートを参照 |
dyn |
実行時に動的ディスパッチするトレイトオブジェクトを表す |
else |
if・if letの条件が成立しない場合の分岐 |
enum |
複数のバリアントを持つ列挙型を定義 |
extern |
外部ABI、外部関数・static、外部crateを扱う |
false |
bool型の偽リテラル |
fn |
関数または関数ポインタ型を定義 |
for |
イテレータによるループ、または対象型へのトレイト実装を記述 |
if |
条件に応じて式を分岐 |
impl |
型のメソッドやトレイト実装を定義 |
in |
forループで反復対象を指定 |
let |
パターンへ値を束縛して変数を宣言 |
loop |
明示的に終了するまで繰り返すループを作成 |
match |
パターンマッチによって網羅的に分岐 |
mod |
moduleを宣言・定義 |
move |
クロージャなどへキャプチャした値を移動 |
mut |
変数、参照、パターンなどを可変として指定 |
pub |
項目を指定した範囲へ公開 |
ref |
パターン内で値を移動せず参照として束縛 |
return |
関数から値を早期に返す |
self |
メソッドのレシーバー、または現在のmoduleを参照 |
Self |
現在定義・実装している型自身を参照 |
static |
固定された格納場所を持つstatic項目を定義 |
struct |
名前付き・タプル形式・ユニット形式の構造体を定義 |
super |
親moduleを参照 |
trait |
型が共有できる振る舞いを定義 |
true |
bool型の真リテラル |
type |
型エイリアスまたは関連型を定義 |
unsafe |
コンパイラだけでは安全性を保証できない操作や契約を明示 |
use |
パスを現在のスコープへ導入、または項目を再公開 |
where |
ジェネリクスやライフタイムの境界条件を記述 |
while |
条件が成立している間ループを繰り返す |
予約キーワード
Section titled “予約キーワード”abstract, become, box, do, final, gen, macro, override, priv, try, typeof, unsized, virtual, yield
これらは将来の言語拡張のために予約されています。Rust 1.98ではキーワードとしての機能を持たず、通常の識別子にも使用できません。typeof も予約されていますが、型を取得する演算子としては実装されていません。
弱いキーワード
Section titled “弱いキーワード”| キーワード | 意味を持つ文脈 |
|---|---|
'static |
プログラム全体にわたって有効なライフタイム |
macro_rules |
宣言的マクロを定義 |
raw |
&raw const・&raw mutによる生ポインタの作成 |
safe |
unsafe externブロック内で、安全に呼び出せる関数・staticを宣言 |
union |
複数フィールドがストレージを共有する共用体を定義 |
識別子にキーワードと同じ名前が必要な場合は、利用可能な文脈に限り r#type のようなraw identifierを使えます。
変数・定数・基本型
Section titled “変数・定数・基本型”Rustの変数はデフォルトで不変です。値を変更する場合は mut を付けます。
let name = "Ferris"; // 型推論により &strlet age: u32 = 10; // 型を明示let mut count = 0; // 可変変数count += 1;
// パターンで分割代入let (x, y) = (10, 20);
// 使用前に必ず初期化すれば、宣言時の初期値は省略できるlet status: String;status = String::from("ready");println!("{status}");シャドーイング
Section titled “シャドーイング”同じ名前を let で再宣言できます。mut と異なり、型も変更できます。
let spaces = " ";let spaces = spaces.len(); // &str から usize に変更定数・static
Section titled “定数・static”const MAX_RETRIES: u32 = 3;static APPLICATION_NAME: &str = "example";constはコンパイル時に評価される値。型の明示が必要staticはプログラム全体で固定されたメモリアドレスを持つ値- 可変な
static mutへのアクセスはunsafeが必要で、通常は同期プリミティブなどを検討する
複数スレッドから安全に更新する単純なカウンターには、アトミック型を利用できます。
use std::sync::atomic::{AtomicU64, Ordering};
static REQUEST_COUNT: AtomicU64 = AtomicU64::new(0);
fn next_request_count() -> u64 { REQUEST_COUNT.fetch_add(1, Ordering::Relaxed) + 1}
assert_eq!(next_request_count(), 1);assert_eq!(next_request_count(), 2);この例の Relaxed は、カウンター自身の不可分な更新だけが必要で、ほかのメモリアクセスとの順序を同期しない場合に適しています。状態間の順序保証が必要な処理では、要件に合う Ordering やロックを選びます。
値とリテラル
Section titled “値とリテラル”リテラルはソースコードに直接記述する値です。数値には型サフィックスを付けられ、_ は桁区切りとして無視されます。
let integer = 42; // i32let unsigned = 42_u64; // u64let floating = 3.14_f32; // f32let enabled = true; // boollet character = 'R'; // charlet text = "Rust"; // &strlet byte = b'R'; // u8let bytes = b"Rust"; // &[u8; 4]let raw = r#"\をエスケープしない文字列"#;let million = 1_000_000;
assert_eq!(integer, 42);assert_eq!(bytes, &[82, 117, 115, 116]);assert!(raw.contains('\\'));assert_eq!(million, 1_000_000);String::from("Rust") のような呼び出しで作られる値や、left + right のような式の評価結果も値です。Rustでは多くの構文が式であり、ブロック、if、match、loop も値を返せます。
| 分類 | 型 | 補足 |
|---|---|---|
| 符号付き整数 | i8, i16, i32, i64, i128, isize |
数値リテラルのデフォルトは i32 |
| 符号なし整数 | u8, u16, u32, u64, u128, usize |
usize は添字やサイズで使用 |
| 浮動小数点 | f32, f64 |
デフォルトは f64 |
| 真偽値 | bool |
true, false |
| 文字 | char |
Unicodeスカラー値(4バイト) |
| ユニット | () |
値を返さないことを表す |
let decimal = 98_222;let hex = 0xff;let octal = 0o77;let binary = 0b1111_0000;let byte = b'A'; // u8
let ratio = 3.5_f64;let enabled = true;let crab = '🦀';デバッグビルドでは整数オーバーフローが実行時に検出されます。意図的に回り込ませる場合は wrapping_add などを使います。
let value = u8::MAX;assert_eq!(value.wrapping_add(1), 0);assert_eq!(value.saturating_add(1), u8::MAX);assert_eq!(value.checked_add(1), None);// タプル:異なる型をまとめられるlet user = ("Ferris", 10, true);let (name, age, active) = user;println!("{name}: {age}, active={active}");
// 配列:長さが型の一部で、スタック上に固定長で確保されるlet numbers: [i32; 3] = [10, 20, 30];let zeros = [0; 5];println!("{} {}", numbers[0], zeros.len());配列への [] アクセスは、範囲外の場合にpanicします。安全に取得するには get を使います。
let values = [10, 20, 30];match values.get(5) { Some(value) => println!("{value}"), None => println!("範囲外です"),}数値型は暗黙変換されません。as または From / TryFrom を使います。
let small: u8 = 42;let large = u32::from(small); // 損失のない変換
let integer = 42_i32;let floating = integer as f64; // プリミティブ型のキャストassert_eq!(floating, 42.0);
let value: i32 = 300;let byte = u8::try_from(value); // Result<u8, TryFromIntError>assert!(byte.is_err());
let integer = 65_u32;let letter = char::from_u32(integer); // Option<char>assert_eq!(letter, Some('A'));as は切り詰めなどが発生し得ます。入力値の範囲を保証できない場合は TryFrom を優先します。
let value = 300_u16;assert_eq!(value as u8, 44); // 上位ビットが切り捨てられるassert!(u8::try_from(value).is_err());型エイリアス
Section titled “型エイリアス”型エイリアスは既存の型に別名を付けます。新しい型は作られないため、元の型と相互に代入できます。
type UserId = u64;type IoResult<T> = Result<T, std::io::Error>;
fn load_user(id: UserId) -> IoResult<String> { Ok(format!("user-{id}"))}
let id: UserId = 42;let raw_id: u64 = id; // 同じ型として扱われるassert_eq!(load_user(raw_id).unwrap(), "user-42");値を混同できない別の型にしたい場合は、タプル構造体を使うnewtypeパターンを検討します。
struct UserId(u64);struct ProductId(u64);
fn find_user(id: UserId) { println!("user={}", id.0);}
find_user(UserId(42));// find_user(ProductId(42)); // 異なる型なのでコンパイルエラーRustに組み込みの typeof 演算子はありません。型を明示して取得する場合は std::any::type_name::<T>()、値から確認する場合は std::any::type_name_of_val を利用できます。
fn type_of<T: ?Sized>(_: &T) -> &'static str { std::any::type_name::<T>()}
let number = 123;println!("{}", type_of(&number));println!("{}", std::any::type_name_of_val(&number));値を直接受け取るヘルパーとして、次の形でも動作します。
fn type_of<T>(_: T) -> &'static str { std::any::type_name::<T>()}
let number = 123;println!("{}", type_of(number));ただし、この形は引数を値で受け取るため、String などの Copy ではない値を渡すと所有権が関数へ移動します。汎用ヘルパーとしては参照を受け取る形が扱いやすくなります。
type_name の出力形式は保証されておらず、コンパイラのバージョンによって変わる可能性があります。型の一意な識別やアプリケーションの分岐条件には使わず、診断やログ表示に利用します。
| 分類 | 演算子 | 補足 |
|---|---|---|
| 算術 | +, -, *, /, % |
整数同士の除算は小数部を切り捨てる |
| 単項 | -, ! |
数値の符号反転、真偽値の否定・整数のビット反転 |
| 比較 | ==, !=, <, <=, >, >= |
型に応じて PartialEq / PartialOrd などを利用 |
| 論理 | &&, ||, ! |
&& と || は短絡評価 |
| ビット | &, |, ^, <<, >> |
整数のビット演算 |
| 代入 | =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= |
複合代入を含む |
| 範囲 | .., ..= |
終端を含まない/含む範囲 |
| 借用・デリファレンス | &, &mut, * |
参照の作成と参照先へのアクセス |
| エラー伝播 | ? |
Result / Option などの失敗を早期リターン |
assert_eq!(7 / 2, 3);assert_eq!(7.0 / 2.0, 3.5);assert_eq!(-5_i32, 0 - 5);assert_eq!(!true, false);
let mut value = 5;value *= 2;assert_eq!(value, 10);
let mut flags = 0b0011_u8;flags |= 0b0100;flags <<= 1;assert_eq!(flags, 0b1110);
let in_range = (1..=10).contains(&value);assert!(in_range);ユーザー定義型では、Add、PartialEq、Index などの標準トレイトを実装することで対応する演算子を利用できます。任意の新しい演算子を定義したり、演算子の優先順位を変更したりすることはできません。
所有権・借用・スライス
Section titled “所有権・借用・スライス”The Rust Programming Language - 所有権
Rustはガベージコレクタを使わず、所有権の規則をコンパイル時に検査してメモリを管理します。
基本規則は次の3つです。
- 各値には所有者が1つ存在する
- 同時に存在できる所有者は1つだけ
- 所有者がスコープを抜けると値は破棄される
let original = String::from("hello");let moved = original; // Stringの所有権がmovedへ移動
println!("{moved}");// println!("{original}"); // コンパイルエラー:移動後は使えないi32 のように Copy を実装する型は、代入時に値がコピーされます。
let x = 5;let y = x;println!("{x} {y}"); // どちらも使用できる独立したヒープデータを複製する場合は clone を使います。
let original = String::from("hello");let cloned = original.clone();println!("{original} {cloned}");参照を渡すと、所有権を移動せずに値を借用できます。
fn length(text: &str) -> usize { text.len()}
let message = String::from("hello");let size = length(&message);println!("{message}: {size}");借用の主な規則は次のとおりです。
- 任意の数の不変参照
&T、または1つの可変参照&mut Tを作れる - 同じ値に対する不変参照と可変参照を同時に有効にはできない
- 参照は常に有効な値を指す必要がある
fn append_world(text: &mut String) { text.push_str(", world");}
let mut message = String::from("hello");append_world(&mut message);println!("{message}");スライスはコレクション内の連続した範囲を借用します。データそのものは所有しません。
let numbers = [10, 20, 30, 40];let middle: &[i32] = &numbers[1..3];assert_eq!(middle, &[20, 30]);
let text = String::from("hello world");let hello: &str = &text[..5];assert_eq!(hello, "hello");文字列の範囲指定はUTF-8の文字境界でなければpanicします。文字単位の処理には chars、バイト単位には bytes を使います。
let text = "日本語";let characters: Vec<char> = text.chars().collect();assert_eq!(characters, vec!['日', '本', '語']);assert_eq!(text.len(), 9); // UTF-8のバイト数assert_eq!(text.chars().count(), 3); // Unicodeスカラー値の数ライフタイムの概要
Section titled “ライフタイムの概要”ライフタイムは参照が有効な範囲を表します。多くの場合はコンパイラが推論します。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() >= y.len() { x } else { y }}'a は返される参照が、入力参照のうち短い方の有効期間を超えないことを示します。値の寿命を延ばす指定ではありません。
'static はプログラム全体にわたって有効なライフタイムです。文字列リテラルは &'static str です。
if・if let・let else
Section titled “if・if let・let else”Rustの if は式なので値を返せます。条件式は必ず bool です。
let score = 80;let grade = if score >= 80 { "A" } else { "B" };
let name = Some("Ferris");if let Some(value) = name { println!("{value}");}
fn print_name(name: Option<&str>) { let Some(name) = name else { println!("名前がありません"); return; }; println!("{name}");}loop・while・for
Section titled “loop・while・for”// 無限ループ。breakで値を返せるlet mut count = 0;let result = loop { count += 1; if count == 3 { break count * 10; }};assert_eq!(result, 30);
// whilewhile count > 0 { count -= 1;}
// forfor value in [10, 20, 30] { println!("{value}");}
for number in 1..=3 { println!("{number}");}
// continue:現在の反復の残りを飛ばすfor number in 1..=5 { if number % 2 == 0 { continue; } println!("odd: {number}");}for では into_iter が呼び出されます。コレクションを値で渡すと所有権が移動する場合があります。
let mut names = vec![String::from("Alice"), String::from("Bob")];
for name in &names { // iter(): &String println!("{name}");}for name in &mut names { // iter_mut(): &mut String name.push('!');}assert_eq!(names, vec!["Alice!", "Bob!"]);
// for name in names { } // into_iter(): String。namesを消費ループラベル
Section titled “ループラベル”'outer: for x in 0..3 { for y in 0..3 { if x + y == 3 { break 'outer; } }}match はすべてのパターンを網羅する必要があります。
let number = 7;
let description = match number { 0 => "zero", 1..=9 => "one digit", n if n % 2 == 0 => "even", _ => "other",};
println!("{description}");let point = (0, 5);match point { (0, y) => println!("y軸上: {y}"), (x, 0) => println!("x軸上: {x}"), (x, y) => println!("({x}, {y})"),}関数・クロージャ
Section titled “関数・クロージャ”引数と戻り値の型は明示します。最後の式に ; を付けなければ、その値が返されます。
fn add(left: i32, right: i32) -> i32 { left + right}
fn divide(left: f64, right: f64) -> Option<f64> { if right == 0.0 { return None; } Some(left / right)}文と式の違いに注意します。
let value = { let base = 10; // 文 base * 2 // 式};assert_eq!(value, 20);クロージャは周囲の変数を借用または取得できます。引数・戻り値の型は多くの場合推論されます。
let offset = 10;let add_offset = |value: i32| value + offset;assert_eq!(add_offset(5), 15);
let mut values = vec![3, 1, 2];values.sort_by_key(|value| *value);move を付けると、使用する値の所有権をクロージャへ移動します。スレッドに渡すときによく使います。
let message = String::from("hello");let show = move || println!("{message}");show();クロージャは使用方法に応じて Fn、FnMut、FnOnce のトレイトを実装します。
fn apply_twice<F>(mut operation: F, value: i32) -> i32where F: FnMut(i32) -> i32,{ let once = operation(value); operation(once)}
assert_eq!(apply_twice(|x| x + 1, 10), 12);関数ポインタ
Section titled “関数ポインタ”環境をキャプチャしない関数やクロージャは fn 型として扱えます。
fn double(value: i32) -> i32 { value * 2}
fn calculate(operation: fn(i32) -> i32, value: i32) -> i32 { operation(value)}
assert_eq!(calculate(double, 5), 10);構造体・共用体・列挙型・パターン
Section titled “構造体・共用体・列挙型・パターン”#[derive(Debug, Clone, PartialEq)]struct User { name: String, age: u32, active: bool,}
let name = String::from("Ferris");let user = User { name, age: 10, active: true,};
let updated = User { age: 11, ..user.clone()};
println!("{updated:?}");#[derive(...)] で標準トレイトの実装を自動生成できます。
| トレイト | 用途 |
|---|---|
Debug |
{:?} / {:#?} によるデバッグ表示 |
Clone |
明示的な複製 |
Copy |
代入時の暗黙コピー。Drop を実装する型には付けられない |
PartialEq, Eq |
等価比較 |
PartialOrd, Ord |
順序比較 |
Hash |
HashMap / HashSet のキー |
Default |
デフォルト値の生成 |
タプル構造体・ユニット構造体
Section titled “タプル構造体・ユニット構造体”struct UserId(u64);struct Point(i32, i32);struct Marker;
let id = UserId(42);let point = Point(10, 20);println!("{} {}", id.0, point.1);共用体(union)
Section titled “共用体(union)”union は複数のフィールドが同じメモリ領域を共有する型です。主にCとのFFIや低レベルのデータ表現で利用します。
#[repr(C)]union Number { integer: i32, floating: f32,}
let number = Number { integer: 42 };
// SAFETY: numberはintegerフィールドで初期化している。let integer = unsafe { number.integer };assert_eq!(integer, 42);フィールドへの書き込みは安全ですが、読み取りは格納されているビット列がそのフィールド型として有効かをコンパイラが確認できないため unsafe が必要です。union には「現在有効なフィールド」を記録する仕組みがないため、必要なら別のタグを持たせて管理します。
フィールド型には、破棄処理を必要としない型、参照、または ManuallyDrop<T> などの制約があります。通常のアプリケーションで複数種類の値を安全に表現する場合は、まず enum を検討します。
メソッド・関連関数
Section titled “メソッド・関連関数”#[derive(Debug)]struct Rectangle { width: u32, height: u32,}
impl Rectangle { // 関連関数。Rectangle::square(10) と呼ぶ fn square(size: u32) -> Self { Self { width: size, height: size, } }
// 不変借用 fn area(&self) -> u32 { self.width * self.height }
// 可変借用 fn resize(&mut self, width: u32, height: u32) { self.width = width; self.height = height; }
// 所有権を受け取る fn dimensions(self) -> (u32, u32) { (self.width, self.height) }}
let mut rectangle = Rectangle::square(10);rectangle.resize(20, 30);assert_eq!(rectangle.area(), 600);assert_eq!(rectangle.dimensions(), (20, 30));各バリアントは異なる型・個数のデータを保持できます。
#[derive(Debug)]enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8),}
fn handle(message: Message) { match message { Message::Quit => println!("quit"), Message::Move { x, y } => println!("move to {x}, {y}"), Message::Write(text) => println!("{text}"), Message::ChangeColor(r, g, b) => println!("rgb({r}, {g}, {b})"), }}
handle(Message::Write(String::from("hello")));Option
Section titled “Option”Rustには null の代わりに Option<T> があります。
enum Option<T> { None, Some(T),}fn find_even(values: &[i32]) -> Option<i32> { values.iter().copied().find(|value| value % 2 == 0)}
let value = find_even(&[1, 3, 4, 5]);assert_eq!(value, Some(4));
// 値があるときだけ変換let doubled = value.map(|number| number * 2);assert_eq!(doubled, Some(8));
// デフォルト値assert_eq!(None::<i32>.unwrap_or(0), 0);unwrap と expect は None でpanicします。回復可能な不在を通常の処理として扱う場合は、パターンマッチ、?、unwrap_or などを使います。
コレクション・文字列
Section titled “コレクション・文字列”Vec<T> は同じ型の値を連続したメモリ領域に保持する可変長配列です。
let mut values = Vec::with_capacity(4);values.push(10);values.extend([20, 30]);
assert_eq!(values[0], 10); // 範囲外はpanicassert_eq!(values.get(1), Some(&20));
for value in &values { println!("{value}");}
let last = values.pop();assert_eq!(last, Some(30));vec! マクロで簡潔に生成できます。
let numbers = vec![1, 2, 3];let zeros = vec![0; 5];Stringと&str
Section titled “Stringと&str”| 型 | 所有権 | 主な用途 |
|---|---|---|
String |
所有する | 可変・伸長可能なUTF-8文字列 |
&str |
借用する | 文字列スライス、関数の読み取り引数 |
let mut message = String::from("Hello");message.push(',');message.push_str(" Rust");
fn greet(name: &str) -> String { format!("Hello, {name}!")}
assert_eq!(greet(&message), "Hello, Hello, Rust!");複数の文字列型を読み取り専用で受け取る関数は、通常 &String より &str を使うと柔軟です。
let first = String::from("Hello");let second = String::from("Rust");
let joined = format!("{first}, {second}"); // 所有権を奪わないlet combined = first + ", " + &second; // firstは移動するHashMap
Section titled “HashMap”use std::collections::HashMap;
let mut scores = HashMap::new();scores.insert(String::from("Alice"), 10);scores.insert(String::from("Bob"), 20);
// 値を取得if let Some(score) = scores.get("Alice") { println!("{score}");}
// キーがない場合だけ追加scores.entry(String::from("Carol")).or_insert(0);
// 既存値を更新*scores.entry(String::from("Alice")).or_insert(0) += 5;
for (name, score) in &scores { println!("{name}: {score}");}HashMap の反復順序には依存しないでください。順序が必要なら BTreeMap を使います。
HashSet・BTreeMap・VecDeque
Section titled “HashSet・BTreeMap・VecDeque”use std::collections::{BTreeMap, HashSet, VecDeque};
let unique: HashSet<_> = [1, 2, 2, 3].into_iter().collect();assert_eq!(unique.len(), 3);
let mut sorted = BTreeMap::new();sorted.insert("b", 2);sorted.insert("a", 1); // キー順に反復できる
let mut queue = VecDeque::new();queue.push_back("first");queue.push_back("second");assert_eq!(queue.pop_front(), Some("first"));エラーハンドリング
Section titled “エラーハンドリング”The Rust Programming Language - エラーハンドリング
Rustでは、回復可能なエラーに Result<T, E>、回復不能なバグに panic! を使います。
Result
Section titled “Result”enum Result<T, E> { Ok(T), Err(E),}use std::fs;use std::io;
fn read_config() -> Result<String, io::Error> { match fs::read_to_string("config.toml") { Ok(contents) => Ok(contents), Err(error) => Err(error), }}? は成功値を取り出し、エラーなら呼び出し元へ早期リターンします。必要なら From によるエラー変換も行います。
use std::fs;use std::io;
fn read_config() -> Result<String, io::Error> { let contents = fs::read_to_string("config.toml")?; Ok(contents)}main も Result を返せます。
use std::error::Error;use std::fs;
fn main() -> Result<(), Box<dyn Error>> { let contents = fs::read_to_string("config.toml")?; println!("{contents}"); Ok(())}Box<dyn Error> は例を簡潔にできますが、ライブラリでは呼び出し側が分類できる具体的なエラー型を検討します。
エラーの変換・追加処理
Section titled “エラーの変換・追加処理”fn parse_number(text: &str) -> Result<i32, String> { let number = text .parse::<i32>() .map_err(|error| format!("整数に変換できません: {error}"))?; Ok(number)}
assert_eq!(parse_number("42"), Ok(42));よく使うメソッドは次のとおりです。
| メソッド | 概要 |
|---|---|
map |
Ok の値を変換 |
map_err |
Err の値を変換 |
and_then |
成功時に別の Result を返す処理を連結 |
unwrap_or |
エラー時の値を指定 |
unwrap_or_else |
エラー時の値をクロージャで生成 |
inspect / inspect_err |
値を変更せず副作用を実行 |
カスタムエラー
Section titled “カスタムエラー”標準ライブラリだけで実装する例です。
use std::error::Error;use std::fmt;
#[derive(Debug)]enum ParseUserError { EmptyName, InvalidAge(std::num::ParseIntError),}
impl fmt::Display for ParseUserError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::EmptyName => write!(formatter, "名前が空です"), Self::InvalidAge(error) => write!(formatter, "年齢が不正です: {error}"), } }}
impl Error for ParseUserError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::InvalidAge(error) => Some(error), Self::EmptyName => None, } }}
impl From<std::num::ParseIntError> for ParseUserError { fn from(error: std::num::ParseIntError) -> Self { Self::InvalidAge(error) }}アプリケーションや大規模なエラー型では、要件に応じて thiserror や anyhow などの外部crateも検討できます。
panic!("継続できない状態です");
let value = Some(42).expect("値が存在する前提");assert_eq!(value, 42);unwrap / expect は、テスト、サンプル、または失敗がプログラムの不変条件違反を意味する箇所に限定すると意図が明確です。通常の入力エラーには Result / Option を使います。
ジェネリクス・トレイト・ライフタイム
Section titled “ジェネリクス・トレイト・ライフタイム”ジェネリクス
Section titled “ジェネリクス”fn largest<T: PartialOrd>(values: &[T]) -> Option<&T> { let mut iterator = values.iter(); let mut largest = iterator.next()?;
for value in iterator { if value > largest { largest = value; } } Some(largest)}
assert_eq!(largest(&[3, 1, 4, 2]), Some(&4));#[derive(Debug)]struct Point<T> { x: T, y: T,}
impl<T> Point<T> { fn x(&self) -> &T { &self.x }}トレイトは複数の型が共有する振る舞いを定義します。
trait Summary { fn summarize(&self) -> String;
fn category(&self) -> &'static str { "general" }}
struct Article { title: String,}
impl Summary for Article { fn summarize(&self) -> String { self.title.clone() }}トレイト境界
Section titled “トレイト境界”use std::fmt::{Debug, Display};
// impl Trait構文fn print_summary(item: &impl Display) { println!("{item}");}
// トレイト境界構文fn print_debug<T: Debug>(item: &T) { println!("{item:?}");}
// 複数の境界とwhere句fn compare_and_print<T, U>(left: &T, right: &U)where T: Display + PartialOrd<U>, U: Display,{ println!("{left} / {right}");}関連型はトレイト実装ごとに具体型を1つ定めます。
trait Repository { type Item; type Error;
fn find(&self, id: u64) -> Result<Option<Self::Item>, Self::Error>;}標準ライブラリの Iterator も type Item という関連型を持ちます。
静的ディスパッチと動的ディスパッチ
Section titled “静的ディスパッチと動的ディスパッチ”use std::fmt::Display;
// コンパイル時に具体型が決まる。単相化されるfn static_dispatch(value: &impl Display) { println!("{value}");}
// 実行時にvtable経由で呼び出すfn dynamic_dispatch(value: &dyn Display) { println!("{value}");}異なる具体型を同じコレクションに入れる場合はトレイトオブジェクトを使えます。
use std::fmt::Display;
let values: Vec<Box<dyn Display>> = vec![ Box::new(42), Box::new(String::from("hello")),];ライフタイム注釈
Section titled “ライフタイム注釈”struct Excerpt<'a> { text: &'a str,}
impl<'a> Excerpt<'a> { fn text(&self) -> &str { self.text }}所有する String を構造体に持たせれば、構造体のライフタイム注釈が不要になることがあります。借用が本当に必要かも含めて設計します。
イテレータは遅延評価されます。map や filter などのアダプタだけでは処理されず、collect や sum などの消費アダプタで評価されます。
let result: Vec<i32> = (1..=10) .filter(|number| number % 2 == 0) .map(|number| number * number) .collect();
assert_eq!(result, vec![4, 16, 36, 64, 100]);よく使うアダプタ
Section titled “よく使うアダプタ”let values = [10, 20, 30];
let indexed: Vec<_> = values.iter().enumerate().collect();let total: i32 = values.iter().sum();let found = values.iter().find(|&&value| value >= 20);let all_positive = values.iter().all(|value| *value > 0);let first_two: Vec<_> = values.iter().take(2).copied().collect();
assert_eq!(total, 60);assert_eq!(found, Some(&20));assert!(all_positive);assert_eq!(first_two, vec![10, 20]);iter・iter_mut・into_iter
Section titled “iter・iter_mut・into_iter”| 呼び出し | 要素 | コレクション |
|---|---|---|
iter() |
&T |
借用する |
iter_mut() |
&mut T |
可変借用する |
into_iter() |
通常 T |
消費する |
let mut values = vec![1, 2, 3];
for value in values.iter_mut() { *value *= 2;}assert_eq!(values, vec![2, 4, 6]);
let total: i32 = values.into_iter().sum();// valuesはここでは使用できないassert_eq!(total, 12);独自イテレータ
Section titled “独自イテレータ”struct Counter { current: u32, end: u32,}
impl Iterator for Counter { type Item = u32;
fn next(&mut self) -> Option<Self::Item> { if self.current >= self.end { return None; } self.current += 1; Some(self.current) }}
let values: Vec<_> = Counter { current: 0, end: 3 }.collect();assert_eq!(values, vec![1, 2, 3]);スマートポインタ
Section titled “スマートポインタ”Box<T> は値をヒープに配置し、単一の所有権を持ちます。再帰型や大きな値の移動コストを一定にしたい場合などに使います。
enum List { Cons(i32, Box<List>), Nil,}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));Rc・Arc
Section titled “Rc・Arc”Rc<T>:単一スレッド内の参照カウント共有所有権Arc<T>:複数スレッド間で使えるアトミック参照カウント共有所有権- どちらもデフォルトでは内部の値を変更できない
use std::rc::Rc;
let shared = Rc::new(String::from("shared"));let first = Rc::clone(&shared);let second = Rc::clone(&shared);
assert_eq!(Rc::strong_count(&shared), 3);assert_eq!(first.as_str(), second.as_str());Cell・RefCell
Section titled “Cell・RefCell”外側が不変でも内部を変更できる「内部可変性」を提供します。
use std::cell::RefCell;
let values = RefCell::new(vec![1, 2]);values.borrow_mut().push(3);assert_eq!(*values.borrow(), vec![1, 2, 3]);RefCell<T> は借用規則を実行時に検査し、違反するとpanicします。スレッドセーフではありません。
struct Connection;
impl Drop for Connection { fn drop(&mut self) { println!("接続を閉じます"); }}
let connection = Connection;drop(connection); // スコープ末尾より前に明示的に破棄RustではRAIIにより、値が破棄されるとロックやファイルなどのリソースも解放されます。
モジュール・パッケージ・可視性
Section titled “モジュール・パッケージ・可視性”Cargoの用語は次のとおりです。
| 用語 | 概要 |
|---|---|
| package | Cargo.toml が定義する1つ以上のcrate |
| crate | コンパイル単位。バイナリcrateまたはライブラリcrate |
| module | crate内の名前空間・可視性を構成する単位 |
mod network { pub mod client { pub fn connect() { println!("connected"); } }
fn private_helper() {}}
use network::client;
fn main() { client::connect();}項目はデフォルトで非公開です。
| 指定 | 可視範囲 |
|---|---|
pub |
外部から公開 |
pub(crate) |
同じcrate内 |
pub(super) |
親モジュール内 |
pub(in path) |
指定した祖先モジュール内 |
ファイル構成
Section titled “ファイル構成”src/├── main.rs # バイナリcrateのルート├── lib.rs # ライブラリcrateのルート├── config.rs # mod config;└── network/ ├── mod.rs # mod network; の従来形式 └── client.rs現在は network.rs と network/client.rs を組み合わせる構成も利用できます。
use・as・再公開
Section titled “use・as・再公開”use std::collections::HashMap;use std::fmt::{self, Display};use std::io::Result as IoResult;
mod models { pub struct User;}
pub use models::User; // crateルートでは外部へ再公開The Rust Programming Language - 並行処理
Rustでは所有権と Send / Sync トレイトにより、多くの並行処理上の誤りをコンパイル時に防ぎます。
use std::thread;
let values = vec![1, 2, 3];
let handle = thread::spawn(move || { let total: i32 = values.iter().sum(); total});
let total = handle.join().expect("スレッドがpanicしました");assert_eq!(total, 6);スコープ付きスレッドなら、現在のスコープの値を所有権移動なしで借用できます。
use std::thread;
let values = vec![1, 2, 3];thread::scope(|scope| { scope.spawn(|| println!("{values:?}"));});標準の mpsc チャネルは複数送信者・単一受信者です。
use std::sync::mpsc;use std::thread;
let (sender, receiver) = mpsc::channel();
for id in 0..2 { let sender = sender.clone(); thread::spawn(move || { sender.send(format!("worker {id}")).unwrap(); });}drop(sender); // 元の送信側を閉じる
for message in receiver { println!("{message}");}Arc<Mutex>
Section titled “Arc<Mutex>”複数スレッドで可変状態を共有する代表的な組み合わせです。
use std::sync::{Arc, Mutex};use std::thread;
let counter = Arc::new(Mutex::new(0));let mut handles = Vec::new();
for _ in 0..10 { let counter = Arc::clone(&counter); handles.push(thread::spawn(move || { let mut value = counter.lock().expect("mutexがpoisonされました"); *value += 1; }));}
for handle in handles { handle.join().unwrap();}
assert_eq!(*counter.lock().unwrap(), 10);ロックガードはスコープを抜けると自動的に解放されます。複数ロックを使う場合は取得順序を統一し、ロック保持時間を短くします。
Send・Sync
Section titled “Send・Sync”Send:値の所有権を別スレッドへ移動できるSync:&Tを複数スレッド間で安全に共有できる
これらはマーカートレイトで、多くの型には構成要素に基づいて自動実装されます。手動の unsafe impl は安全性の不変条件を実装者が保証できる場合に限ります。
async / await
Section titled “async / await”Rustの標準ライブラリは Future と構文を提供しますが、汎用の非同期ランタイムは提供しません。実際のI/O処理ではTokioなどのランタイムを用途に応じて選びます。
async fn fetch_value() -> u32 { 42}
async fn calculate() -> u32 { let value = fetch_value().await; value * 2}async fn の呼び出しは遅延評価される Future を返します。.await するかランタイム上で実行するまで処理は進みません。
マクロ・属性
Section titled “マクロ・属性”よく使うマクロ
Section titled “よく使うマクロ”let name = "Ferris";let error = "接続に失敗しました";
println!("name={name}");eprintln!("error: {error}");
let message = format!("id={}", 42);let values = vec![1, 2, 3];
assert!(values.contains(&2));assert_eq!(values.len(), 3);assert_ne!(message, "");
todo!("未実装");unreachable!("この分岐には到達しない前提");todo!、unimplemented!、unreachable! は実行されるとpanicします。
宣言的マクロ
Section titled “宣言的マクロ”macro_rules! hash_map { ($($key:expr => $value:expr),* $(,)?) => {{ let mut map = std::collections::HashMap::new(); $(map.insert($key, $value);)* map }};}
let scores = hash_map! { "Alice" => 10, "Bob" => 20,};手続き的マクロにはカスタムderive、属性マクロ、関数形式マクロがあり、別の proc-macro crateとして定義します。
よく使う属性
Section titled “よく使う属性”#[derive(Debug, Clone, PartialEq)]struct User;
#[cfg(target_os = "linux")]fn platform_name() -> &'static str { "Linux"}
#[must_use]fn calculate() -> i32 { 42}条件付きコンパイルはCargo featureにも利用できます。
#[cfg(feature = "json")]mod json_support;ユニットテスト
Section titled “ユニットテスト”pub fn add(left: i32, right: i32) -> i32 { left + right}
#[cfg(test)]mod tests { use super::*;
#[test] fn adds_two_numbers() { assert_eq!(add(2, 3), 5); }
#[test] #[should_panic(expected = "範囲外")] fn panics_when_out_of_range() { panic!("範囲外です"); }
#[test] #[ignore = "外部サービスが必要"] fn integration_like_test() {}}Resultを返すテスト
Section titled “Resultを返すテスト”#[test]fn parses_number() -> Result<(), Box<dyn std::error::Error>> { let value: i32 = "42".parse()?; assert_eq!(value, 42); Ok(())}tests/ 配下の各ファイルは別のcrateとしてコンパイルされ、ライブラリの公開APIをテストします。
src/lib.rstests/api_test.rsuse my_crate::add;
#[test]fn public_api_adds_numbers() { assert_eq!(add(2, 3), 5);}ドキュメントテスト
Section titled “ドキュメントテスト”/// 2つの整数を加算します。////// # Examples////// ```/// let result = my_crate::add(2, 3);/// assert_eq!(result, 5);/// ```pub fn add(left: i32, right: i32) -> i32 { left + right}cargo test は通常のテストに加えてドキュメントテストも実行します。
テストコマンド
Section titled “テストコマンド”cargo test # すべてのテストcargo test add # 名前で絞り込みcargo test -- --nocapture # 標準出力を表示cargo test -- --ignored # ignoreされたテストcargo test --doc # ドキュメントテストのみcargo test --all-features # 全featureを有効化標準ライブラリ早引き
Section titled “標準ライブラリ早引き”表示・フォーマット
Section titled “表示・フォーマット”let name = "Ferris";let age = 10;
println!("{name} is {age}");println!("debug: {:?}", vec![1, 2, 3]);println!("pretty: {:#?}", vec![1, 2, 3]);println!("hex: {age:x}, binary: {age:b}");println!("right: {:>8}", name);println!("zero pad: {age:04}");let text = " Hello, Rust ";
assert_eq!(text.trim(), "Hello, Rust");assert!(text.contains("Rust"));assert!(text.trim().starts_with("Hello"));assert_eq!(text.replace("Rust", "World"), " Hello, World ");
let parts: Vec<_> = "a,b,c".split(',').collect();assert_eq!(parts, vec!["a", "b", "c"]);
let joined = ["a", "b", "c"].join("-");assert_eq!(joined, "a-b-c");fn main() -> Result<(), Box<dyn std::error::Error>> { let number: i32 = "42".parse()?; let hex = i32::from_str_radix("ff", 16)?; let text = number.to_string();
assert_eq!(hex, 255); assert_eq!(text, "42"); Ok(())}ファイル・パス
Section titled “ファイル・パス”use std::fs;use std::io;use std::path::{Path, PathBuf};
fn copy_file() -> io::Result<()> { let path = Path::new("data").join("input.txt"); let contents = fs::read_to_string(&path)?; fs::write("output.txt", contents)?;
let mut buffer = PathBuf::from("data"); buffer.push("output.json"); Ok(())}大きなファイルは全体をメモリに読み込まず、BufReader で逐次処理できます。
use std::fs::File;use std::io::{self, BufRead, BufReader};
fn print_lines(path: &str) -> io::Result<()> { let file = File::open(path)?; for line in BufReader::new(file).lines() { println!("{}", line?); } Ok(())}環境変数・コマンドライン引数
Section titled “環境変数・コマンドライン引数”use std::env;
let args: Vec<String> = env::args().collect();
match env::var("APP_ENV") { Ok(value) => println!("APP_ENV={value}"), Err(env::VarError::NotPresent) => println!("未設定"), Err(env::VarError::NotUnicode(_)) => println!("Unicodeではありません"),}シークレットをログやエラーメッセージに出力しないよう注意します。
use std::thread;use std::time::{Duration, Instant, SystemTime};
let started = Instant::now();thread::sleep(Duration::from_millis(10));println!("elapsed={:?}", started.elapsed());
let now = SystemTime::now();標準ライブラリは日時の書式化やタイムゾーン処理を広範には提供しません。必要な場合は要件に合う外部crateを選びます。
OnceLock・LazyLock
Section titled “OnceLock・LazyLock”use std::sync::{LazyLock, OnceLock};
static CONFIG: OnceLock<String> = OnceLock::new();static DEFAULTS: LazyLock<Vec<String>> = LazyLock::new(|| vec![String::from("default")]);
let config = CONFIG.get_or_init(|| String::from("production"));assert_eq!(config, "production");assert_eq!(DEFAULTS.len(), 1);外部関数の呼び出し(extern・FFI)
Section titled “外部関数の呼び出し(extern・FFI)”extern はRust以外のABIを使う関数やstatic項目を宣言するために使います。extern "C" はC ABIを表します。
Edition 2024では、外部ブロックを unsafe extern として宣言する必要があります。宣言した関数のシグネチャが実際の外部関数と一致することはRustコンパイラが検証できないため、宣言者が安全性を保証します。
use std::ffi::c_int;
unsafe extern "C" { fn abs(input: c_int) -> c_int;}
// SAFETY: Cのabsへ、有効範囲内のc_int値を渡している。let value = unsafe { abs(-42) };assert_eq!(value, 42);Rustの関数をC ABIで外部へ公開する場合も extern "C" を使います。
#[unsafe(no_mangle)]pub extern "C" fn add(left: i32, right: i32) -> i32 { left + right}
assert_eq!(add(2, 3), 5);Edition 2024では no_mangle はunsafe属性のため、#[unsafe(no_mangle)] と記述します。
FFI境界では次の点を明示的に確認します。
- ABIと関数シグネチャが外部定義と一致していること
- 共有する構造体などに必要に応じて
#[repr(C)]を指定すること - ポインタのnull、有効範囲、アラインメント、ライフタイム
- 文字列のエンコーディングと終端方法
- メモリをどちらが確保・解放するか
- panicや外部例外をABI境界を越えて伝播させないこと
外部ヘッダーから多数の宣言を取り込む場合は、手書きによる不一致を避けるため、用途に応じてbindgenなどの生成ツールも検討します。
unsafe Rust
Section titled “unsafe Rust”The Rust Programming Language - unsafe Rust
unsafe は借用検査を無効にするものではなく、安全性をコンパイラだけでは証明できない一部の操作を許可します。
unsafeコンテキストで可能になる主な操作は次のとおりです。
- 生ポインタのデリファレンス
- unsafe関数・メソッドの呼び出し
- 可変static変数へのアクセス
- unsafeトレイトの実装
unionフィールドへのアクセス
unsafe fn first_unchecked(values: &[i32]) -> &i32 { // SAFETY: 呼び出し側がvaluesは空でないことを保証する。 unsafe { values.get_unchecked(0) }}
let values = [10, 20];// SAFETY: valuesには2要素あり、空ではない。let first = unsafe { first_unchecked(&values) };assert_eq!(*first, 10);Edition 2024では、unsafe fn の本体でもunsafe操作を明示的な unsafe ブロックに入れる必要があります。unsafeコードは範囲を小さくし、安全性の前提を SAFETY コメントで記録します。
安全なAPIとして公開する場合は、内部のすべての呼び出しに対して安全性の不変条件が成り立つよう設計・検証する必要があります。
Cargo・依存関係管理
Section titled “Cargo・依存関係管理”よく使うコマンド
Section titled “よく使うコマンド”cargo new app # 新規バイナリpackagecargo new --lib my-library # 新規ライブラリpackagecargo init # 既存ディレクトリを初期化
cargo check # 高速な型チェックcargo build # デバッグビルドcargo build --release # 最適化ビルドcargo run -- arg1 arg2 # 実行ファイルへ引数を渡すcargo test # テストcargo doc --open # ドキュメント生成・表示
cargo fmt --check # フォーマット確認cargo clippy --all-targets --all-features -- -D warningscargo check は最終バイナリを生成しないため、実装中の確認に適しています。リリース前は cargo build / cargo test も実行します。
Cargo.toml
Section titled “Cargo.toml”[package]name = "example"version = "0.1.0"edition = "2024"rust-version = "1.98"
[dependencies]
[dev-dependencies]
[features]default = []json = []edition と rust-version は別の設定です。
edition:利用する言語Editionと移行上の互換性境界rust-version:packageがサポートする最小Rustバージョン(MSRV)
ライブラリでは、実際にサポート・CI検証する最小バージョンを rust-version に指定します。
cargo add serde --features derivecargo add --dev tempfilecargo updatecargo treecargo tree -d # 重複バージョンを確認Cargo.lock は再現可能なビルドのための正確な依存バージョンを記録します。バイナリ・アプリケーションでは通常コミットします。ライブラリでもワークスペースやCIの再現性のためコミットできますが、利用者が解決する依存バージョンは Cargo.toml の制約に従います。
feature
Section titled “feature”[features]default = []json = ["dep:serde", "dep:serde_json"]
[dependencies]serde = { version = "1", features = ["derive"], optional = true }serde_json = { version = "1", optional = true }cargo build --features jsoncargo test --all-featurescargo test --no-default-featuresfeatureは加算的に設計することが推奨されます。同じfeature名を無効化の意味に使うと、依存グラフ上で組み合わされたときに予期しない動作になりやすくなります。
ワークスペース
Section titled “ワークスペース”[workspace]resolver = "3"members = ["crates/core", "crates/cli"]
[workspace.package]edition = "2024"rust-version = "1.98"
[workspace.dependencies]serde = { version = "1", features = ["derive"] }[package]name = "example-core"version = "0.1.0"edition.workspace = truerust-version.workspace = true
[dependencies]serde.workspace = trueTips & よくあるミス
Section titled “Tips & よくあるミス”不要なclone
Section titled “不要なclone”借用で十分な箇所に clone を追加すると、所有権エラーは消えても余分な割り当てやコピーが発生します。
fn print_name(name: &str) { println!("{name}");}
let name = String::from("Ferris");print_name(&name); // clone不要println!("{name}");まず所有権を誰が持つべきか、関数が値を所有する必要があるかを検討します。
&Stringより&str
Section titled “&Stringより&str”fn length(text: &str) -> usize { text.len()}
length("literal");length(&String::from("owned"));読み取り専用の文字列引数は &str にすると、文字列リテラルと String の両方を受け取れます。同様に、読み取り専用の可変長配列は &Vec<T> より &[T] が一般的です。
インデックスアクセス
Section titled “インデックスアクセス”let values = vec![10, 20, 30];
// 入力に由来する添字など、範囲外になり得る場合if let Some(value) = values.get(10) { println!("{value}");}values[index] は範囲外でpanicします。不確実な添字には get を使います。
Unicode文字列
Section titled “Unicode文字列”let text = "🦀Rust";assert_eq!(text.len(), 8); // バイト数
let first = text.chars().next();assert_eq!(first, Some('🦀'));String / str は整数による直接インデックスを提供しません。書記素クラスタ(見た目上の文字)単位の処理が必要なら、Unicode仕様を扱う外部crateを検討します。
Mutexガードをawaitをまたいで保持しない
Section titled “Mutexガードをawaitをまたいで保持しない”同期 Mutex のガードを .await の間保持すると、デッドロックやタスク停滞の原因になります。必要な値を取り出してガードを先に破棄するか、非同期ランタイム向けのMutexを要件に応じて使います。
use std::sync::Mutex;
async fn async_operation(_value: String) {}
async fn process(shared: &Mutex<String>) { let value = { let guard = shared.lock().expect("mutexがpoisonされました"); guard.clone() }; // ここでガードを解放
async_operation(value).await;}ブロッキング処理をasyncタスクで直接実行しない
Section titled “ブロッキング処理をasyncタスクで直接実行しない”CPU負荷の高い処理や同期I/Oを非同期executor上で直接実行すると、他のタスクを止めることがあります。利用するランタイムが提供するブロッキング処理用APIや専用スレッドを使います。
公開APIのpanic
Section titled “公開APIのpanic”ライブラリの公開APIでは、通常の入力でpanicせず Result / Option を返す設計を検討します。panicする条件がある場合は、ドキュメントの # Panics セクションに記載します。
/// 指定位置の値を返します。////// # Panics////// `index` が範囲外の場合にpanicします。pub fn value_at(values: &[i32], index: usize) -> i32 { values[index]}警告を放置しない
Section titled “警告を放置しない”cargo fmt --checkcargo clippy --all-targets --all-features -- -D warningscargo test --all-featuresClippyの提案は有用ですが、意味や性能特性が変わらないか確認して適用します。プロジェクト固有のlint方針がある場合はそちらを優先します。
公式リファレンス
Section titled “公式リファレンス”- Rust公式サイト
- The Rust Programming Language
- Rust By Example
- The Rust Reference
- Rust標準ライブラリ
- The Cargo Book
- Clippy Documentation
- The Rust Edition Guide
- Rust 1.98.0リリースノート
Rustでは、所有権・借用・型によって不正な状態を表現しにくくすることが重要です。 コンパイラのエラーは制約だけでなく、設計上の問題を早い段階で見つける手がかりとして活用できます。