Dev Study
← コース一覧

Rust

所有権モデルによってメモリ安全性をコンパイル時に保証するシステムプログラミング言語です。変数や関数の基礎から、所有権・借用、コレクション、エラー処理、トレイト、イテレータまでを1テーマずつ順番に学びます。

公式ドキュメント: The Rust Programming Language

基本文法

  1. 1
    Cargo — プロジェクトとクレートの管理
    Cargo は Rust に標準で付いてくる、プロジェクトの作成・ビルド・実行・テストをまとめて担うツールです。cargo new プロジェクト名 で雛形を作ると、ソースを置く src フォルダと、設定ファイル Cargo.toml が用意されます。Rust の開発はほぼ必ず Cargo を通して行うので、最初に押さえておきたい土台です。
  2. 2
    変数と可変性 — let と mut
    Rust では let で変数を宣言します。宣言した変数はデフォルトで不変(イミュータブル)で、あとから値を代入し直すことはできません。書き換えたい変数には let mut のように mut キーワードを付けます。
    実行可
  3. 3
    基本型と型推論
    Rust は静的型付け言語で、整数の i32 や u64、浮動小数点数の f64、真偽値の bool、文字の char などの基本型があります。型は let x: i32 = 5; のようにコロンの後ろに書きますが、多くの場合は右辺から推論されるので省略できます。
    実行可
  4. 4
    シャドーイング — 同じ名前で再宣言
    let で宣言済みの名前をもう一度 let で宣言し直すことを「シャドーイング」と呼びます。再代入と違って mut は不要で、新しい変数が古い変数を覆い隠す形になるため、型を変えることもできます。
    実行可
  5. 5
    関数 — 文と式の違い
    関数は fn で定義し、引数には型注釈が必須で、戻り値の型は -> の後ろに書きます。Rust では「文(セミコロンで終わり値を返さない)」と「式(値を返す)」が区別され、関数本体の最後にセミコロンなしで式を置くと、それが戻り値になります。
    実行可
  6. 6
    if 式 — 条件分岐
    条件分岐は if 条件 { ... } else { ... } と書き、条件に丸括弧は不要です。条件は必ず bool でなければならず、数値を「0 でなければ真」のように扱うことはできません。他言語の癖でつまずきやすいポイントです。
    実行可
  7. 7
    loop と while — 条件による繰り返し
    loop { ... } は無限に繰り返すループで、break で抜けます。break に値を添えると、その値が loop 式全体の値になります。条件を毎回チェックしながら繰り返すには while 条件 { ... } を使います。
    実行可
  8. 8
    for とレンジ — 回数が決まった繰り返し
    for は for i in 0..3 { ... } のように、レンジ(範囲)を順に回るループです。0..3 は 0, 1, 2 を表し、終端を含めたいときは 1..=3 と書きます。
    実行可

所有権

  1. 9
    所有権 — 値はムーブする
    Rust では、すべての値にただ一人の「所有者」となる変数がいます。String のようなヒープ上のデータを別の変数に代入すると、所有権が移動(ムーブ)し、元の変数はその時点で使えなくなります。
    実行可
  2. 10
    Copy と Clone — ムーブしない型・明示的な複製
    i32 や bool、char などの単純な型は Copy という性質を持ち、代入すると値がそのまま複製されます。ムーブが起きないので、代入後も元の変数を使い続けられます。
    実行可
  3. 11
    参照と借用 — &T
    値の所有権を渡さずに中身を読みたいときは、& を付けて参照を作ります。参照を受け取ることを「借用」と呼び、関数の引数を &String のように宣言すれば、呼び出し後も元の変数をそのまま使えます。
    実行可
  4. 12
    可変参照 — &mut T
    借用した値を書き換えたいときは、&mut で可変参照を作ります。参照される側の変数も let mut で宣言されている必要があり、関数側は引数を &mut String のように受け取ります。
    実行可
  5. 13
    スライス — データの一部分への参照
    スライスは、String や配列の「一部分」を指す参照です。&s[0..5] のようにレンジで範囲を指定して作り、データを複製せずに部分列を扱えます。文字列のスライスは &str という型になります。
    実行可

構造体

  1. 14
    struct の定義 — 関連データをまとめる
    struct は複数の値に名前を付けてひとまとめにする型です。struct User { name: String, age: u32 } のようにフィールドを定義し、User { name: ..., age: ... } という形でインスタンスを作り、ドット記法でフィールドにアクセスします。
    実行可
  2. 15
    メソッド — impl ブロックと &self
    構造体に振る舞いを持たせるには、impl 構造体名 { ... } というブロックの中に関数を書きます。第1引数を &self にすると、rect.area() のようにドット記法で呼べるメソッドになります。
    実行可
  3. 16
    関連関数 — ::new とコンストラクタ
    impl ブロックには self を取らない関数も書けます。これを関連関数と呼び、Circle::new(3.5) のように「型名::関数名」で呼び出します。String::from もこの形です。
    実行可

列挙型とパターン

  1. 17
    列挙型 enum
    enum は「取りうる値の種類を列挙する」型です。enum Status { Loading, Success, Error } のように定義し、Status::Loading の形で値を作ります。変数に入るのは、列挙したバリアントのどれか1つだけです。
    実行可
  2. 18
    パターンマッチ match
    match は値をパターンと照合して分岐する式です。match value { パターン => 処理, ... } の形で書き、enum のバリアントごとの分岐や、バリアントが持つデータの取り出しが1つの構文でできます。
    実行可
  3. 19
    if let — 1パターンだけの照合
    「特定のバリアントのときだけ処理したい」場面では、match の代わりに if let パターン = 値 { ... } と書けます。パターンに一致したときだけ中の処理が実行され、バリアントが持つデータも取り出せます。
    実行可

モジュールとパッケージ

  1. 20
    mod — コードを名前空間で分ける
    プログラムが大きくなったら、関連する関数を mod モジュール名 { ... } というブロックでひとまとまりにできます。モジュールの中の関数は、モジュール名::関数名 というパスで呼び出します。なお、モジュールの中身はそのままでは外から見えないため、例では関数に pub を付けています(pub の詳細は次のレッスンで学びます)。
    実行可
  2. 21
    pub — 公開範囲を制御する
    モジュールの中の関数や構造体は、デフォルトではすべて非公開で、モジュールの外から呼び出せません。外に見せたいものにだけ pub を付ける、というのが Rust の可視性のルールです。
    実行可
  3. 22
    use — パスを短くする
    モジュールの階層が深くなると、毎回フルパスで書くのは冗長です。ファイルの上部に use パス; と書いておくと、そのパスの最後の要素を短い名前のまま使えるようになります。
    実行可

コレクション

  1. 23
    Vec<T> — 可変長の配列
    Vec<T> は要素を増減できる可変長の配列です。vec![10, 20, 30] マクロで初期値つきで作り、push で末尾に追加、numbers[0] のように添字でアクセスします。
    実行可
  2. 24
    String — 伸長できる文字列
    String はヒープ上に文字列を持ち、あとから伸ばせる型です。String::from("...") やリテラルの .to_string() で作り、push_str で連結します。複数の値を組み立てるなら format! マクロが便利です。
    実行可
  3. 25
    HashMap<K, V> — キーと値のペア
    HashMap<K, V> はキーから値を引ける表です。標準ライブラリの std::collections にあるため、ファイル先頭で use std::collections::HashMap; と書いてから使います。insert で登録し、map["key"] のようにキーで取り出せます。
    実行可

エラー処理

  1. 26
    panic! — 回復不能なエラー
    panic! マクロを呼ぶと、メッセージを表示してプログラムがその場で停止します。配列の範囲外アクセスなどでも内部的に panic が起きており、「これ以上続行できない」状態を表す仕組みです。
    実行可
  2. 27
    Option<T> — null の代わり
    Rust には null がなく、「値があるかもしれないし、ないかもしれない」は標準の enum である Option<T> で表します。値があるなら Some(値)、ないなら None で、中身を使うには match などで取り出します。
    実行可
  3. 28
    Result<T, E> — 回復可能なエラー
    失敗しうる処理の結果は、標準の enum である Result<T, E> で表します。成功なら Ok(値)、失敗なら Err(エラー) を返し、呼び出し側は match で両方のケースを処理します。
    実行可
  4. 29
    ? 演算子 — エラーの委譲
    Result を返す式の後ろに ? を付けると、「Ok なら中身を取り出して続行、Err ならその場で関数から Err を return」という処理を1文字で書けます。エラーを呼び出し元に委譲するときの定番構文です。
    実行可
  5. 30
    From と Into — 型の変換とエラーの自動変換
    From は「ある型から別の型を作る」変換を定義するトレイトです。impl From<A> for B と書いて from 関数を実装すると、B::from(a) で A から B を作れます。From を実装すると、対になる Into(a.into() で B を得る)も自動で使えるようになります。型と型の橋渡しを標準化する仕組みです。
    実行可

ジェネリクスとトレイト

  1. 31
    ジェネリック関数 — 型をパラメータにする
    fn swap<T, U>(...) のように関数名の後ろに <T> を書くと、T を「あとで決まる型」として使えます。これをジェネリクスと呼び、i32 用・f64 用と同じ関数を型ごとに書き分ける重複をなくせます。
    実行可
  2. 32
    トレイトの定義と実装
    トレイトは「この型はこういうメソッドを持つ」という共通の振る舞いの約束です。trait Greet { fn greet(&self) -> String; } のようにシグネチャだけを定義し、impl Greet for Dog { ... } の形で型ごとに実装します。
    実行可
  3. 33
    トレイト境界 — ジェネリクスに条件を付ける
    ジェネリック関数の中で比較や表示などの操作をするには、<T: PartialOrd> のように「T はこのトレイトを実装していること」という条件を付けます。これをトレイト境界と呼びます。
    実行可

ライフタイム

  1. 34
    ライフタイムとは — 参照の有効期間
    参照には「いつまで有効か」という期間があり、これをライフタイムと呼びます。参照の指す先の値がスコープを抜けて消えたのに参照だけが残ると、消えたデータを読む危険な状態(ダングリング参照)になるため、Rust はこれをコンパイル時に検出して拒否します。
    実行可
  2. 35
    ライフタイム注釈 — <'a>
    複数の参照を受け取って参照を返す関数では、「返す参照がどの引数と同じ期間生きるのか」をコンパイラが判断できないことがあります。そこで fn longest<'a>(a: &'a str, b: &'a str) -> &'a str のように <'a> という注釈を書き、引数と戻り値のライフタイムの関係を伝えます。
    実行可
  3. 36
    構造体に参照を持たせる
    構造体のフィールドに参照を持たせるには、struct Excerpt<'a> { part: &'a str } のように構造体自体にライフタイムパラメータを付けます。これは「Excerpt のインスタンスは、part が指す元データより長生きできない」という宣言です。
    実行可

テスト

  1. 37
    #[test] と assert! — テストの書き方
    Rust ではテストをコードのすぐ隣に書きます。#[cfg(test)] を付けた mod tests の中に #[test] を付けた関数を書き、assert!(条件) で検証します。条件が偽だと panic し、そのテストは失敗扱いになります。
    実行可
  2. 38
    assert_eq! と Result を返すテスト
    2つの値が等しいことを検証するには、assert!(a == b) より assert_eq!(a, b) を使います。失敗したとき左右の実際の値を表示してくれるため、何がどう違ったのかをすぐ特定できます。
    実行可

クロージャとイテレータ

  1. 39
    クロージャ — その場で作る無名関数
    クロージャは |引数| 式 という形でその場に書ける無名関数です。let add = |a, b| a + b; のように変数に入れて、普通の関数と同じように呼び出せます。引数や戻り値の型は多くの場合推論されます。
    実行可
  2. 40
    イテレータ — 要素を順に取り出す
    イテレータは「要素を順に取り出せるもの」を表す仕組みです。Vec なら .iter() でイテレータを作り、.next() を呼ぶたびに Some(次の要素) が返り、要素が尽きると None が返ります。
    実行可
  3. 41
    map・filter・collect — 変換パイプライン
    イテレータにはクロージャを受け取るメソッドが用意されています。map は各要素を変換し、filter は条件を満たす要素だけを残し、collect で結果を Vec などのコレクションに集めます。
    実行可

スマートポインタ

  1. 42
    Box<T> — ヒープにデータを置く
    Box<T> は「値をヒープに置き、その場所を指すポインタ」を持つ型です。Box::new(5) で値をヒープに移し、変数自体にはポインタだけが残ります。中身には * で届き、メソッドはドット記法でそのまま呼べます。
    実行可
  2. 43
    Deref — スマートポインタを参照のように使う
    Box の中身に * で届けるのは、Box が Deref トレイトを実装しているからです。Deref は「* を付けたらどの値への参照を返すか」を決めるトレイトで、impl<T> Deref for MyBox<T> のように自作の型にも実装できます。
    実行可
  3. 44
    Drop — 値が消えるときの処理
    Drop トレイトを実装すると、値がスコープを抜けて破棄される瞬間に呼ばれる処理を書けます。fn drop(&mut self) に後片付けのコードを書いておけば、所有者が消えるタイミングでコンパイラが自動で呼び出してくれます。
    実行可
  4. 45
    Rc<T> — 所有者を複数にする
    所有権のルールでは値の所有者は常に1人ですが、Rc<T>(参照カウント)で包むと複数の所有者で1つの値を共有できます。Rc::clone(&data) は中身を複製せず「所有者の数」を1増やすだけで、カウントが0になった時点で値が解放されます。
    実行可
  5. 46
    RefCell<T> — 実行時に借用を検査する
    RefCell<T> は借用ルールの検査をコンパイル時ではなく実行時に行う型です。borrow() で読み取りの借用、borrow_mut() で書き換えの借用を取り出せて、let mut なしの変数の中身でも書き換えられます。これを「内部可変性」と呼びます。
    実行可

並行処理

  1. 47
    thread::spawn — スレッドを起動する
    thread::spawn にクロージャを渡すと、その処理を実行する新しいスレッドが起動します。spawn は JoinHandle を返し、handle.join() を呼ぶとそのスレッドが終わるまで待てます。クロージャの最後の値がスレッドの返り値になり、join().unwrap() で受け取れます。
    実行可
  2. 48
    move クロージャ — スレッドに所有権を渡す
    スレッドのクロージャで外の変数を使うときは、クロージャの前に move を付けて所有権ごとスレッドに渡します。スレッドが元の変数より長く生きる可能性があるため、借用のままではコンパイラが安全を保証できず、move なしではコンパイルエラーになります。
    実行可
  3. 49
    チャネル — スレッド間でデータを送る
    mpsc::channel() は送信機 tx と受信機 rx のペアを作り、tx.send(値) で送ったデータを別スレッドの rx で受け取れます。値は所有権ごと送られるので、送信側はその後その値に触れません。「メモリを共有して通信するのではなく、通信してデータを共有する」というのがチャネルの考え方です。
    実行可
  4. 50
    Mutex と Arc — 共有状態を安全に更新する
    複数のスレッドで同じ値を書き換えたいときは Mutex<T> で包みます。lock().unwrap() でロックを取ると中身への書き換え可能なアクセスが得られ、その値がスコープを抜けると自動でロックが解放されます。ロックを持てるのは同時に1スレッドだけなので、更新が混ざって壊れることがありません。
    実行可

トレイトオブジェクト

  1. 51
    dyn Trait — 異なる型を同じ入れ物に
    Box<dyn Trait> と書くと「このトレイトを実装した何かの型」を指す「トレイトオブジェクト」になります。Vec<Box<dyn Shape>> のようにすれば、Circle と Square のような異なる型をひとつのベクタに入れて、共通のメソッドを同じ書き方で呼び出せます。
    実行可
  2. 52
    静的ディスパッチと動的ディスパッチ — ジェネリクスとの使い分け
    ジェネリクス(<T: Greet>)は、使われた型ごとに専用の関数をコンパイル時に作る「静的ディスパッチ」です。一方 &dyn Greet は、どの実装を呼ぶかを実行時に対応表(vtable)から探す「動的ディスパッチ」です。書き方は似ていますが、呼び先が決まるタイミングが違います。
    実行可

パターン詳説

  1. 53
    分配束縛 — structとタプルをほどく
    let Point { x, y } = p; のように書くと、構造体のフィールドをフィールド名と同じ名前の変数に一度に取り出せます。タプルも let (name, price) = pair; のようにほどけて、パターンの中にパターンを書くネストもできます。
    実行可
  2. 54
    マッチガードと @ 束縛
    match の腕のパターンの後ろに if 条件を足したものを「マッチガード」と呼び、パターンだけでは表せない条件で腕を絞り込めます。また x @ 1..=9 のように @ を使うと、値が範囲に一致するかを調べつつ、その値自体を変数に束縛して腕の中で使えます。
    実行可

高度な機能

  1. 55
    unsafe — コンパイラの保証を一部外す
    unsafe ブロックの中では、生ポインタの参照外しなど、コンパイラが安全性を保証できない数種類の操作が許可されます。&num as *const i32 のように生ポインタを「作る」だけなら通常のコードで書けて、「参照外し」だけが unsafe ブロックを必要とします。
    実行可
  2. 56
    演算子オーバーロード — Add トレイト
    std::ops::Add トレイトを実装すると、自作の型に + 演算子が使えるようになります。fn add(self, other: Point) -> Point の中に「足す」の意味を定義し、type Output で結果の型を指定します(Output のような「関連型」は次のレッスンで詳しく見ます)。
    実行可
  3. 57
    関連型 — トレイトに型を持たせる
    トレイト定義の中に type Item; のように書くものを「関連型」と呼びます。実装側が type Item = String; と具体的な型をひとつ決めると、トレイトのメソッドはその型を Self::Item として参照できます。
    実行可
  4. 58
    macro_rules! — 宣言的マクロ
    macro_rules! は「このパターンの呼び出しを、このコードに展開する」という規則を定義する宣言的マクロです。$( $x:expr ),* は「式がカンマ区切りで0個以上並ぶ」形に一致し、展開側の $( ... )* がその個数ぶん繰り返されます。vec! もこの仕組みで実装されています。
    実行可