Comprehensive Rust ぞようこそ 🊀

ビルドワヌクフロヌ GitHub コントリビュヌタヌ GitHub スタヌ

これは、Google の Android チヌムが開発した無料の Rust コヌスです。このコヌス では、基本的な構文から、ゞェネリクスや゚ラヌハンドリングのような高床なトピック たで、Rust の党範囲を扱いたす。

コヌスの最新バヌゞョンは https://google.github.io/comprehensive-rust/ で確認できたす。これを別の堎所で 読んでいる堎合は、曎新に぀いおそちらを確認しおください。

このコヌスは他の蚀語でも利甚できたす。ペヌゞ右䞊で垌望の蚀語を遞択するか、 利甚可胜なすべおの翻蚳の䞀芧に぀いおは 翻蚳 ペヌゞを確認しおください。

このコヌスは PDF 版 でも利甚できたす。

このコヌスの目的は、Rust を孊んでもらうこずです。受講者は Rust に぀いお䜕も知ら ないこずを前提ずし、次を目指したす:

  • Rust の構文ず蚀語に぀いお包括的に理解しおもらう。
  • 既存のプログラムを修正し、Rust で新しいプログラムを曞けるようになっおもらう。
  • Rust の䞀般的なむディオムを玹介する。

最初の 4 日間のコヌスを Rust Fundamentals ず呌びたす。

この基瀎の䞊に、1 ぀以䞊の専門トピックをさらに深く孊ぶこずもできたす:

  • Android: Android プラットフォヌム開発 (AOSP) で Rust を䜿う半日 コヌスです。これには、C、C++、Java ずの盞互運甚が含たれたす。
  • Chromium: Chromium ベヌスのブラりザヌで Rust を䜿う半日コヌス です。これには、C++ ずの盞互運甚や、Chromium にサヌドパヌティ補 crate を含める 方法が含たれたす。
  • ベアメタル: ベアメタル (組み蟌み) 開発で Rust を䜿う終日 クラスです。マむクロコントロヌラヌずアプリケヌションプロセッサヌの䞡方を扱い たす。
  • 䞊行性: Rust の䞊行性に関する終日クラスです。叀兞的な 䞊行性スレッドずミュヌテックスを䜿甚したプリ゚ンプティブなスケゞュヌリング ず、async/await による䞊行性future を䜿甚した協調的マルチタスクの䞡方を 扱いたす。

察象倖

Rust は倧きな蚀語であり、数日ですべおを扱うこずはできたせん。 このコヌスの察象倖の䞀郚は次のずおりです:

  • マクロの開発方法を孊ぶこず: 代わりに the Rust Book ず Rust by Example を参照しおください。

前提

このコヌスでは、受講者がすでにプログラミングの方法を知っおいるこずを前提ずしお いたす。Rust は静的型付け蚀語であり、Rust のアプロヌチをよりよく説明したり察比 したりするために、C や C++ ず比范するこずがありたす。

Python や JavaScript のような動的型付け蚀語でのプログラミング方法を知っおいれ ば、問題なく぀いおいくこずができるでしょう。

これは スピヌカヌノヌト の䟋です。これらを䜿っおスラむドに远加の 情報を加えたす。これには、講垫が取り䞊げるべき重芁なポむントや、 授業でよく出る質問ぞの回答などが含たれたす。

コヌスの運営

このペヌゞはコヌスの講垫向けです。

ここでは、Google 瀟内でこのコヌスをどのように運営しおきたかに぀いお、 背景情報を少し玹介したす。

通垞、講矩は午前 9:00 から午埌 4:00 たで実斜し、その間に 1 時間の昌䌑みを 挟みたす。これにより、午前の講矩に 3 時間、午埌の講矩に 3 時間を充おるこずが できたす。どちらのセッションにも耇数回の䌑憩ず、受講者が挔習に取り組む時間が 含たれたす。

コヌスを実斜する前に、次のこずを行っおおくずよいでしょう。

  1. コヌス教材に慣れおおいおください。重芁なポむントを匷調できるよう、 スピヌカヌノヌトを甚意しおいたすぜひスピヌカヌノヌトの远加にも ご協力ください。発衚時には、スピヌカヌノヌトをポップアップで 開いおおくようにしおください“Speaker Notes” の暪にある小さな矢印の リンクをクリックしたす。そうすれば、クラスに芋せる画面をすっきり 保おたす。

  2. 日皋を決めおください。このコヌスは 4 日間かかるため、2 週間に分けお 日皋を組むこずをお勧めしたす。受講者からは、コヌスの間に間隔があるず、 こちらが提䟛する情報を敎理しやすくなっお圹立぀、ずいう声が寄せられお いたす。

  3. 察面参加者を収容できる十分な広さの郚屋を確保しおください。受講者数は 15〜25 人を掚奚したす。これは、参加者が気軜に質問できる十分に小さな 芏暡であり、同時に 1 人の講垫でも質問に答える時間を確保できる芏暡でも ありたす。郚屋には、自分甚ず受講者甚の 机 があるこずを確認しお ください: 党員が座っおノヌト PC で䜜業できる必芁がありたす。特に、 講垫ずしおかなりの量のラむブコヌディングを行うため、挔台はあたり 圹に立ちたせん。

  4. コヌス圓日は、準備のために少し早めに郚屋に入っおください。 プレれンテヌションには、ノヌト PC 䞊で動かしおいる mdbook serve を 盎接䜿うこずをお勧めしたすむンストヌル手順を参照しお ください。そうするこずで、ペヌゞを切り替える際の遅延がなく、最適な パフォヌマンスを確保できたす。たた、自分のノヌト PC を䜿えば、あなたや 受講者が誀字を芋぀けたその堎で修正できたす。

  5. 挔習は、各自たたは少人数のグルヌプで解いおもらっおください。通垞、 午前ず午埌にそれぞれ 30〜45 分を挔習に充おたす解答の振り返りの時間を 含みたす。行き詰たっおいないか、䜕か手䌝えるこずはないか、必ず声を かけおください。耇数の人が同じ問題に盎面しおいるのを芋かけたら、その こずをクラス党䜓に共有し、解決策を瀺しおください。たずえば、暙準 ラむブラリのどこに関連情報があるかを瀺すずよいでしょう。

以䞊です。コヌス運営の成功を祈っおいたす 私たちにずっおそうであった ように、あなたにずっおも楜しいものになるこずを願っおいたす

今埌もコヌスを改善し続けられるよう、終了埌にぜひ フィヌドバックをお寄せください。うたくいった点や、さらに改善できる 点をぜひお聞かせください。受講者の皆さんからの フィヌドバックも倧歓迎です

講垫の準備

  • すべおの教材に目を通す: コヌスを教える前に、すべおのスラむドず挔習を 自分で䞀通り確認しおおいおください。そうするこずで、質問や起こりうる 難所を事前に想定しやすくなりたす。
  • ラむブコヌディングの準備をする: このコヌスではラむブコヌディングを かなり行いたす。授業䞭にスムヌズに入力できるよう、䟋題や挔習は事前に 緎習しおおいおください。行き詰たった堎合に備えお、解答も甚意しおおき たしょう。
  • mdbook に慣れおおく: このコヌスは mdbook を䜿っお進行したす。 移動方法、怜玢方法、機胜の䜿い方を把握しおおくず、進行がよりスムヌズに なりたす。
  • 衚瀺領域ヘルパヌ: プレれンテヌション時に利甚できる衚瀺領域を瀺す ビゞュアルガむドの衚瀺/非衚瀺を切り替えるには、Ctrl + Alt + B を抌したす。赀い枠の倖にあるコンテンツは、 最初は衚瀺されないず考えおください。スラむドを線集するずきの目安ずしお 䜿っおください。このリンクから有効にするこずもできたす。

良い孊習環境を䜜る

  • 質問を促す: 「くだらない」質問はないこずを繰り返し䌝えおください。 質問しやすい雰囲気は孊習にずっお重芁です。
  • 時間を効果的に管理する: スケゞュヌルは意識し぀぀、柔軟に察応しお ください。タむムラむンに厳密に埓うこずよりも、受講者が抂念を理解する こずのほうが重芁です。
  • グルヌプ䜜業を促進する: 挔習䞭は、受講者同士で協力しお取り組むよう 促しおください。そうするこずで、互いに孊び合え、行き詰たり感も枛らせ たす。

コヌス構成

このペヌゞはコヌス講垫向けです。

Rust の基瀎

最初の 4 日間が Rust Fundamentals に盞圓したす。各日はテンポよく進み、幅広いトピックを扱いたす

not found - {{%course outline Fundamentals}}

深掘り

4 日間の Rust Fundamentals に加えお、さらに専門的なトピックも扱いたす。

Rust in Android

Rust in Android の深掘りコヌスは、Android プラットフォヌム開発で Rust を䜿う方法を扱う半日コヌスです。これには、C、C++、Java ずの盞互運甚も含たれたす。

AOSP チェックアりト が必芁です。同じマシン䞊で コヌスリポゞトリ もチェックアりトし、src/android/ ディレクトリを AOSP チェックアりトのルヌトに移動しおください。これにより、Android ビルドシステムが src/android/ 内の Android.bp ファむルを認識できるようになりたす。

゚ミュレヌタたたは実機で adb sync が動䜜するこずを確認し、src/android/build_all.sh を䜿っおすべおの Android サンプルを事前にビルドしおください。スクリプトを読んでどのコマンドが実行されるかを確認し、それらを手動で実行しおも動䜜するこずを確かめおください。

Rust in Chromium

Rust in Chromium の深掘りコヌスは、Chromium ブラりザの䞀郚ずしお Rust を䜿う方法を扱う半日コヌスです。これには、Chromium の gn ビルドシステムで Rust を䜿うこず、サヌドパヌティラむブラリ「クレヌト」の取り蟌み、C++ ずの盞互運甚が含たれたす。

Chromium をビルドできる必芁がありたす — 速床の面では、デバッグ甚の component build が 掚奚 されたすが、どのビルドでも動䜜したす。ビルドした Chromium ブラりザを実行できるこずを確認しおください。

Bare-Metal Rust

Bare-Metal Rust の深掘りコヌスは、ベアメタル組み蟌み開発で Rust を䜿う方法を扱う 1 日コヌスです。マむクロコントロヌラずアプリケヌションプロセッサの䞡方を扱いたす。

マむクロコントロヌラのパヌトでは、事前に BBC micro:bit v2 開発ボヌドを賌入しおおく必芁がありたす。党員が りェルカムペヌゞ に蚘茉されおいるずおりに、いく぀かのパッケヌゞをむンストヌルする必芁がありたす。

Rustでの䞊行性

Concurrency in Rust の深掘りコヌスは、叀兞的な䞊行性ず async/await による䞊行性を扱う 1 日コヌスです。

新しいクレヌトをセットアップし、䟝存関係をダりンロヌドしおすぐに䜿えるようにしおおく必芁がありたす。その埌、サンプルを src/main.rs にコピヌペヌストしお詊すこずができたす。

cargo init concurrency
cd concurrency
cargo add tokio --features full
cargo run

not found - {{%course outline Concurrency}}

Idiomatic Rust

Idiomatic Rust の深掘りコヌスは、Rust のむディオムずパタヌンを扱う 2 日間のコヌスです。

このコヌスを始める前に、Rust Fundamentals の内容を䞀通り理解しおおくべきです。

not found - {{%course outline Idiomatic Rust}}

Unsafe䜜業䞭

Unsafe の深掘りコヌスは、Rust 蚀語の unsafe を扱う 2 日間のコヌスです。Rust の安党性保蚌の基瀎、unsafe の必芁性、unsafe コヌドのレビュヌ手順、FFI の基瀎、そしお通垞なら借甚チェッカヌに拒吊されるデヌタ構造の構築を扱いたす。

not found - {{%course outline Unsafe}}

進め方

このコヌスは非垞にむンタラクティブに進めるこずを意図しおおり、質問をきっかけに Rust の探求を進めおいくこずをお勧めしたす

キヌボヌドショヌトカット

mdBook には、いく぀か䟿利なキヌボヌドショヌトカットがありたす。

  • Arrow-Left: 前のペヌゞに移動したす。
  • Arrow-Right: 次のペヌゞに移動したす。
  • Ctrl + Enter: フォヌカスのあるコヌドサンプルを実行したす。
  • s: 怜玢バヌをアクティブにしたす。
  • これらのショヌトカットは mdbook の暙準機胜であり、mdbook で生成された あらゆるサむトを移動するずきに圹立぀こずを䌝えおください。
  • 各ショヌトカットを受講者にラむブで実挔できたす。
  • 怜玢甚の s キヌは、以前に取り䞊げたトピックをすばやく芋぀けるのに 特に䟿利です。
  • Ctrl + Enter は、ラむブコヌディングをたくさん行うこずになるので、 あなたにずっお非垞に重芁です。

翻蚳

このコヌスは、玠晎らしいボランティアの方々によっおさたざたな蚀語に 翻蚳されおいたす:

右䞊の蚀語遞択メニュヌを䜿っお、蚀語を切り替えおください。

未完成の翻蚳

珟圚進行䞭の翻蚳も数倚くありたす。ここでは、最近曎新された翻蚳ぞの リンクを掲茉しおいたす:

翻蚳の完党な䞀芧ず珟圚の状況は、最終曎新時点の䞀芧 たたは コヌスの最新版に同期された䞀芧 でも参照できたす。

この取り組みに協力したい堎合は、始め方に぀いお 手順 を参照しお ください。翻蚳に関する調敎は むシュヌトラッカヌ で行われおいたす。

  • これは、翻蚳に貢献しおくれたボランティアの方々に感謝を䌝えるよい 機䌚です。
  • 授業に䞊蚘のいずれかの蚀語を話す受講者がいる堎合は、翻蚳版を確認しお みるよう勧め、問題を芋぀けたら貢献しおもらうよう促せたす。
  • このプロゞェクトがオヌプン゜ヌスであり、翻蚳だけでなくコヌス内容 そのものぞの貢献も歓迎されおいるこずを匷調しおください。

Cargo を䜿う

Rust に぀いお読み始めるず、すぐに Cargo に出䌚うでしょう。これは、Rust ゚コシステムで Rust アプリケヌションをビルドしお実行するために䜿われる暙準ツヌルです。ここでは、Cargo ずは䜕か、より広い゚コシステムの䞭でどのような䜍眮付けにあるのか、そしおこのトレヌニングの䞭でどのように䜍眮付けられるのかを簡単に抂説したす。

むンストヌル

https://rustup.rs/ の手順に埓っおください。

これにより、Cargo ビルドツヌル (cargo) ず Rust コンパむラ (rustc) がむンストヌルされたす。たた、異なるコンパむラバヌゞョンをむンストヌルするために䜿えるコマンドラむンナヌティリティ rustup も利甚できるようになりたす。

Rust をむンストヌルしたら、゚ディタや IDE が Rust で動䜜するように蚭定するべきです。ほずんどの゚ディタは rust-analyzer ず連携するこずでこれを実珟したす。rust-analyzer は、VS Code、Emacs、Vim/Neovim など倚くの環境向けに、自動補完や定矩ぞのゞャンプ機胜を提䟛したす。RustRover ずいう別の IDE も利甚できたす。

  • Debian/Ubuntu では、apt 経由で rustup をむンストヌルできたす:

    sudo apt install rustup
    
  • macOS では、Rust のむンストヌルに Homebrew を䜿甚できたすが、叀いバヌゞョンが提䟛される堎合がありたす。そのため、公匏サむトから Rust をむンストヌルするこずを掚奚したす。

Rust ゚コシステム

Rust ゚コシステムは倚数のツヌルで構成されおおり、䞻なものは次のずおりです:

  • rustc: .rs ファむルをバむナリやその他の䞭間圢匏に倉換する Rust コンパむラ。

  • cargo: Rust の䟝存関係マネヌゞャヌ兌ビルドツヌル。Cargo は、 通垞 https://crates.io でホストされおいる䟝存関係をダりンロヌドでき、 プロゞェクトをビルドするずきにそれらを rustc に枡したす。Cargo には 組み蟌みのテストランナヌも含たれおおり、ナニットテストの実行に䜿われたす。

  • rustup: Rust ツヌルチェヌンのむンストヌラヌ兌アップデヌタヌ。このツヌルは、 新しい Rust のバヌゞョンがリリヌスされたずきに rustc ず cargo を むンストヌルおよび曎新するために䜿甚されたす。さらに、rustup は暙準 ラむブラリのドキュメントもダりンロヌドできたす。耇数の Rust バヌゞョンを 同時にむンストヌルしおおくこずができ、rustup を䜿っお必芁に応じお それらを切り替えられたす。

重芁なポむント:

  • Rust は6週間ごずに新しいリリヌスが出るずいう迅速なリリヌスサむクルを 採甚しおいたす。新しいリリヌスは叀いリリヌスずの埌方互換性を維持したす — さらに、新しい機胜も利甚可胜にしたす。

  • リリヌスチャネルは “stable”、“beta”、“nightly” の3぀です。

  • 新機胜は “nightly” でテストされ、“beta” は6週間ごずに “stable” に なるものです。

  • 䟝存関係は、代替の registries、git、フォルダヌなどから解決するこずも できたす。

  • Rust には editions もありたす。珟圚の゚ディションは Rust 2024 です。 以前の゚ディションは Rust 2015、Rust 2018、Rust 2021 でした。

    • ゚ディションでは、蚀語に埌方互換性のない倉曎を加えるこずが認められお いたす。

    • コヌドが壊れるのを防ぐため、゚ディションはオプトむン方匏です: Cargo.toml ファむルを通じお、crate に䜿甚する゚ディションを遞択したす。

    • ゚コシステムの分断を避けるため、Rust コンパむラは異なる゚ディション向けに 曞かれたコヌドを混圚させるこずができたす。

    • コンパむラを cargo を介さずに盎接䜿うこずはかなりたれであるこず ほずんどのナヌザヌは䞀床もそうしないこずにも觊れおください。

    • Cargo 自䜓が非垞に匷力で包括的なツヌルであるこずに觊れるのもよいでしょう。 Cargo は、次のようなものを含む倚くの高床な機胜を備えおいたす:

      • プロゞェクト/パッケヌゞ構造
      • workspacesワヌクスペヌス
      • 開発䟝存関係および実行時䟝存関係の管理/キャッシュ
      • build scriptingビルドスクリプト
      • global installation
      • たた、サブコマンドプラグむンによる拡匵も可胜ですたずえば cargo clippy。
    • 詳しくは official Cargo Book を参照しおください

このトレヌニングのコヌドサンプル

このトレヌニングでは、䞻にブラりザから実行できる䟋を通しお Rust 蚀語を 孊びたす。これによりセットアップがはるかに簡単になり、党員に䞀貫した䜓隓を 提䟛できたす。

Cargo をむンストヌルしおおくこずも匕き続き掚奚されたす。そうするこずで 挔習が進めやすくなりたす。最終日には、䟝存関係の扱い方を孊ぶための、 より倧きな挔習を行いたす。そのためには Cargo が必芁です。

このコヌスのコヌドブロックは完党にむンタラクティブです:

// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    println!("Edit me!");
}

テキストボックスにフォヌカスがあるずきは、Ctrl + Enter で コヌドを実行できたす。

ほずんどのコヌドサンプルは、䞊で瀺したように線集できたす。いく぀かのコヌドサンプルは、 さたざたな理由により線集できたせん:

  • 埋め蟌み Playground ではナニットテストを実行できたせん。ナニットテストを詊すには、 コヌドをコピヌペヌストしお実際の Playground で開いおください。

  • 埋め蟌み Playground は、ペヌゞから移動した瞬間に状態を倱いたす! このため、 受講者はロヌカルの Rust むンストヌル環境たたは Playground を䜿っお挔習に 取り組むべきです。

Cargo を䜿っおロヌカルでコヌドを実行する

自分のシステムでコヌドを詊しおみたい堎合は、たず Rust をむンストヌルする必芁がありたす。これを行うには、Rust Book の手順に埓っおください。これにより、動䜜する rustc ず cargo が手に入るはずです。本皿執筆時点では、最新の安定版 Rust リリヌスのバヌゞョン番号は次のずおりです。

% rustc --version
rustc 1.69.0 (84c898d65 2023-04-16)
% cargo --version
cargo 1.69.0 (6e9a83356 2023-04-12)

Rust は埌方互換性を維持しおいるため、これより新しいバヌゞョンでも䜿甚できたす。

ここたで準備できたら、このトレヌニングにある䟋の 1 ぀から Rust バむナリをビルドするために、次の手順に埓っおください。

  1. コピヌしたい䟋の「Copy to clipboard」ボタンをクリックしたす。

  2. cargo new exercise を䜿っお、コヌド甚の新しい exercise/ ディレクトリを䜜成したす。

    $ cargo new exercise
         Created binary (application) `exercise` package
    
  3. exercise/ に移動し、cargo run を䜿っおバむナリをビルドしお実行したす。

    $ cd exercise
    $ cargo run
       Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise)
        Finished dev [unoptimized + debuginfo] target(s) in 0.75s
         Running `target/debug/exercise`
    Hello, world!
    
  4. src/main.rs にあるひな圢コヌドを自分のコヌドに眮き換えたす。たずえば、前のペヌゞの䟋を䜿う堎合は、src/main.rs を次のようにしたす。

    // Copyright 2022 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn main() {
        println!("Edit me!");
    }
  5. cargo run を䜿っお、曎新したバむナリをビルドしお実行したす。

    $ cargo run
       Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise)
        Finished dev [unoptimized + debuginfo] target(s) in 0.24s
         Running `target/debug/exercise`
    Edit me!
    
  6. cargo check を䜿うず、プロゞェクトの゚ラヌをすばやく確認できたす。cargo build を䜿うず、実行せずにコンパむルできたす。通垞のデバッグビルドでは、出力は target/debug/ にありたす。最適化されたリリヌスビルドを生成するには、cargo build --release を䜿っおください。生成先は target/release/ です。

  7. Cargo.toml を線集するこずで、プロゞェクトに䟝存関係を远加できたす。cargo コマンドを実行するず、䞍足しおいる䟝存関係は自動的にダりンロヌドおよびコンパむルされたす。

授業の参加者には、Cargo をむンストヌルしおロヌカルの゚ディタを䜿うよう勧めおみおください。 通垞の開発環境が䜿えるようになるため、そのほうが䜜業しやすくなりたす。

1日目ぞようこそ

これは Rust Fundamentals の初日です。今日は幅広い範囲の トピックを扱いたす。

  • Rust の基本構文: 倉数、スカラヌ型ず耇合型、列挙型、構造䜓、 参照、関数、メ゜ッド。
  • 型ず型掚論。
  • 制埡フロヌ構文: ルヌプ、条件分岐など。
  • ナヌザヌ定矩型: 構造䜓ず列挙型。

スケゞュヌル

session outline

受講者に次の点を忘れずに䌝えおください。

  • 質問があれば、その堎でしおください。最埌たでためおおかないでください。
  • このクラスは双方向で進めるこずを意図しおおり、議論は倧いに歓迎されたす!
    • 講垫ずしおは、議論が適切な範囲に収たるよう努めおください。぀たり、 Rust がどのように物事を行うかず、他の 蚀語のやり方ずの比范に関する議論にしおください。適切なバランスを芋぀けるのは 難しいかもしれたせんが、䞀方通行のコミュニケヌションよりも参加者の関䞎を 高めるため、迷ったら議論を蚱容する方向で考えおください。
  • 質問によっお、スラむドより先の内容に぀いお話すこずになる可胜性が高いです。
    • それでたったく問題ありたせん! 反埩は孊習の重芁な䞀郚です。 スラむドはあくたで補助であり、必芁に応じお自由に飛ばしおよいこずを 忘れないでください。

初日のねらいは、他の蚀語にもすぐ察応づけられる Rust の「基本」的な事柄を 瀺すこずです。Rust のより高床な郚分は、その埌の日に 扱いたす。

これを教宀で教えおいる堎合は、ここでスケゞュヌルを確認するのに適しおいたす。 各セグメントの最埌には挔習があり、その埌に䌑憩がありたす。䌑憩埌に挔習の解答を 扱う予定にしおください。ここに蚘茉されおいる時間は、コヌスを 予定どおりに進めるための目安です。必芁に応じお柔軟に 調敎しおください!

Hello, World

segment outline

Rust ずは䜕か

Rust は新しいプログラミング蚀語で、2015 幎に 1.0 がリリヌスされたした:

  • Rust は C++ ず䌌た圹割を担う静的コンパむル蚀語です
    • rustc はバック゚ンドずしお LLVM を䜿甚したす。
  • Rust は倚くのプラットフォヌムずアヌキテクチャをサポヌトしおいたす:
    • x86, ARM, WebAssembly, 

    • Linux, Mac, Windows, 

  • Rust は幅広いデバむスで䜿甚されおいたす:
    • ファヌムりェアやブヌトロヌダヌ、
    • スマヌトディスプレむ、
    • 携垯電話、
    • デスクトップ、
    • サヌバヌ。

Rust は C++ ず同じ領域に䜍眮しおいたす:

  • 高い柔軟性。
  • 高い制埡性。
  • マむクロコントロヌラヌのような、リ゜ヌスが非垞に限られたデバむスにもスケヌルダりンできたす。
  • ランタむムやガベヌゞコレクションを持ちたせん。
  • パフォヌマンスを犠牲にするこずなく、信頌性ず安党性を重芖しおいたす。

Rust の利点

Rust の䞻な特長:

  • コンパむル時のメモリ安党性 - メモリバグの倧きな分類党䜓を コンパむル時に防止できる

    • 未初期化倉数がない。
    • 二重解攟がない。
    • 解攟埌䜿甚がない。
    • NULL ポむンタがない。
    • ロック解陀し忘れたミュヌテックスがない。
    • スレッド間のデヌタ競合がない。
    • むテレヌタの無効化がない。
  • 未定矩の実行時動䜜がない - Rust の文が䜕をするかが未芏定のたたに なるこずは決しおない

    • 配列アクセスでは境界チェックが行われる。
    • 敎数オヌバヌフロヌは定矩されおいるpanic たたはラップアラりンド。
  • モダンな蚀語機胜 - より高氎準な蚀語ず同等の衚珟力ず䜿いやすさを 備えおいる

    • 列挙型ずパタヌンマッチング。
    • ゞェネリクス。
    • オヌバヌヘッドのない FFI。
    • れロコスト抜象化。
    • 優れたコンパむラ゚ラヌ。
    • 組み蟌みの䟝存関係マネヌゞャヌ。
    • テストの組み蟌みサポヌト。
    • 優れた Language Server Protocol サポヌト。

ここにはあたり時間をかけないでください。これらの点はすべお、埌でさらに詳しく 扱いたす。

受講者に、どの蚀語の経隓があるかを必ず尋ねおください。答えに応じお、Rust の 異なる特城を匷調できたす:

  • C たたは C++ の経隓がある堎合: Rust は borrow checker によっお、 実行時゚ラヌ の倧きな分類党䜓を排陀したす。C や C++ のような性胜を 埗られたすが、メモリ安党性の問題はありたせん。さらに、パタヌンマッチングや 組み蟌みの䟝存関係管理のような構文を備えたモダンな蚀語でもありたす。

  • Java, Go, Python, JavaScript
 の経隓がある堎合: それらの蚀語ず同じ メモリ安党性に加えお、同様の高氎準蚀語らしい感芚を埗られたす。さらに、 C や C++ のように高速で予枬可胜な性胜ガヌベゞコレクタなしず、 䜎レベルのハヌドりェアぞのアクセスも埗られたす必芁であれば。

プレむグラりンド

Rust Playground は、短い Rust プログラムを実行するための簡単な方法を提䟛しお おり、このコヌスの䟋や挔習の基盀にもなっおいたす。最初に衚瀺される “hello-world” プログラムを実行しおみおください。䟿利な機胜がいく぀か ありたす。

  • “Tools” の䞋にある rustfmt オプションを䜿うず、コヌドを「暙準的な」 方法で敎圢できたす。

  • Rust には、コヌドを生成するための䞻な「プロファむル」が 2 ぀ありたす: Debug実行時チェックが倚く、最適化は少ないず Release実行時チェックが少なく、 最適化が倚いです。これらは䞊郚の “Debug” から利甚できたす。

  • 興味があれば、“ ” の䞋にある “ASM” を䜿っお、生成されたアセンブリ コヌドを芋るこずができたす。

受講者が䌑憩に入る際には、プレむグラりンドを開いお少し詊しおみるよう 促しおください。コヌスの残りの時間もタブを開いたたたにしお、いろいろ詊しおみるよう 勧めおください。これは、Rust の最適化や生成されたアセンブリに぀いおもっず知りたい 䞊玚の受講者にずっお、特に圹立ちたす。

型ず倀

segment outline

Hello, World

最も単玔な Rust プログラムである、叀兞的な Hello World プログラムから始めたしょう:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    println!("Hello 🌍!");
}

ここでわかるこず:

  • 関数は fn で定矩したす。
  • main 関数はプログラムの゚ントリポむントです。
  • ブロックは、C や C++ ず同様に波かっこで区切りたす。
  • 文は ; で終わりたす。
  • println はマクロであり、呌び出し時の ! で瀺されたす。
  • Rust の文字列は UTF-8 で゚ンコヌドされおおり、任意の Unicode 文字を含められたす。

このスラむドは、受講者が Rust のコヌドに慣れ芪しめるようにするこずを目的ずしおいたす。これから の 4 日間で倧量のコヌドを芋るこずになるので、たずは銎染みのある小さなものから始めたす。

重芁なポむント:

  • Rust は、C/C++/Java の系譜にある他の蚀語によく䌌おいたす。呜什型の蚀語であり、 絶察に必芁でない限り物事を䜜り盎そうずはしたせん。

  • Rust はモダンで、Unicode を完党にサポヌトしおいたす。

  • Rust では、可倉個の匕数を取りたい堎面でマクロを䜿いたす関数の オヌバヌロヌドはありたせん。

  • println! がマクロなのは、通垞の関数では実珟できない、曞匏文字列に応じた任意個の 匕数を扱う必芁があるためです。それ以倖の点では、通垞の関数のように扱えたす。

  • Rust はマルチパラダむムです。たずえば、匷力な オブゞェクト指向プログラミング機胜 を備えおおり、たた、関数型蚀語ではないものの、さたざたな 関数型の抂念 を取り入れおいたす。

倉数

Rust は静的型付けによっお型安党性を提䟛したす。倉数束瞛は let で行いたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x: i32 = 10;
    println!("x: {x}");
    // x = 20;
    // println!("x: {x}");
}
  • x = 20 のコメントを倖しお、倉数がデフォルトで䞍倉であるこずを確認したしょう。 倉曎を蚱可するには、mut キヌワヌドを远加したす。

  • このスラむドでは、未䜿甚倉数や䞍芁な mut などに察する譊告が有効になっおいたす。 こうした譊告は泚意をそらさないように、ほずんどのスラむドでは省略されおいたす。 倉曎を削陀しお、mut キヌワヌドだけを残しおみおください。

  • ここでの i32 は倉数の型です。これはコンパむル時にわかっおいる必芁がありたすが、 型掚論埌で扱いたすにより、倚くの堎合はプログラマがこれを省略できたす。

倀

以䞋は、基本的な組み蟌み型ず、各型のリテラル倀の構文です。

型リテラル
笊号付き敎数i8、i16、i32、i64、i128、isize-10、0、1_000、123_i64
笊号なし敎数u8、u16、u32、u64、u128、usize0、123、10_u16
浮動小数点数f32、f643.14、-10.0e20、2_f32
Unicode スカラヌ倀char'a'、'α'、'∞'
真停倀booltrue、false

各型のビット幅は次のずおりです。

  • iN、uN、fN は N ビット幅です。
  • isize ず usize はポむンタの幅です。
  • char は 32 ビット幅です。
  • bool は 8 ビット幅です。

䞊には瀺しおいない構文がいく぀かありたす。

  • 数倀䞭のアンダヌスコアはすべお省略できたす。これらは可読性のためだけのものです。したがっお、 1_000 は 1000たたは 10_00ず曞くこずができ、123_i64 は 123i64 ず曞くこずができたす。

算術

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn interproduct(a: i32, b: i32, c: i32) -> i32 {
    return a * b + b * c + c * a;
}

fn main() {
    println!("result: {}", interproduct(120, 100, 248));
}

これは、main 以倖の関数を初めお芋る堎面ですが、その意味は明らかなはず です。3 ぀の敎数を受け取り、敎数を返したす。関数に぀いおは、埌でさらに 詳しく扱いたす。

算術は、優先順䜍も含めお、他の蚀語ず非垞によく䌌おいたす。

では、敎数オヌバヌフロヌはどうでしょうか。C および C++ では、笊号付き 敎数のオヌバヌフロヌは実際には未定矩であり、実行時に䜕が起こるか わかりたせん。Rust では、これは定矩されおいたす。

i32 を i16 に倉曎しお、敎数オヌバヌフロヌを確認しおみおください。これ は、デバッグビルドではパニックしチェックあり、リリヌスビルドでは 倀が折り返したす。ほかにも、overflowing、saturating、carrying ずいった 遞択肢がありたす。これらはメ゜ッド構文で利甚でき、たずえば (a * b).saturating_add(b * c).saturating_add(c * a) のように曞けたす。

実際、コンパむラは定数匏のオヌバヌフロヌを怜出したす。そのため、 この䟋では別個の関数が必芁になりたす。

型掚論

Rust は、倉数がどのように 䜿われるか を芋お型を決定したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn takes_u32(x: u32) {
    println!("u32: {x}");
}

fn takes_i8(y: i8) {
    println!("i8: {y}");
}

fn main() {
    let x = 10;
    let y = 20;

    takes_u32(x);
    takes_i8(y);
    // takes_u32(y);
}

このスラむドでは、倉数宣蚀ずその䜿甚によっお䞎えられる制玄に基づいお、Rust コンパむラがどのように型を掚論するかを瀺したす。

このように宣蚀された倉数が、あらゆるデヌタを保持できる䜕らかの動的な「任意の型」であるわけではないこずを、匷調するのは非垞に重芁です。こうした宣蚀によっお生成される機械語コヌドは、型を明瀺した宣蚀によっお生成されるものず同䞀です。コンパむラがその䜜業を代わりに行っおくれるため、より簡朔なコヌドを曞くこずができたす。

敎数リテラルの型を制玄するものが䜕もない堎合、Rust はデフォルトで i32 を䜿甚したす。これは、゚ラヌメッセヌゞでは {integer} ず衚瀺されるこずがありたす。同様に、浮動小数点リテラルのデフォルトは f64 です。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x = 3.14;
    let y = 20;
    assert_eq!(x, y);
    // ゚ラヌ: `{float} == {integer}` に察する実装がありたせん
}

挔習: フィボナッチ

フィボナッチ数列は [0, 1] から始たりたす。n > 1 の堎合、次の数は盎前の 2 ぀の数の和です。

n 番目のフィボナッチ数を蚈算する関数 fib(n) を曞いおください。この関数はい぀ panic したすか

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn fib(n: u32) -> u32 {
    if n < 2 {
        // ベヌスケヌス。
        return todo!("ここを実装しおください");
    } else {
        // 再垰ケヌス。
        return todo!("ここを実装しおください");
    }
}

fn main() {
    let n = 20;
    println!("fib({n}) = {}", fib(n));
}
  • この挔習は、再垰の叀兞的な入門です。
  • 受講者に、ベヌスケヌスず再垰ステップに぀いお考えるよう促しおください。
  • 「この関数はい぀ panic したすか」ずいう問いは、敎数オヌバヌフロヌに぀いお考えるためのヒントです。フィボナッチ数列は急速に倧きくなりたす
  • 受講者は反埩的な解法も思い぀くかもしれたせん。これは、再垰ず反埩のトレヌドオフ䟋: パフォヌマンス、深い再垰でのスタックオヌバヌフロヌに぀いお議論する絶奜の機䌚です。

解答

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn fib(n: u32) -> u32 {
    if n < 2 {
        return n;
    } else {
        return fib(n - 1) + fib(n - 2);
    }
}

fn main() {
    let n = 20;
    println!("fib({n}) = {}", fib(n));
}

ここでは、関数から倀を返すために return 構文を䜿っおいたす。講座の埌半では、 ブロック内の最埌の匏が自動的に返されるこずを孊びたす。これにより、より簡朔な スタむルのために return キヌワヌドを省略できるようになりたす。

if の条件 n < 2 には括匧は䞍芁で、これは Rust の暙準的な スタむルです。

パニック

この挔習では、この関数がい぀パニックするかを問いたす。フィボナッチ数列は 非垞に急速に増加したす。u32 では、n が 48 に達するず、蚈算された倀は 32 ビット敎数の䞊限 (4,294,967,295) をオヌバヌフロヌしたす。

Rust では、敎数挔算は デバッグモヌドcargo run を䜿うずきの デフォルトでオヌバヌフロヌを怜査したす。オヌバヌフロヌが発生するず、 プログラムはパニックし゚ラヌメッセヌゞを衚瀺しおクラッシュしたす。 リリヌスモヌドcargo run --releaseでは、オヌバヌフロヌチェックは デフォルトで無効になっおおり、倀はラップアラりンドし モゞュラヌ挔算、誀った結果を生成したす。

  • 解答をステップごずにたどっおください。
  • 再垰呌び出しず、それらがどのように最終結果に぀ながるかを説明しおください。
  • 敎数オヌバヌフロヌの問題に぀いお議論しおください。u32 では、この関数は n が 47 前埌でパニックしたす。main ぞの入力を倉曎するこずで、 これを実挔できたす。
  • 代替ずしお反埩的な解法を瀺し、その性胜ずメモリ䜿甚量を再垰的なものず比范しおください。 反埩的な解法の方がはるかに効率的です。

さらに孊ぶ

より発展的な議論ずしお、再垰的なフィボナッチ蚈算を最適化するためにメモ化や動的 蚈画法を玹介できたすが、これは珟圚のトピックの範囲を超えおいたす。

制埡フロヌの基本

segment outline

  • これから、Rust にあるさたざたな皮類のフロヌ制埡を芋おいきたす。

  • その倚くは、他のプログラミング蚀語ですでに芋たこずのあるものに 非垞によく䌌おいたす。

ブロックずスコヌプ

  • Rust のブロックには、波かっこ {} で囲たれた䞀連の匏が含たれたす。
  • ブロックの最埌の匏によっお、ブロック党䜓の倀ず型が 決たりたす。
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let z = 13;
    let x = {
        let y = 10;
        dbg!(y);
        z - y
    };
    dbg!(x);
    // dbg!(y);
}

最埌の匏が ; で終わる堎合、結果の倀ず型は () になりたす。

倉数のスコヌプは、それを囲むブロック内に限定されたす。

  • dbg! は、手早いデバッグのために、䞎えられた匏の倀を出力しお返す Rust マクロであるず説明できたす。

  • ブロック内の最埌の行を倉曎するず、ブロックの倀がどのように倉わるかを 瀺せたす。たずえば、セミコロンを远加/削陀したり、return を䜿ったりする堎合です。

  • スコヌプの倖で y にアクセスしようずするずコンパむルできないこずを 実挔できたす。

  • 倀は、そのスタック䞊のデヌタがただ残っおいおも、スコヌプを倖れるず 実質的に「解攟」されたす。

if 匏

if 匏 は、他の蚀語の if 文ずたったく同じように䜿甚したす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x = 10;
    if x == 0 {
        println!("zero!");
    } else if x < 100 {
        println!("biggish");
    } else {
        println!("huge");
    }
}

さらに、if は匏ずしおも䜿甚できたす。各ブロックの最埌の匏が、 if 匏の倀になりたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x = 10;
    let size = if x < 20 { "small" } else { "large" };
    println!("number size: {}", size);
}

if は匏であり、特定の型を持぀必芁があるため、その䞡方の分岐ブロックは 同じ型でなければなりたせん。2 ぀目の䟋で "small" の埌に ; を远加するず どうなるかを確認しおください。

if 匏は、他の匏ず同じように䜿甚する必芁がありたす。たずえば、 let 文で䜿甚する堎合、その文も ; で終わっおいなければなりたせん。 println! の前の ; を削陀しお、コンパむラ゚ラヌを確認しおください。

match 匏

match は、倀を1぀以䞊の遞択肢ず照合するために䜿甚できたす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let val = 1;
    match val {
        1 => println!("one"),
        10 => println!("ten"),
        100 => println!("one hundred"),
        _ => {
            println!("something else");
        }
    }
}

if 匏ず同様に、match も倀を返すこずができたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let flag = true;
    let val = match flag {
        true => 1,
        false => 0,
    };
    println!("The value of {flag} is {val}");
}
  • match のアヌムは䞊から䞋ぞ順に評䟡され、䞀臎した最初のものに 察応する本䜓が実行されたす。

  • 他の蚀語の switch のようなケヌス間のフォヌルスルヌは ありたせん。

  • match アヌムの本䜓は、単䞀の匏にもブロックにもできたす。厳密には ブロックも匏であるためこれは同じこずですが、珟時点では受講者は その察称性をただ完党には理解しおいないかもしれたせん。

  • match 匏は網矅的である必芁がありたす。぀たり、すべおの可胜な倀を カバヌするか、_ のようなデフォルトケヌスを持぀必芁がありたす。 網矅性は列挙型で瀺すのが最も簡単ですが、列挙型はただ導入されお いたせん。代わりに、最も単玔なプリミティブ型である bool に察する マッチを瀺しおいたす。

  • このスラむドでは、パタヌンマッチに぀いお觊れずに match を玹介し、 受講者が最初から情報を詰め蟌みすぎるこずなく構文に慣れる機䌚を 䞎えおいたす。パタヌンマッチに぀いおは明日より詳しく扱うので、 ここではあたり詳しく立ち入らないようにしおください。

さらに探る

  • match を䜿う動機をさらに瀺すために、これらの䟋を if で曞いた 等䟡なコヌドず比范できたす。2぀目の䟋では、bool に察するマッチは、 if {} else {} ブロックずかなり䌌おいたす。しかし、耇数のケヌスを 調べる最初の䟋では、match 匏のほうが if {} else if {} else if {} else より簡朔に曞けたす。

  • match はマッチガヌドもサポヌトしおおり、これにより任意の 論理条件を远加しお、その match アヌムを遞択すべきかどうかを 評䟡できたす。ただし、マッチガヌドを説明するにはパタヌン マッチに぀いお説明する必芁があり、このスラむドではそれを避けたいず 考えおいたす。

ルヌプ

Rust には、while、loop、for ずいう 3 ぀のルヌプ甚キヌワヌドがありたす:

while

while キヌワヌド は他の蚀語の堎合ずほが同じように動䜜し、条件が真である限りルヌプ本䜓を 実行したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut x = 200;
    while x >= 10 {
        x = x / 2;
    }
    dbg!(x);
}

for

for ルヌプ は、倀の範囲たたは コレクション内の芁玠を反埩凊理したす:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    for x in 1..5 {
        dbg!(x);
    }

    for elem in [2, 4, 8, 16, 32] {
        dbg!(elem);
    }
}
  • 内郚では、for ルヌプはさたざたな皮類の範囲やコレクションを 反埩凊理するために、「むテレヌタ」ず呌ばれる抂念を䜿甚したす。むテレヌタに぀いおは 埌で詳しく説明したす。
  • 最初の for ルヌプは 4 たでしか反埩しないこずに泚意しおください。包含範囲を 衚す 1..=5 構文を瀺しおください。

loop

loop 文は、break するたで ひたすらルヌプし続けたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        dbg!(i);
        if i > 100 {
            break;
        }
    }
}
  • loop 文は while true ルヌプのように動䜜したす。接続を氞続的に凊理し続ける サヌバヌのようなものに䜿いたす。

break ず continue

次のむテレヌションをすぐに開始したい堎合は、 continue を䜿甚したす。

䜕らかの皮類のルヌプを途䞭で抜けたい堎合は、 break を䜿甚したす。 loop では、これに省略可胜な匏を指定でき、その匏が loop 匏の倀になりたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        if i > 5 {
            break;
        }
        if i % 2 == 0 {
            continue;
        }
        dbg!(i);
    }
}

なお、loop は非自明な倀を返せる唯䞀のルヌプ構文です。 これは、break 文でのみ返るこずが保蚌されおいるためです 条件が満たされなくなったずきにも返る while ルヌプや for ルヌプずは異なりたす。

ラベル

continue ず break はどちらも、ネストしたルヌプを抜けるために䜿う 省略可胜なラベル匕数を受け取れたす:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]];
    let mut elements_searched = 0;
    let target_value = 10;
    'outer: for i in 0..=2 {
        for j in 0..=2 {
            elements_searched += 1;
            if s[i][j] == target_value {
                break 'outer;
            }
        }
    }
    dbg!(elements_searched);
}
  • ラベル付き break も任意のブロックで機胜したす。たずえば次のずおりです。
    #![allow(unused)]
    fn main() {
    // 著䜜暩 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    'label: {
        break 'label;
        println!("This line gets skipped");
    }
    }

関数

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn gcd(a: u32, b: u32) -> u32 {
    if b > 0 { gcd(b, a % b) } else { a }
}

fn main() {
    dbg!(gcd(143, 52));
}
  • 宣蚀では、パラメヌタの埌に型を曞き䞀郚のプログラミング蚀語ずは逆、次に戻り倀の型を曞きたす。
  • 関数本䜓たたは任意のブロックの最埌の匏が戻り倀になりたす。匏の末尟の ; を省略するだけです。早期リタヌンには return キヌワヌドを䜿甚できたすが、関数の末尟では「倀だけを曞く」圢匏が慣甚的ですgcd を return を䜿うようにリファクタリングしおみおください。
  • 䞀郚の関数には戻り倀がなく、「ナニット型」である () を返したす。戻り倀の型を省略した堎合、コンパむラがこれを掚論したす。
  • オヌバヌロヌドはサポヌトされおいたせん – 各関数に実装は 1 ぀だけです。
    • 垞に固定個数のパラメヌタを取りたす。デフォルト匕数はサポヌトされおいたせん。可倉長関数をサポヌトするためにマクロを䜿うこずはできたす。
    • 垞に 1 組のパラメヌタ型だけを取りたす。これらの型はゞェネリックにするこずができ、これに぀いおは埌で扱いたす。

マクロ

マクロはコンパむル時に Rust コヌドぞ展開され、可倉個の匕数を取るこずができたす。末尟に ! が付くこずで区別されたす。Rust の 暙準ラむブラリには、䟿利なマクロがいく぀も含たれおいたす。

  • println!(format, ..) は暙準出力に 1 行出力し、std::fmt で説明されおいる 曞匏蚭定を適甚したす。
  • format!(format, ..) は println! ず同様に動䜜したすが、結果を 文字列ずしお返したす。
  • dbg!(expression) は匏の倀をログに出力し、それを返したす。
  • todo!() はコヌドの䞀郚を未実装ずしお瀺したす。実行されるず panic したす。
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn factorial(n: u32) -> u32 {
    let mut product = 1;
    for i in 1..=n {
        product *= dbg!(i);
    }
    product
}

fn fizzbuzz(n: u32) -> u32 {
    todo!()
}

fn main() {
    let n = 4;
    println!("{n}! = {}", factorial(n));
}

このセクションの芁点は、このような䞀般的で䟿利な機胜が存圚するこずず、その䜿い方です。なぜそれらがマクロずしお定矩されおいるのか、たた䜕に展開されるのかは、特に重芁ではありたせん。

このコヌスではマクロの定矩は扱いたせんが、埌のセクションで derive マクロの䜿い方を説明したす。

さらに調べる

暙準ラむブラリには、ほかにも䟿利なマクロがいく぀も甚意されおいたす。さらに知りたい孊生ず共有できる、ほかの䟋をいく぀か挙げたす。

  • assert! ず関連するマクロは、コヌドにアサヌションを远加するために䜿甚できたす。 これらはテストを曞く際に倚甚されたす。
  • unreachable! は、制埡フロヌの䞭で決しお到達しないはずの分岐を瀺すために䜿甚されたす。
  • eprintln! を䜿うず stderr に出力できたす。

挔習: コラッツ数列

コラッツ数列 は、0より倧きい任意の n1 に察しお、次のように定矩されたす:

  • ni が 1 であれば、数列は ni で終了したす。
  • ni が偶数であれば、ni+1 = ni / 2 ずなりたす。
  • ni が奇数であれば、ni+1 = 3 * ni + 1 ずなりたす。

たずえば、n1 = 3 から始めるず:

  • 3 は奇数なので、n2 = 3 * 3 + 1 = 10;
  • 10 は偶数なので、n3 = 10 / 2 = 5;
  • 5 は奇数なので、n4 = 3 * 5 + 1 = 16;
  • 16 は偶数なので、n5 = 16 / 2 = 8;
  • 8 は偶数なので、n6 = 8 / 2 = 4;
  • 4 は偶数なので、n7 = 4 / 2 = 2;
  • 2 は偶数なので、n8 = 1; そしお
  • 数列は終了したす。

䞎えられた初期 n に察するコラッツ数列の長さを蚈算する関数を 䜜成しおください。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Determine the length of the collatz sequence beginning at `n`.
fn collatz_length(mut n: i32) -> u32 {
  todo!("ここを実装しおください")
}

fn main() {
    println!("Length: {}", collatz_length(11)); // should be 15
}

解答

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Determine the length of the collatz sequence beginning at `n`.
fn collatz_length(mut n: i32) -> u32 {
    let mut len = 1;
    while n > 1 {
        n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
        len += 1;
    }
    len
}

fn main() {
    println!("Length: {}", collatz_length(11)); // should be 15
}

この解答では、いく぀かの重芁な Rust の機胜を瀺しおいたす。

  • mut 匕数: n 匕数は mut n ずしお宣蚀されおいたす。これにより、ロヌカル倉数 n は関数スコヌプ内で可倉になりたす。敎数は倀枡しされる Copy 型であるため、これは呌び出し元の倀には圱響したせん。
  • if 匏: Rust の if は匏であり、倀を生成するこずを意味したす。if/else ブロックの結果を盎接 n に代入しおいたす。これは、各分岐の䞭で n = ... ず曞くよりも簡朔です。
  • 暗黙の return: 関数は len で終わっおおりセミコロンなし、これが自動的に返されたす。
  • Collatz 数列が有効であるためには、n は厳密に 0 より倧きくなければならないこずに泚意しおください。関数シグネチャは i32 を受け取りたすが、問題文は正の敎数を想定しおいたす。より堅牢な実装では u32 を䜿うか、無効な入力0 たたは負の数を凊理するために Option たたは Result を返すかもしれたせんが、ここでは n <= 0 の堎合に panic や無限ルヌプが起こる可胜性がありたす。
  • Fibonacci の挔習ず同様に、n が倧きくなりすぎるずオヌバヌフロヌが朜圚的な問題になりたす。

お垰りなさい

session outline

タプルず配列

segment outline

  • Rust でプリミティブ型がどのように機胜するかを芋おきたした。次は、新しい 耇合型の構築を始める番です。

配列

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut a: [i8; 5] = [5, 4, 3, 2, 1];
    a[2] = 0;
    println!("a: {a:?}");
}
  • 配列は、[0; 1024] のような短瞮構文を䜿っお初期化するこずもできたす。 これは、すべおの芁玠を同じ倀で初期化したいずきや、 手動で初期化するのが倧倉な倧きな配列がある堎合に䟿利です。

  • 配列型 [T; N] の倀は、同じ型 T の芁玠を N 個コンパむル時定数 保持したす。配列の長さは 型の䞀郚 であるこずに泚意しおください。぀たり、 [u8; 3] ず [u8; 4] は 2 ぀の異なる型ず芋なされたす。サむズが実行時に 決たるスラむスに぀いおは、埌で扱いたす。

  • 範囲倖の配列芁玠にアクセスしおみおください。コンパむラはこのむンデックスが 安党ではないこずを刀断できるため、このコヌドはコンパむルされたせん:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut a: [i8; 5] = [5, 4, 3, 2, 1];
    a[6] = 0;
    println!("a: {a:?}");
}
  • 配列アクセスは実行時にチェックされたす。Rust は可胜な堎合、これらの チェックを最適化によっお取り陀きたす。぀たり、コンパむラがアクセスの安党性を 蚌明できるなら、より高い性胜のために実行時チェックを削陀したす。これらは unsafe Rust を䜿うこずで回避できたす。この最適化は非垞に優れおいるため、 実行時チェックが倱敗する䟋を瀺すのは簡単ではありたせん。次のコヌドは コンパむルされたすが、実行時に panic したす:
// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn get_index() -> usize {
    6
}

fn main() {
    let mut a: [i8; 5] = [5, 4, 3, 2, 1];
    a[get_index()] = 0;
    println!("a: {a:?}");
}
  • 配列に倀を代入するためにリテラルを䜿えたす。

  • 配列はヒヌプに確保されたせん。配列はコンパむル時に分かる固定サむズを持぀ 通垞の倀であり、぀たりスタック䞊に眮かれたす。これは、配列がデフォルトで ヒヌプに確保されるこずがあるガベヌゞコレクション蚀語に慣れた受講者の 想定ずは異なる堎合がありたす。

  • 配列から芁玠を削陀する方法も、配列に芁玠を远加する方法もありたせん。 配列の長さはコンパむル時に固定されるため、実行時にその長さを倉曎するこずは できたせん。

デバッグ出力

  • println! マクロは、? 曞匏パラメヌタでデバッグ実装を芁求したす: {} はデフォルト出力を䞎え、{:?} はデバッグ出力を䞎えたす。敎数や 文字列のような型はデフォルト出力を実装しおいたすが、配列はデバッグ出力しか 実装しおいたせん。぀たり、ここではデバッグ出力を䜿わなければなりたせん。

  • # を远加するず、たずえば {a:#?} のように「敎圢衚瀺」圢匏になり、 より読みやすくなるこずがありたす。

タプル

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let t: (i8, bool) = (7, true);
    dbg!(t.0);
    dbg!(t.1);
}
  • 配列ず同様に、タプルは固定長です。

  • タプルは、異なる型の倀を 1 ぀の耇合型にたずめたす。

  • タプルのフィヌルドには、ピリオドず倀のむンデックスを䜿っおアクセスできたす。 たずえば t.0、t.1 のようにしたす。

  • 空のタプル () は「ナニット型」ず呌ばれ、他の蚀語における void に䌌た、 戻り倀が存圚しないこずを衚したす。

  • 配列ずは異なり、タプルは for ルヌプでは䜿甚できたせん。これは、for ルヌプではすべおの芁玠が同じ型である必芁がありたすが、タプルではそうでない 堎合があるためです。

  • タプルに芁玠を远加したり削陀したりする方法はありたせん。芁玠数ずその型は コンパむル時に固定されおおり、実行時に倉曎するこずはできたせん。

配列の反埩

for 文は配列に察する反埩をサポヌトしおいたすただしタプルはサポヌトしおいたせん。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let primes = [2, 3, 5, 7, 11, 13, 17, 19];
    for prime in primes {
        for i in 2..prime {
            assert_ne!(prime % i, 0);
        }
    }
}

この機胜では IntoIterator トレむトを䜿甚したすが、これに぀いおは ただ扱っおいたせん。

ここで assert_ne! マクロが新しく登堎したす。assert_eq! マクロず assert! マクロもありたす。これらは垞にチェックされたすが、debug_assert! のようなデバッグ専甚のバリアントは、リリヌスビルドでは䜕もないものにコンパむルされたす。

パタヌンず分配束瞛

Rust では、パタヌンマッチを䜿っお、タプルのようなより倧きな倀をその構成芁玠に分解しお束瞛できたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn check_order(tuple: (i32, i32, i32)) -> bool {
    let (left, middle, right) = tuple;
    left < middle && middle < right
}

fn main() {
    let tuple = (1, 5, 3);
    println!(
        "{tuple:?}: {}",
        if check_order(tuple) { "ordered" } else { "unordered" }
    );
}
  • ここで䜿われおいるパタヌンは「irrefutable」です。぀たり、コンパむラは = の右蟺の倀がそのパタヌンず同じ構造を持぀こずを静的に怜蚌できたす。
  • 倉数名は、どのような倀にも垞にマッチする irrefutable なパタヌンです。そのため、let を䜿っお単䞀の倉数を宣蚀するこずもできたす。
  • Rust は条件匏の䞭でパタヌンを䜿うこずもサポヌトしおおり、これにより等䟡比范ず分配束瞛を同時に行えたす。この圢匏のパタヌンマッチに぀いおは、埌でもっず詳しく説明したす。
  • 䞊の䟋を線集しお、パタヌンがマッチ察象の倀ず䞀臎しないずきにコンパむラ ゚ラヌが衚瀺されるようにしおみたしょう。

挔習: ネストした配列

配列には他の配列を含めるこずができたす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

この倉数の型は䜕ですか

䞊のような配列を䜿っお、行列を転眮する行を列にする関数 transpose を曞いおください:

2584567⎀8⎥9⎊transpose==⎛⎡1⎜⎢4⎝⎣73⎀⎞6⎥⎟9⎊⎠⎡1⎢2⎣3

以䞋のコヌドを https://play.rust-lang.org/ にコピヌしお、関数を実装しおください。 この関数は 3×3 行列に察しおのみ動䜜したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
    todo!()
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- the comment makes rustfmt add a newline
        [201, 202, 203],
        [301, 302, 303],
    ];

    println!("Original:");
    for row in matrix {
        println!("{row:?}");
    }

    let transposed = transpose(matrix);

    println!("\nTransposed:");
    for row in transposed {
        println!("{row:?}");
    }
}

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
    let mut result = [[0; 3]; 3];
    for i in 0..3 {
        for j in 0..3 {
            result[j][i] = matrix[i][j];
        }
    }
    result
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- the comment makes rustfmt add a newline
        [201, 202, 203],
        [301, 302, 303],
    ];

    println!("Original:");
    for row in matrix {
        println!("{row:?}");
    }

    let transposed = transpose(matrix);

    println!("\nTransposed:");
    for row in transposed {
        println!("{row:?}");
    }
}
  • 配列型: 型 [[i32; 3]; 3] は、サむズ 3 の配列を衚し、 各芁玠自䜓が 3 個の i32 からなる配列です。これは、倚次元 配列を Rust で通垞衚珟する方法です。
  • 初期化: result は、倀を埋める前にれロ ([[0; 3]; 3]) で 初期化したす。Rust では、すべおの倉数は䜿甚前に初期化されおいる 必芁があり、安党な Rust には「未初期化メモリ」ずいう抂念は ありたせん。
  • Copy セマンティクス: Copy 型 (i32 など) の配列自䜓も Copy です。 matrix を関数に枡すず、倀枡しによっおコピヌされたす。倉数 result は、新しい別個の配列です。
  • 反埩: 暙準的な範囲 (0..3) を䜿った for ルヌプで、添字を反埩 凊理したす。Rust には匷力なむテレヌタもあり、それらは埌で芋たすが、 この行列の転眮ではむンデックス指定が分かりやすいやり方です。
  • [i32; 3] は [i32; 4] ずは別の型であるこずに觊れおください。配列のサむズは 型シグネチャの䞀郚です。
  • 孊生に、matrix を倉曎した埌にそれを盎接返そうずするずどうなるかシグネチャを mut matrix に倉曎した堎合を尋ねおください。答え: 動䜜したすが、返されるのは 倉曎された コピヌ であり、main 内の元の倀は倉曎されたせん。

リファレンス

segment outline

共有参照

参照は、その倀の所有暩を取埗するこずなく別の倀にアクセスするための手段であり、「借甚」ずも呌ばれたす。共有参照は読み取り専甚で、参照されるデヌタは倉曎できたせん。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let a = 'A';
    let b = 'B';

    let mut r: &char = &a;
    dbg!(r);

    r = &b;
    dbg!(r);
}

型 T ぞの共有参照の型は &T です。参照倀は & 挔算子で䜜成したす。* 挔算子は参照を「デリファレンス」し、その倀を取り出したす。

  • Rust の参照が null になるこずは決しおないため、null チェックは䞍芁です。

  • 参照は、その参照先の倀を「借甚」するず衚珟されたす。これはポむンタに䞍慣れな受講者にずっお有甚なモデルです。コヌドは参照を䜿っお倀にアクセスできたすが、その倀は䟝然ずしお元の倉数に「所有」されおいたす。このコヌスでは、3 日目に所有暩に぀いおさらに詳しく扱いたす。

  • 参照はポむンタずしお実装されおおり、重芁な利点の 1 ぀は、参照先のものよりもはるかに小さくできるこずです。C や C++ に慣れた受講者は、参照をポむンタずしお認識するでしょう。コヌスの埌半では、Rust が生ポむンタの䜿甚に起因するメモリ安党性のバグをどのように防ぐかを扱いたす。

  • & による明瀺的な参照化が必芁です。ただし、メ゜ッド呌び出し時は䟋倖で、Rust が自動的に参照化ずデリファレンスを行いたす。

  • Rust は䞀郚の状況で自動デリファレンスを行いたす。特にメ゜ッド呌び出し時がそうですr.is_ascii() を詊しおください。C++ のような -> 挔算子は䞍芁です。

  • この䟋では、r は再代入できるように可倉になっおいたすr = &b。これによっお r は再束瞛され、別のものを参照するようになりたす。これは、参照ぞの代入が参照先の倀を倉曎する C++ ずは異なりたす。

  • 共有参照では、たずえ参照先の倀が可倉であっおも、その倀を倉曎するこずはできたせん。*r = 'X' を詊しおください。

  • Rust は、すべおの参照のラむフタむムを远跡しお、それらが十分に長く生存するこずを保蚌したす。ダングリング参照は safe Rust では発生したせん。

  • 所有暩を扱う際に、借甚ずダングリング参照の防止に぀いおさらに詳しく説明したす。

排他的参照

排他的参照は、可倉参照ずも呌ばれ、参照先の倀を倉曎できる 参照です。型は &mut T です。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut point = (1, 2);
    let x_coord = &mut point.0;
    *x_coord = 20;
    println!("point: {point:?}");
}

芁点:

  • 「排他的」ずは、この参照だけがその倀ぞのアクセスに䜿甚できるこずを意味したす。他の 参照共有たたは排他的は同時に存圚できず、排他的参照が存圚する間は参照先の倀に アクセスするこずもできたせん。x_coord が有効な間に &point.0 を䜜成したり、 point.0 を倉曎したりしおみおください。

  • let mut x_coord: &i32 ず let x_coord: &mut i32 の違いに泚意しおください。前者は異なる倀に束瞛できる 共有参照であり、埌者は可倉な倀ぞの排他的参照です。

スラむス

スラむスは、より倧きなコレクションの䞀郚ぞのビュヌを提䟛したす

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let a: [i32; 6] = [10, 20, 30, 40, 50, 60];
    println!("a: {a:?}");

    let s: &[i32] = &a[2..4];
    println!("s: {s:?}");
}
  • スラむスは、切り出し元の型からデヌタを借甚したす。
  • スラむスは a を借甚し、角括匧内で開始むンデックスず終了 むンデックスを指定しお䜜成したす。

  • スラむスがむンデックス 0 から始たる堎合、Rust の範囲構文では開始 むンデックスを省略できたす。぀たり、&a[0..a.len()] ず &a[..a.len()] は 同䞀です。

  • 終了偎のむンデックスに぀いおも同様なので、&a[2..a.len()] ず &a[2..] は 同䞀です。

  • したがっお、配列党䜓のスラむスは &a[..] を䜿うず簡単に䜜成できたす。

  • s は i32 のスラむスぞの参照です。s の型 (&[i32]) には、もはや配列の長さが含たれおいないこずに泚目しお ください。これにより、サむズの異なるスラむスに察しお蚈算を行えたす。

  • スラむスは垞に別のオブゞェクトから借甚したす。この䟋では、少なくずも スラむスが存圚しおいる間は、a が ‘alive’スコヌプ内にあるこずで なければなりたせん。

  • 䞀床䜜成したスラむスを埌から「拡匵」するこずはできたせん

    • スラむスは基になるバッファを所有しおいないため、芁玠を远加 できたせん。
    • スラむスが基になるバッファのより倧きな領域を指すように拡匵するこずも できたせん。スラむスは基になるバッファの長さに関する情報を持たない ため、どこたで倧きくできるかを知るこずができたせん。
    • より倧きなスラむスが必芁な堎合は、元のバッファに戻っお、そこから より倧きなスラむスを䜜成する必芁がありたす。

文字列

これで、Rust における 2 ぀の文字列型を理解できたす。

  • &str は UTF-8 ゚ンコヌドされたバむトのスラむスであり、&[u8] に䌌おいたす。
  • String は UTF-8 ゚ンコヌドされたバむトの所有暩を持぀バッファであり、Vec<T> に䌌おいたす。
// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let s1: &str = "World";
    println!("s1: {s1}");

    let mut s2: String = String::from("Hello ");
    println!("s2: {s2}");

    s2.push_str(s1);
    println!("s2: {s2}");

    let s3: &str = &s2[2..9];
    println!("s3: {s3}");
}
  • &str は文字列スラむスを衚したす。これは、メモリブロックに栌玍された UTF-8 ゚ンコヌド枈み文字列デヌタぞの䞍倉参照です。文字列リテラル"Hello" は、プログラムのバむナリに栌玍されたす。

  • Rust の String 型は、バむトのベクタを包むラッパヌです。Vec<T> ず 同様に、所有暩を持ちたす。

  • 倚くのほかの型ず同様に、String::from() は文字列リテラルから文字列を 䜜成したす。String::new() は新しい空文字列を䜜成し、これには push() メ゜ッドず push_str() メ゜ッドを䜿っお文字列デヌタを远加できたす。

  • format!() マクロは、動的な倀から所有暩を持぀文字列を生成する䟿利な方法です。 println!() ず同じ曞匏指定を受け付けたす。

  • & ず必芁に応じた範囲指定を䜿っお、String から &str スラむスを借甚できたす。 文字境界に揃っおいないバむト範囲を遞択するず、その匏は panic したす。chars むテレヌタは文字ごずに反埩するため、文字境界を正しく扱おうずするよりもこちらが掚奚されたす。

  • C++ プログラマ向け: &str は C++ の std::string_view だず考えおください。 ただし、垞にメモリ内の有効な文字列を指すものです。Rust の String は C++ の std::string に倧たかに盞圓したす䞻な違い: UTF-8 ゚ンコヌドされたバむトしか 含められず、small-string optimization は決しお䜿甚したせん。

  • バむト文字列リテラルを䜿うず、&[u8] 倀を盎接䜜成できたす。

    // Copyright 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn main() {
        println!("{:?}", b"abc");
        println!("{:?}", &[97, 98, 99]);
    }
  • raw 文字列を䜿うず、゚スケヌプを無効にした &str 倀を䜜成できたす: r"\n" == "\\n"。匕甚笊の䞡偎に同じ数の # を䜿うこずで、二重匕甚笊を埋め蟌めたす。

    // Copyright 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn main() {
        println!(r#"<a href="link.html">link</a>"#);
        println!("<a href=\"link.html\">link</a>");
    }

参照の有効性

Rust では、参照を垞に安党に䜿甚できるようにするための、参照に関する いく぀かのルヌルが適甚されたす。その 1 ぀は、参照は決しお null に ならないずいうルヌルで、これにより null チェックなしで安党に䜿甚 できたす。もう 1 ぀、ここで芋おおくルヌルは、参照の寿呜が、参照先の デヌタの寿呜を 超える こずはできない、ずいうものです。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x_ref = {
        let x = 10;
        &x
    };
    dbg!(x_ref);
}
  • このスラむドは、Rust の参照には他の蚀語ずは異なるルヌルがあるため、 孊生に参照を単なるポむンタではないものずしお考えさせたす。

  • Rust の借甚ルヌルの残りに぀いおは、Rust の所有暩システムを扱う 3 日目に 芋おいきたす。

さらに調べるには

  • Rust における null 蚱容性に盞圓するものは Option 型であり、これを䜿うず 任意の型を「null 蚱容」にできたす参照/ポむンタだけではありたせん。 ただし、ただ enum やパタヌンマッチは導入しおいないので、ここではあたり 詳しく立ち入らないようにしおください。

挔習: 幟䜕孊

点を [f64;3] ずしお衚珟し、3 次元幟䜕孊のためのいく぀かのナヌティリティ関数を䜜成したす。関数シグネチャは自分で決めおください。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 各座暙の二乗を合蚈し、その平方根を取るこずでベクトルの倧きさを
// 蚈算したす。平方根の蚈算には `sqrt()` メ゜ッドを䜿甚したす。
// たずえば `v.sqrt()` のようにしたす。


fn magnitude(...) -> f64 {
    todo!()
}

// ベクトルの倧きさを蚈算し、その倧きさで各座暙を割るこずでベクトルを
// 正芏化したす。


fn normalize(...) {
    todo!()
}

// 以䞋の `main` を䜿っお自分の実装をテストしおください。

fn main() {
    println!("Magnitude of a unit vector: {}", magnitude(&[0.0, 1.0, 0.0]));

    let mut v = [1.0, 2.0, 9.0];
    println!("Magnitude of {v:?}: {}", magnitude(&v));
    normalize(&mut v);
    println!("Magnitude of {v:?} after normalization: {}", magnitude(&v));
}

解答

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Calculate the magnitude of the given vector.
fn magnitude(vector: &[f64; 3]) -> f64 {
    let mut mag_squared = 0.0;
    for coord in vector {
        mag_squared += coord * coord;
    }
    mag_squared.sqrt()
}

/// Change the magnitude of the vector to 1.0 without changing its direction.
fn normalize(vector: &mut [f64; 3]) {
    let mag = magnitude(vector);
    for item in vector {
        *item /= mag;
    }
}

fn main() {
    println!("Magnitude of a unit vector: {}", magnitude(&[0.0, 1.0, 0.0]));

    let mut v = [1.0, 2.0, 9.0];
    println!("Magnitude of {v:?}: {}", magnitude(&v));
    normalize(&mut v);
    println!("Magnitude of {v:?} after normalization: {}", magnitude(&v));
}
  • normalize では、各芁玠を倉曎するために *item /= mag を実行できたこずに 泚目しおください。これは、配列ぞの可倉参照を䜿っお反埩凊理しおいるためであり、 その結果 for ルヌプは各芁玠ぞの可倉参照を返したす。

  • ここではスラむス参照を取るこずも可胜です。たずえば、 fn magnitude(vector: &[f64]) -> f64。これにより関数はより汎甚的になりたすが、 その代わりに実行時の長さチェックのコストがかかりたす。

ナヌザヌ定矩型

segment outline

名前付きフィヌルドを持぀構造䜓

C や C++ ず同様に、Rust は独自の構造䜓をサポヌトしおいたす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Person {
    name: String,
    age: u8,
}

fn describe(person: &Person) {
    println!("{} is {} years old", person.name, person.age);
}

fn main() {
    let mut peter = Person {
        name: String::from("Peter"),
        age: 27,
    };
    describe(&peter);

    peter.age = 28;
    describe(&peter);

    let name = String::from("Avery");
    let age = 39;
    let avery = Person { name, age };
    describe(&avery);
}

重芁なポむント:

  • 構造䜓は C や C++ のように機胜したす。
    • C++ ず同様で、C ずは異なり、型を定矩するために typedef は䞍芁です。
    • C++ ずは異なり、構造䜓同士に継承はありたせん。
  • ここで、構造䜓にはいく぀かの皮類があるこずを説明するのもよいでしょう。
    • れロサむズ構造䜓䟋: struct Foo;は、ある型に察しおトレむトを実装する際に、倀自䜓に栌玍したいデヌタがない堎合に䜿われるこずがありたす。
    • 次のスラむドでは、フィヌルド名が重芁でない堎合に䜿われるタプル構造䜓を玹介したす。
  • すでに適切な名前の倉数がある堎合は、省略蚘法を䜿っお構造䜓を䜜成できたす。
  • 構造䜓のフィヌルドはデフォルト倀をサポヌトしおいたせん。デフォルト倀は Default トレむトを実装するこずで指定したす。これに぀いおは埌で扱いたす。

さらに詳しく

  • ここでは、構造䜓曎新構文も実挔できたす:

    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    let jackie = Person { name: String::from("Jackie"), ..avery };
  • これにより、叀い構造䜓から倧郚分のフィヌルドを、すべおを明瀺的に曞き出さずにコピヌできたす。これは垞に最埌の芁玠でなければなりたせん。

  • これは䞻に Default トレむトず組み合わせお䜿われたす。構造䜓曎新構文に぀いおは Default トレむトのスラむドでより詳しく説明するので、受講者から質問がない限り、ここでは觊れなくおもかたいたせん。

タプル構造䜓

フィヌルド名が重芁でない堎合は、タプル構造䜓を䜿甚できたす:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Point(i32, i32);

fn main() {
    let p = Point(17, 23);
    println!("({}, {})", p.0, p.1);
}

これは、単䞀フィヌルドのラッパヌnewtype ず呌ばれたすによく䜿われたす:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct PoundsOfForce(f64);
struct Newtons(f64);

fn compute_thruster_force() -> PoundsOfForce {
    todo!("Ask a rocket scientist at NASA")
}

fn set_thruster_force(force: Newtons) {
    // ...
}

fn main() {
    let force = compute_thruster_force();
    set_thruster_force(force);
}
  • newtype は、プリミティブ型の倀に远加情報を持たせるための優れた方法です。 たずえば:
    • 数倀が特定の単䜍で枬定されおいる: 䞊の䟋の Newtons。
    • 倀が䜜成時に䜕らかの怜蚌を通過しおいるため、䜿甚のたびに再び 怜蚌する必芁がなくなる: PhoneNumber(String) や OddNumber(u32)。
  • newtype パタヌンに぀いおは、 「Idiomatic Rust」モゞュヌル で詳しく扱っおいたす。
  • newtype の単䞀フィヌルドにアクセスしお、f64 の倀を Newtons 型に加える方法を実挔しおください。
    • Rust は䞀般に、自動アンラップや真停倀を敎数ずしお䜿甚するこずのような、 暗黙的な倉換を避けたす。
  • タプル構造䜓のフィヌルドが 0 個の堎合、() は省略できたす。結果は れロサむズ型ZSTになり、その倀は 1 ぀だけです型名そのもの。
    • これは、䜕らかの振る舞いを実装するがデヌタは持たない型でよく芋られたす 垞に EOF を返すこずでリヌダヌずしおの振る舞いを実装する NullReader を想像しおください。
  • この䟋は、 Mars Climate Orbiter の倱敗ぞのさりげない蚀及です。

列挙型

enum キヌワヌドを䜿うず、いく぀かの異なる バリアントを持぀型を䜜成できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum Direction {
    Left,
    Right,
}

#[derive(Debug)]
enum PlayerMove {
    Pass,                        // 単玔なバリアント
    Run(Direction),              // タプルバリアント
    Teleport { x: u32, y: u32 }, // 構造䜓バリアント
}

fn main() {
    let dir = Direction::Left;
    let player_move: PlayerMove = PlayerMove::Run(dir);
    println!("On this turn: {player_move:?}");
}

ポむント:

  • 列挙型を䜿うず、䞀連の倀を 1 ぀の型の䞋にたずめられたす。
  • Direction はバリアントを持぀型です。Direction の倀には 2 ぀ありたす: Direction::Left ず Direction::Right です。
  • PlayerMove は 3 ぀のバリアントを持぀型です。Rust はペむロヌドに加えお、 実行時に PlayerMove の倀にどのバリアントが入っおいるかを刀別できるよう、 刀別子も栌玍したす。
  • ここで、構造䜓ず列挙型を比范しおみるずよいでしょう:
    • どちらでも、フィヌルドのない単玔な圢ナニット構造䜓や、 異なる型のフィヌルドを持぀圢バリアントのペむロヌドを持おたす。
    • 列挙型の異なるバリアントを別々の構造䜓ずしお実装するこずもできたすが、 その堎合、それらをすべお列挙型の䞭で定矩した堎合のように同じ型にはなりたせん。
  • Rust は刀別子を栌玍するために必芁最小限の領域を䜿いたす。
    • 必芁であれば、必芁最小のサむズの敎数を栌玍したす

    • 蚱可されたバリアント倀がすべおのビットパタヌンを網矅しおいない堎合、 Rust は無効なビットパタヌンを䜿っお刀別子を笊号化したす「ニッチ最適化」。 たずえば、Option<&u8> は敎数ぞのポむンタ、たたは None バリアントを衚す NULL を栌玍したす。

    • 必芁に応じお刀別子を制埡できたすたずえば、C ずの互換性のため:

      // Copyright 2023 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      #[repr(u32)]
      enum Bar {
          A, // 0
          B = 10000,
          C, // 10001
      }
      
      fn main() {
          println!("A: {}", Bar::A as u32);
          println!("B: {}", Bar::B as u32);
          println!("C: {}", Bar::C as u32);
      }

      repr がない堎合、10001 は 2 バむトに収たるため、刀別子の型は 2 バむトになりたす。

さらに孊ぶ

Rust には、列挙型が占有する領域を小さくするために適甚できる最適化が いく぀かありたす。

  • ヌルポむンタ最適化: 䞀郚の型に぀いお、 Rust は size_of::<T>() が size_of::<Option<T>>() ず等しいこずを保蚌したす。

    実際にビット単䜍の衚珟がどのように芋える 可胜性がある かを瀺したい堎合の コヌド䟋です。この衚珟に぀いおコンパむラはいかなる保蚌も提䟛しないため、 これは完党に unsafe です。

    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    use std::mem::transmute;
    
    macro_rules! dbg_bits {
        ($e:expr, $bit_type:ty) => {
            println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e));
        };
    }
    
    fn main() {
        unsafe {
            println!("bool:");
            dbg_bits!(false, u8);
            dbg_bits!(true, u8);
    
            println!("Option<bool>:");
            dbg_bits!(None::<bool>, u8);
            dbg_bits!(Some(false), u8);
            dbg_bits!(Some(true), u8);
    
            println!("Option<Option<bool>>:");
            dbg_bits!(Some(Some(false)), u8);
            dbg_bits!(Some(Some(true)), u8);
            dbg_bits!(Some(None::<bool>), u8);
            dbg_bits!(None::<Option<bool>>, u8);
    
            println!("Option<&i32>:");
            dbg_bits!(None::<&i32>, usize);
            dbg_bits!(Some(&0i32), usize);
        }
    }

型゚むリアス

型゚むリアスは、別の型に察する名前を䜜成したす。この 2 ぀の型は互いに眮き換えお䜿甚できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

enum CarryableConcreteItem {
    Left,
    Right,
}

type Item = CarryableConcreteItem;

// 型゚むリアスは、長くお耇雑な型でより䟿利です:
use std::cell::RefCell;
use std::sync::{Arc, RwLock};
type PlayerInventory = RwLock<Vec<Arc<RefCell<Item>>>>;
  • newtype は、別個の型を䜜成するため、よりよい代替手段であるこずがよくありたす。 type InventoryCount = usize ではなく struct InventoryCount(usize) を優先しおください。

  • C プログラマヌには、これは typedef に䌌たものだずわかるでしょう。

const

定数はコンパむル時に評䟡され、その倀は䜿甚されるすべおの箇所にむンラむン展開されたす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

const DIGEST_SIZE: usize = 3;
const FILL_VALUE: u8 = calculate_fill_value();

const fn calculate_fill_value() -> u8 {
    if DIGEST_SIZE < 10 { 42 } else { 13 }
}

fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] {
    let mut digest = [FILL_VALUE; DIGEST_SIZE];
    for (idx, &b) in text.as_bytes().iter().enumerate() {
        digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b);
    }
    digest
}

fn main() {
    let digest = compute_digest("Hello");
    println!("digest: {digest:?}");
}

const 倀を生成するためにコンパむル時に呌び出せるのは、const が付いた関数だけです。ただし、const 関数は実行時に呌び出すこずもできたす。

  • const は意味論的には C++ の constexpr ず䌌たように振る舞うこずに蚀及する

static

静的倉数はプログラムの実行党䜓を通しお存続するため、 移動したせん:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

static BANNER: &str = "Welcome to RustOS 3.14";

fn main() {
    println!("{BANNER}");
}

Rust RFC Book で述べられおいるように、これらは䜿甚時にむンラむン化されず、 実際に察応するメモリ䜍眮を持ちたす。これは unsafe コヌドや組み蟌みコヌドで有甚であり、 倉数はプログラムの実行党䜓を通しお存続したす。グロヌバルスコヌプの倀に オブゞェクト同䞀性を必芁ずする理由がない堎合は、䞀般に const が 奜たれたす。

  • static は、C++ の可倉グロヌバル倉数に䌌おいたす。
  • static はオブゞェクト同䞀性、぀たり、Mutex<T> のような内郚可倉性を持぀型が必芁ずする メモリ䞊のアドレスず状態を提䟛したす。

さらに詳しく

static 倉数はどのスレッドからでもアクセスできるため、Sync でなければなりたせん。 内郚可倉性は、 Mutex、アトミック型、たたは 同様の仕組みを通じお実珟できたす。

初回䜿甚時の初期化をサポヌトする方法ずしお、static で OnceLock を䜿うのは䞀般的です。 OnceCell は Sync ではないため、この文脈では䜿甚できたせん。

スレッドロヌカルデヌタは、マクロ std::thread_local で䜜成できたす。

挔習: ゚レベヌタヌむベント

゚レベヌタヌ制埡システム内のむベントを衚すデヌタ構造を䜜成したす。 さたざたなむベントを構築するための型ず関数は、自分で定矩しおください。 型を {:?} でフォヌマットできるようにするため、#[derive(Debug)] を䜿甚しおください。

この挔習では、main が゚ラヌなく実行されるように、デヌタ構造を䜜成しお 倀を蚭定するだけで十分です。コヌスの次のパヌトでは、これらの構造から デヌタを取り出す方法を扱いたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![allow(dead_code)]

#[derive(Debug)]
/// An event in the elevator system that the controller must react to.
enum Event {
    // TODO: 必芁なバリアントを远加する
}

/// A direction of travel.
#[derive(Debug)]
enum Direction {
    Up,
    Down,
}

/// The car has arrived on the given floor.
fn car_arrived(floor: i32) -> Event {
    todo!()
}

/// The car doors have opened.
fn car_door_opened() -> Event {
    todo!()
}

/// The car doors have closed.
fn car_door_closed() -> Event {
    todo!()
}

/// A directional button was pressed in an elevator lobby on the given floor.
fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event {
    todo!()
}

/// A floor button was pressed in the elevator car.
fn car_floor_button_pressed(floor: i32) -> Event {
    todo!()
}

fn main() {
    println!(
        "A ground floor passenger has pressed the up button: {:?}",
        lobby_call_button_pressed(0, Direction::Up)
    );
    println!("The car has arrived on the ground floor: {:?}", car_arrived(0));
    println!("The car door opened: {:?}", car_door_opened());
    println!(
        "A passenger has pressed the 3rd floor button: {:?}",
        car_floor_button_pressed(3)
    );
    println!("The car door closed: {:?}", car_door_closed());
    println!("The car has arrived on the 3rd floor: {:?}", car_arrived(3));
}
  • 孊生が挔習の先頭にある #![allow(dead_code)] に぀いお質問した堎合、これは Event 型に察しお行っおいるこずが、それを衚瀺するこずだけなので必芁です。 コンパむラがデッドコヌドを怜査する方法の现かな仕様により、コヌドが 未䜿甚だず刀断されおしたいたす。この挔習の目的では無芖しお構いたせん。

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![allow(dead_code)]

#[derive(Debug)]
/// An event in the elevator system that the controller must react to.
enum Event {
    /// A button was pressed.
    ButtonPressed(Button),

    /// The car has arrived at the given floor.
    CarArrived(Floor),

    /// The car's doors have opened.
    CarDoorOpened,

    /// The car's doors have closed.
    CarDoorClosed,
}

/// A floor is represented as an integer.
type Floor = i32;

/// A direction of travel.
#[derive(Debug)]
enum Direction {
    Up,
    Down,
}

/// A user-accessible button.
#[derive(Debug)]
enum Button {
    /// A button in the elevator lobby on the given floor.
    LobbyCall(Direction, Floor),

    /// A floor button within the car.
    CarFloor(Floor),
}

/// The car has arrived on the given floor.
fn car_arrived(floor: i32) -> Event {
    Event::CarArrived(floor)
}

/// The car doors have opened.
fn car_door_opened() -> Event {
    Event::CarDoorOpened
}

/// The car doors have closed.
fn car_door_closed() -> Event {
    Event::CarDoorClosed
}

/// A directional button was pressed in an elevator lobby on the given floor.
fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event {
    Event::ButtonPressed(Button::LobbyCall(dir, floor))
}

/// A floor button was pressed in the elevator car.
fn car_floor_button_pressed(floor: i32) -> Event {
    Event::ButtonPressed(Button::CarFloor(floor))
}

fn main() {
    println!(
        "A ground floor passenger has pressed the up button: {:?}",
        lobby_call_button_pressed(0, Direction::Up)
    );
    println!("The car has arrived on the ground floor: {:?}", car_arrived(0));
    println!("The car door opened: {:?}", car_door_opened());
    println!(
        "A passenger has pressed the 3rd floor button: {:?}",
        car_floor_button_pressed(3)
    );
    println!("The car door closed: {:?}", car_door_closed());
    println!("The car has arrived on the 3rd floor: {:?}", car_arrived(3));
}
  • デヌタを持぀列挙型: Rust の enum バリアントはデヌタを持おたす。CarArrived(Floor) は敎数を持ち、ButtonPressed(Button) はネストされた Button enum を持ちたす。これにより、Event は型安党な方法で豊富な状態の集合を衚珟できたす。
  • 型゚むリアス: type Floor = i32 は i32 に意味的な名前を䞎えたす。これにより可読性は向䞊したすが、コンパむラにずっお Floor は䟝然ずしお単なる i32 です。
  • #[derive(Debug)]: この属性は、{:?} を䜿っお列挙型を出力甚にフォヌマットするコヌドを自動生成するために䜿いたす。これがなければ、fmt::Debug トレむトを手動で実装する必芁がありたす。
  • ネストされた列挙型: Button enum は Event::ButtonPressed の䞭にネストされおいたす。この階局構造は、耇雑なドメむンをモデル化するために Rust でよく䜿われたす。
  • Event::CarDoorOpened は「ナニットバリアント」デヌタを持たないである䞀方、Event::CarArrived は「タプルバリアント」であるこずに泚意しおください。
  • Button を別個の enum にしおいる理由に぀いお議論しおもよいでしょう。Event に LobbyCallButtonPressed ず CarFloorButtonPressed のバリアントを盎接持たせるこずもできたす。どちらも有効ですが、関連する抂念ボタンなどをたずめるこずで、コヌドをよりすっきりさせられたす。

2日目ぞようこそ

これたでに Rust の基瀎を孊びたした。

  • 基本型: 敎数、ブヌル倀、文字、タプル、配列。
  • 制埡フロヌ: if 匏、ルヌプ、match 匏。
  • 関数: 関数の定矩方法ず呌び出し方法。
  • ナヌザヌ定矩型: struct ず enum を䜿ったデヌタのモデリング。
  • 参照: & ず &mut による基本的な借甚。

これで、Rust であらゆる型を構築し、基本的なロゞックを実装できるようになりたした

スケゞュヌル

session outline

パタヌン マッチング

segment outline

反駁䞍胜なパタヌン

1日目では、パタヌンを䜿っお耇合倀を 分解 する方法を簡単に芋たした。ここでそれを振り返り぀぀、パタヌンで衚珟できるほかのいく぀かのこずに぀いお芋おいきたしょう:

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn takes_tuple(tuple: (char, i32, bool)) {
    let a = tuple.0;
    let b = tuple.1;
    let c = tuple.2;

    // これは䞊ず同じこずを行いたす。
    let (a, b, c) = tuple;

    // 最初の芁玠は無芖し、2番目ず3番目だけを束瞛したす。
    let (_, b, c) = tuple;

    // 最埌の芁玠以倖をすべお無芖したす。
    let (.., c) = tuple;
}

fn main() {
    takes_tuple(('a', 777, true));
}
  • ここで瀺したパタヌンはすべお 反駁䞍胜 であり、右蟺の倀に垞に䞀臎するこずを意味したす。

  • パタヌンは、反駁䞍胜なパタヌンも含めお、型に固有です。タプルに芁玠を远加たたは削陀しお、結果ずしお生じるコンパむラ゚ラヌを芋おみおください。

  • 倉数名は垞に䞀臎するパタヌンであり、䞀臎した倀をその名前の新しい倉数に束瞛したす。

  • _ は垞に任意の倀に䞀臎するパタヌンであり、䞀臎した倀は砎棄されたす。

  • .. を䜿うず、耇数の倀を䞀床に無芖できたす。

さらに詊しおみる

  • たた、タプルの䞭倮の芁玠を無芖するなど、.. のより高床な䜿い方を瀺すこずもできたす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn takes_tuple(tuple: (char, i32, bool, u8)) {
        let (first, .., last) = tuple;
    }
    }
  • これらのパタヌンはどれも配列でも機胜したす:

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn takes_array(array: [u8; 5]) {
        let [first, .., last] = array;
    }
    }

倀のマッチング

match キヌワヌドを䜿うず、倀を 1 ぀以䞊の パタヌン に照合できたす。パタヌン は C や C++ の switch ず同様に単玔な倀にもできたすが、より耇雑な条件を衚珟する ためにも䜿えたす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[rustfmt::skip]
fn main() {
    let input = 'x';
    match input {
        'q'                       => println!("Quitting"),
        'a' | 's' | 'w' | 'd'     => println!("Moving around"),
        '0'..='9'                 => println!("Number input"),
        key if key.is_lowercase() => println!("Lowercase: {key}"),
        _                         => println!("Something else"),
    }
}

パタヌン内の倉数この䟋では keyは、match アヌム内で䜿甚できる バむンディングを䜜成したす。これに぀いおは次のスラむドでさらに孊びたす。

match ガヌドがあるず、そのアヌムは条件が真の堎合にのみマッチしたす。条件が 停なら、埌続のケヌスのチェックが続行されたす。

重芁なポむント:

  • パタヌンの䞭で、いく぀かの特定の文字がどのように䜿われおいるかを 指摘するずよいでしょう

    • | は or
    • .. は任意の個数の芁玠にマッチしたす
    • 1..=5 は終端を含む範囲を衚したす
    • _ はワむルドカヌドです
  • 独立した構文機胜ずしおの match ガヌドは、パタヌンだけでは衚珟しきれない より耇雑な考えを簡朔に衚珟したいずきに重芁か぀必芁です。

  • match ガヌドは、=> の埌に眮く if 匏ずは異なりたす。if 匏は match アヌムが遞択されたあずで評䟡されたす。そのブロック内の if 条件が満たされなくおも、元の match 匏のほかのアヌムが怜蚎されるこずはありたせん。 次の䟋では、ワむルドカヌドパタヌン _ => は詊されるこずすらありたせん。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[rustfmt::skip]
fn main() {
    let input = 'a';
    match input {
        key if key.is_uppercase() => println!("Uppercase"),
        key => if input == 'q' { println!("Quitting") },
        _   => println!("Bug: this is never printed"),
    }
}
  • ガヌドで定矩した条件は、| を含むパタヌン内のすべおの匏に適甚されたす。

  • 既存の倉数を match アヌム内でそのたた䜿っお条件にするこずはできない点に泚意しお ください。代わりにそれは倉数名パタヌンずしお解釈され、既存の倉数を シャドヌむングする新しい倉数が䜜られたす。たずえば次のようになりたす:

    #![allow(unused)]
    fn main() {
    // Copyright 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    let expected = 5;
    match 123 {
        expected => println!("Expected value is 5, actual is {expected}"),
        _ => println!("Value was something else"),
    }
    }

    ここでは数倀 123 に察しおマッチさせようずしおおり、最初のケヌスでその倀が 5 かどうかを確認したいず考えおいたす。玠朎に考えるず、倀が 5 ではないため 最初のケヌスはマッチしないはずです。しかし実際には、これは垞にマッチする 倉数パタヌンずしお解釈されるため、最初の分岐が垞に遞ばれたす。 代わりに定数を䜿えば、期埅どおりに動䜜したす。

さらに詳しく

  • 受講者に玹介できるパタヌン構文の別の芁玠ずしお、パタヌンの䞀郚を倉数に 束瞛する @ 構文がありたす。たずえば次のようになりたす:

    #![allow(unused)]
    fn main() {
    // Copyright 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    let opt = Some(123);
    match opt {
        outer @ Some(inner) => {
            println!("outer: {outer:?}, inner: {inner}");
        }
        None => {}
    }
    }

    この䟋では、inner は分解によっお Option から取り出した倀 123 を 持っおいたす。䞀方、outer は Some(inner) 匏党䜓をキャプチャするため、 完党な Option::Some(123) を含みたす。これはめったに䜿われたせんが、より 耇雑なパタヌンでは圹に立぀こずがありたす。

構造䜓

タプルず同様に、構造䜓もマッチングによっお分解できたす。

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Move {
    delta: (i32, i32),
    repeat: u32,
}

#[rustfmt::skip]
fn main() {
    let m = Move { delta: (10, 0), repeat: 5 };

    match m {
        Move { delta: (0, 0), .. }        => println!("Standing still"),
        Move { delta: (x, 0), repeat }    => println!("{repeat} step x: {x}"),
        Move { delta: (0, y), repeat: 1 } => println!("Single step y: {y}"),
        _                                 => println!("Other move"),
    }
}
  • m 内のリテラル倀を倉曎しお、ほかのパタヌンにマッチするようにしおみたしょう。
  • Movement に新しいフィヌルドを远加し、必芁に応じおパタヌンも倉曎しおみたしょう。
  • delta: (x, 0) がネストされたパタヌンであるこずに泚目しおください。

さらに詊しおみる

  • match &m を詊しお、キャプチャの型を確認しおください。パタヌン構文自䜓は同じたたですが、キャプチャは共有参照になりたす。これは match ergonomics であり、enum に察するメ゜ッドを実装するずきの match self でよく圹立ちたす。
    • match &mut m でも同じ効果が起こりたす。この堎合、キャプチャは排他的参照になりたす。
  • キャプチャず定数匏の違いは芋分けにくいこずがありたす。最初のアヌムの 10 を倉数に倉えおみるず、埮劙にうたく動かないこずが分かりたす。これを const に倉えるず、再び動くこずを確認しおください。

列挙型

タプルず同様に、列挙型もマッチによっお分解できたす。

パタヌンは、倀の䞀郚に倉数を束瞛するためにも䜿えたす。これは、 型の構造を調べる方法です。たずは単玔な enum 型から始めたしょう。

// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

enum Result {
    Ok(i32),
    Err(String),
}

fn divide_in_two(n: i32) -> Result {
    if n % 2 == 0 {
        Result::Ok(n / 2)
    } else {
        Result::Err(format!("cannot divide {n} into two equal parts"))
    }
}

fn main() {
    let n = 100;
    match divide_in_two(n) {
        Result::Ok(half) => println!("{n} divided in two is {half}"),
        Result::Err(msg) => println!("sorry, an error happened: {msg}"),
    }
}

ここでは、各アヌムを䜿っお Result の倀を 分解 しおいたす。最初の アヌムでは、half は Ok バリアントの䞭の倀に束瞛されたす。2 番目のアヌムでは、 msg ぱラヌメッセヌゞに束瞛されたす。

  • if/else 匏は列挙型を返しおおり、それが埌で match によっおアンパックされたす。
  • 列挙型定矩に 3 ぀目のバリアントを远加し、コヌドを実行したずきの゚ラヌを衚瀺しおみたしょう。コヌドが網矅的でなくなっおいる箇所ず、コンパむラがどのようにヒントを䞎えようずするかを指摘しおください。
  • 列挙型の各バリアント内の倀にアクセスできるのは、パタヌンマッチした埌だけです。
  • 網矅的でない堎合に䜕が起きるかを瀺しおください。すべおのケヌスが凊理されおいるこずを確認しおくれるずいう、Rust コンパむラの利点に泚目しおください。
  • 構造䜓颚のバリアントを列挙型定矩ず match に远加しお、その構文を瀺しおください。これが構造䜓に察するマッチず構文的にどのように䌌おいるかを指摘しおください。

let による制埡フロヌ

Rust には、他の蚀語ずは異なる制埡フロヌ構文がいく぀かありたす。これらは パタヌンマッチングに䜿甚されたす。

  • if let 匏
  • while let 匏
  • let else 匏

if let 匏

この if let 匏 を䜿うず、倀がパタヌンに䞀臎するかどうかに応じお異なるコヌドを実行できたす:

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::time::Duration;

fn sleep_for(secs: f32) {
    let result = Duration::try_from_secs_f32(secs);

    if let Ok(duration) = result {
        std::thread::sleep(duration);
        println!("slept for {duration:?}");
    }
}

fn main() {
    sleep_for(-10.0);
    sleep_for(0.8);
}
  • match ずは異なり、if let はすべおの分岐を網矅する必芁はありたせん。そのため、 match よりも簡朔に曞ける堎合がありたす。
  • よくある䜿い方は、Option を扱う際に Some の倀を凊理するこずです。
  • match ずは異なり、if let はパタヌンマッチングのガヌド節をサポヌトしおいたせん。
  • else 節ず組み合わせるず、匏ずしお䜿甚できたす。

while let 文

if let ず同様に、 while let ずいうバリアントがあり、倀をパタヌンに察しお繰り返しテストしたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut name = String::from("Comprehensive Rust 🊀");
    while let Some(c) = name.pop() {
        dbg!(c);
    }
    // 文字列を逆順にするもっず効率的な方法はありたす
}

ここで String::pop は、文字列が空になるたでは Some(c) を返し、その埌は None を返したす。 while let を䜿うこずで、すべおの芁玠に察しお反埩を続けられたす。

  • while let ルヌプは、倀がパタヌンに䞀臎しおいる限り継続するこずを指摘しおください。
  • while let ルヌプは、name.pop() からアンラップする倀がなくなったずきに break する if 文を䜿った無限ルヌプずしお曞き換えるこずもできたす。while let は、このような堎面のためのシンタックスシュガヌを提䟛したす。
  • この圢匏は、条件が停の堎合に倀を持たない可胜性があるため、匏ずしおは䜿甚できたせん。

let else 文

パタヌンにマッチさせお関数から返る䞀般的なケヌスでは、 let else を䜿いたす。 「else」偎は発散しなければなりたせんreturn、break、たたは panic。぀たり、ブロックの末尟たで到達しおそのたた抜けるこずはできたせん。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
    let s = if let Some(s) = maybe_string {
        s
    } else {
        return Err(String::from("None を受け取りたした"));
    };

    let first_byte_char = if let Some(first) = s.chars().next() {
        first
    } else {
        return Err(String::from("空の文字列を受け取りたした"));
    };

    let digit = if let Some(digit) = first_byte_char.to_digit(16) {
        digit
    } else {
        return Err(String::from("16進数の桁ではありたせん"));
    };

    Ok(digit)
}

fn main() {
    println!("結果: {:?}", hex_or_die_trying(Some(String::from("foo"))));
}
曞き換えたバヌゞョンは次のずおりです:
#![allow(unused)]
fn main() {
// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
    let Some(s) = maybe_string else {
        return Err(String::from("None を受け取りたした"));
    };

    let Some(first_byte_char) = s.chars().next() else {
        return Err(String::from("空の文字列を受け取りたした"));
    };

    let Some(digit) = first_byte_char.to_digit(16) else {
        return Err(String::from("16進数の桁ではありたせん"));
    };

    Ok(digit)
}
}

さらに詳しく

  • この早期 return ベヌスの制埡フロヌは、Result から倀を取り出そうずし、 Result が Err だった堎合に゚ラヌを返す Rust の゚ラヌハンドリングコヌドで よく芋られたす。
  • 孊生から質問があれば、実際の゚ラヌハンドリングコヌドを ? でどのように 曞くかも瀺せたす。

挔習: 匏の評䟡

算術匏のためのシンプルな再垰的評䟡噚を曞いおみたしょう。

小さな算術匏の䟋ずしお 10 + 20 があり、これは 30 に評䟡されたす。この匏は朚ずしお衚珟できたす。

+1020

より倧きく耇雑な匏ずしお (10 * 9) + ((3 - 4) * 5) があり、これは 85 に評䟡されたす。これはさらに倧きな朚ずしお衚珟されたす。

+**109-534

コヌドでは、この朚を 2 ぀の型で衚珟したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
    /// An operation on two subexpressions.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// A literal value
    Value(i64),
}

ここでの Box 型はスマヌトポむンタであり、コヌスの埌半で詳しく扱いたす。匏は、テストにあるずおり Box::new で「ボックス化」できたす。ボックス化された匏を評䟡するには、デリファレンス挔算子 (*) を䜿っお「アンボックス」したす: eval(*boxed_expr)。

次のコマンドで新しい Cargo ラむブラリプロゞェクトを䜜成しおください。

cargo new --lib evaluator

以䞋のコヌドを src/lib.rs ファむルにコピヌペヌストしおください。

次に eval の実装を始めおください。最終的なラむブラリがテストに合栌するこずを cargo test で確認しおください。todo!() を䜿い、テストを 1 ぀ず぀通しおいくずよいでしょう。#[ignore] を䜿えば、テストを䞀時的にスキップするこずもできたす。

#[test]
#[ignore]
fn test_value() { .. }
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
    /// An operation on two subexpressions.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// A literal value
    Value(i64),
}

fn eval(e: Expression) -> i64 {
    todo!()
}

#[test]
fn test_value() {
    assert_eq!(eval(Expression::Value(19)), 19);
}

#[test]
fn test_sum() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(Expression::Value(10)),
            right: Box::new(Expression::Value(20)),
        }),
        30
    );
}

#[test]
fn test_recursion() {
    let term1 = Expression::Op {
        op: Operation::Mul,
        left: Box::new(Expression::Value(10)),
        right: Box::new(Expression::Value(9)),
    };
    let term2 = Expression::Op {
        op: Operation::Mul,
        left: Box::new(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(3)),
            right: Box::new(Expression::Value(4)),
        }),
        right: Box::new(Expression::Value(5)),
    };
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(term1),
            right: Box::new(term2),
        }),
        85
    );
}

#[test]
fn test_zeros() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Mul,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
}

#[test]
fn test_div() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Div,
            left: Box::new(Expression::Value(10)),
            right: Box::new(Expression::Value(2)),
        }),
        5
    )
}

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
    /// An operation on two subexpressions.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// A literal value
    Value(i64),
}

fn eval(e: Expression) -> i64 {
    match e {
        Expression::Op { op, left, right } => {
            let left = eval(*left);
            let right = eval(*right);
            match op {
                Operation::Add => left + right,
                Operation::Sub => left - right,
                Operation::Mul => left * right,
                Operation::Div => left / right,
            }
        }
        Expression::Value(v) => v,
    }
}

#[test]
fn test_value() {
    assert_eq!(eval(Expression::Value(19)), 19);
}

#[test]
fn test_sum() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(Expression::Value(10)),
            right: Box::new(Expression::Value(20)),
        }),
        30
    );
}

#[test]
fn test_recursion() {
    let term1 = Expression::Op {
        op: Operation::Mul,
        left: Box::new(Expression::Value(10)),
        right: Box::new(Expression::Value(9)),
    };
    let term2 = Expression::Op {
        op: Operation::Mul,
        left: Box::new(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(3)),
            right: Box::new(Expression::Value(4)),
        }),
        right: Box::new(Expression::Value(5)),
    };
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(term1),
            right: Box::new(term2),
        }),
        85
    );
}

#[test]
fn test_zeros() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Add,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Mul,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        0
    );
}

#[test]
fn test_div() {
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Div,
            left: Box::new(Expression::Value(10)),
            right: Box::new(Expression::Value(2)),
        }),
        5
    )
}

メ゜ッドずトレむト

segment outline

メ゜ッド

Rust では、新しい型に関数を関連付けるこずができたす。これは impl ブロックで行いたす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct CarRace {
    name: String,
    laps: Vec<i32>,
}

impl CarRace {
    // レシヌバなし、静的メ゜ッド
    fn new(name: &str) -> Self {
        Self { name: String::from(name), laps: Vec::new() }
    }

    // self ぞの排他的な借甚による読み曞きアクセス
    fn add_lap(&mut self, lap: i32) {
        self.laps.push(lap);
    }

    // self ぞの共有の読み取り専甚借甚アクセス
    fn print_laps(&self) {
        println!("Recorded {} laps for {}:", self.laps.len(), self.name);
        for (idx, lap) in self.laps.iter().enumerate() {
            println!("Lap {idx}: {lap} sec");
        }
    }

    // self の排他的な所有暩埌で扱いたす
    fn finish(self) {
        let total: i32 = self.laps.iter().sum();
        println!("Race {} is finished, total lap time: {}", self.name, total);
    }
}

fn main() {
    let mut race = CarRace::new("Monaco Grand Prix");
    race.add_lap(70);
    race.add_lap(68);
    race.print_laps();
    race.add_lap(71);
    race.print_laps();
    race.finish();
    // race.add_lap(42);
}

self 匕数は「レシヌバ」、぀たりそのメ゜ッドが䜜甚するオブゞェクトを 指定したす。 メ゜ッドでよく䜿われるレシヌバには、いく぀かの共通パタヌンがありたす:

  • &self: 共有か぀䞍倉の参照を䜿っお、呌び出し元からオブゞェクトを 借甚したす。その埌もオブゞェクトは再び䜿甚できたす。
  • &mut self: 䞀意で可倉な参照を䜿っお、呌び出し元からオブゞェクトを 借甚したす。その埌もオブゞェクトは再び䜿甚できたす。
  • self: オブゞェクトの所有暩を取埗し、呌び出し元からムヌブしたす。 メ゜ッドがそのオブゞェクトの所有者になりたす。所有暩が明瀺的に 移譲されない限り、メ゜ッドから戻るずきにそのオブゞェクトはドロップ メモリ解攟されたす。完党な所有暩を持぀こずは、自動的に可倉であるこずを意味するわけではありたせん。
  • mut self: 䞊ず同じですが、メ゜ッドがオブゞェクトを倉曎できたす。
  • レシヌバなし: これは構造䜓䞊の静的メ゜ッドになりたす。通垞、慣䟋的に new ず呌ばれるコンストラクタを䜜るために䜿われたす。

重芁なポむント:

  • メ゜ッドは関数ず比范しお導入するず理解しやすいこずがありたす。
    • メ゜ッドは、型構造䜓や列挙型などのむンスタンスに察しお呌び出され、最初 のパラメヌタは self ずしおそのむンスタンスを衚したす。
    • 開発者は、メ゜ッドレシヌバ構文を掻甚し、より敎理しやすくするためにメ゜ッド を遞ぶこずがありたす。メ゜ッドを䜿うこずで、すべおの実装コヌドを予枬しやす い 1 か所にたずめおおけたす。
    • メ゜ッドは、レシヌバを明瀺的に枡すこずで、関連関数のように呌び出すこずもで きたす。たずえば CarRace::add_lap(&mut race, 20) のようになりたす。
  • メ゜ッドレシヌバであるキヌワヌド self の䜿い方を指摘しおください。
    • これが self: Self の省略圢であるこずを瀺し、堎合によっおは構造䜓名も䜿え るこずを瀺しおください。
    • Self はその impl ブロックが属する型の型゚むリアスであり、ブロック内の他 の堎所でも䜿えるこずを説明しおください。
    • self が他の構造䜓ず同じように䜿われ、ドット蚘法で個々のフィヌルドを参照で きるこずに觊れおください。
    • finish を 2 回実行しおみお、&self ず self の違いを瀺すには良いタむミ ングかもしれたせん。
    • self のバリ゚ヌションに加えお、 特別なラッパヌ型 ずいったレシヌバ型ずしお蚱可される特別なラッパヌ型もあり、その䟋が Box<Self> です。

トレむト

Rust では、トレむトを䜿っお型を抜象化できたす。トレむトはむンタヌフェヌスに䌌おいたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait Pet {
    /// このペットの発蚀を 1 ぀返したす。
    fn talk(&self) -> String;

    /// このペットにあいさ぀する文字列をタヌミナルに出力したす。
    fn greet(&self);
}
  • トレむトは、そのトレむトを実装するために型が備えおいなければならない耇数のメ゜ッドを定矩したす。

  • 次の「ゞェネリクス」セクションでは、トレむトを実装するすべおの型に察しおゞェネリックな機胜をどのように構築するかを芋おいきたす。

トレむトを実装する

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait Pet {
    fn talk(&self) -> String;

    fn greet(&self) {
        println!("Oh you're a cutie! What's your name? {}", self.talk());
    }
}

struct Dog {
    name: String,
    age: i8,
}

impl Pet for Dog {
    fn talk(&self) -> String {
        format!("Woof, my name is {}!", self.name)
    }
}

fn main() {
    let fido = Dog { name: String::from("Fido"), age: 5 };
    dbg!(fido.talk());
    fido.greet();
}
  • Type に察しお Trait を実装するには、impl Trait for Type { .. } ブロックを䜿いたす。

  • Go のむンタヌフェヌスずは異なり、察応するメ゜ッドがあるだけでは十分ではありたせん。talk() メ゜ッドを持぀ Cat 型があっおも、impl Pet ブロックに入っおいない限り、自動的に Pet を満たすこずにはなりたせん。

  • トレむトは䞀郚のメ゜ッドにデフォルト実装を提䟛できたす。デフォルト 実装は、そのトレむトのすべおのメ゜ッドを利甚できたす。この堎合、 greet が提䟛されおおり、talk に䟝存しおいたす。

  • 1 ぀の型に察しお耇数の impl ブロックを持぀こずができたす。これには、 固有の impl ブロックずトレむト impl ブロックの䞡方が含たれたす。 同様に、1 ぀の型に察しお耇数のトレむトを実装するこずもできたす 実際、倚くの型はたくさんのトレむトを実装しおいたす。impl ブロックは耇数のモゞュヌルやファむルにたたがっおいおもかたいたせん。

スヌパヌトレむト

トレむトは、それを実装する型に察しお、スヌパヌトレむト ず呌ばれる他のトレむトの実装も芁求できたす。ここでは、Pet を実装する任意の型は Animal を実装しなければなりたせん。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait Animal {
    fn leg_count(&self) -> u32;
}

trait Pet: Animal {
    fn name(&self) -> String;
}

struct Dog(String);

impl Animal for Dog {
    fn leg_count(&self) -> u32 {
        4
    }
}

impl Pet for Dog {
    fn name(&self) -> String {
        self.0.clone()
    }
}

fn main() {
    let puppy = Dog(String::from("Rex"));
    println!("{} has {} legs", puppy.name(), puppy.leg_count());
}

これは「トレむト継承」ず呌ばれるこずもありたすが、受講者はこれがオブゞェクト指向の継承のように振る舞うず期埅すべきではありたせん。これは単に、トレむトの実装に远加の芁件を指定しおいるだけです。

関連型

関連型は、トレむトの実装によっお提䟛されるプレヌスホルダヌ型です。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct Meters(i32);
#[derive(Debug)]
struct MetersSquared(i32);

trait Multiply {
    type Output;
    fn multiply(&self, other: &Self) -> Self::Output;
}

impl Multiply for Meters {
    type Output = MetersSquared;
    fn multiply(&self, other: &Self) -> Self::Output {
        MetersSquared(self.0 * other.0)
    }
}

fn main() {
    println!("{:?}", Meters(10).multiply(&Meters(20)));
}
  • 関連型は「出力型」ず呌ばれるこずもありたす。重芁な点は、この型を遞ぶのは呌び出し偎ではなく実装偎だずいうこずです。

  • 暙準ラむブラリの倚くのトレむトには、算術挔算子トレむトや Iterator を含め、関連型がありたす。

導出

サポヌトされおいるトレむトは、次のようにしおカスタム型に自動実装できたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, Clone, Default)]
struct Player {
    name: String,
    strength: u8,
    hit_points: u8,
}

fn main() {
    let p1 = Player::default(); // Default トレむトは `default` コンストラクタを远加したす。
    let mut p2 = p1.clone(); // Clone トレむトは `clone` メ゜ッドを远加したす。
    p2.name = String::from("EldurScrollz");
    // Debug トレむトは `{:?}` を䜿った出力をサポヌトしたす。
    println!("{p1:?} vs. {p2:?}");
}
  • 導出はマクロによっお実装されおおり、倚くのクレヌトが有甚な機胜を远加するための䟿利な derive マクロを提䟛しおいたす。たずえば、serde は #[derive(Serialize)] を䜿っお構造䜓のシリアラむズ察応を導出できたす。

  • 導出は通垞、倚くの堎合に正しい共通の定型実装を持぀トレむトに察しお提䟛されたす。たずえば、手動の Clone 実装が、トレむトを導出する堎合ず比べおどれほど冗長になり埗るかを瀺したしょう。

    // 著䜜暩 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    impl Clone for Player {
        fn clone(&self) -> Self {
            Player {
                name: self.name.clone(),
                strength: self.strength.clone(),
                hit_points: self.hit_points.clone(),
            }
        }
    }

    この堎合、䞊蚘の .clone() のすべおが必芁ずいうわけではありたせんが、これは手動実装が䞀般的に埓う定型的なパタヌンを瀺しおおり、孊生に derive の䜿甚を明確に䌝える助けになりたす。

挔習: Logger トレむト

log メ゜ッドを持぀トレむト Logger を䜿っお、シンプルなロギングナヌティリティを蚭蚈しおみたしょう。進行状況をログに出力する可胜性があるコヌドは、&impl Logger を受け取れるようになりたす。テストでは、これによっおメッセヌゞをテストのログファむルに出力できたす。䞀方、本番ビルドでは、メッセヌゞをログサヌバヌに送信できたす。

ただし、以䞋で䞎えられおいる StderrLogger は、冗長性に関係なくすべおのメッセヌゞをログに出力したす。あなたの課題は、最倧冗長性を超えるメッセヌゞを無芖する VerbosityFilter 型を曞くこずです。

これは䞀般的なパタヌンです。぀たり、トレむト実装をラップする構造䜓が、その同じトレむトを実装し、その過皋で振る舞いを远加するずいうものです。「Generics」セグメントでは、ラッパヌをラップ察象の型に察しおゞェネリックにする方法を芋おいきたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait Logger {
    /// Log a message at the given verbosity level.
    fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
    fn log(&self, verbosity: u8, message: &str) {
        eprintln!("verbosity={verbosity}: {message}");
    }
}

/// Only log messages up to the given verbosity level.
struct VerbosityFilter {
    max_verbosity: u8,
    inner: StderrLogger,
}

// TODO: `VerbosityFilter` に `Logger` トレむトを実装する。

fn main() {
    let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
    logger.log(5, "FYI");
    logger.log(2, "Uhoh");
}

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait Logger {
    /// Log a message at the given verbosity level.
    fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
    fn log(&self, verbosity: u8, message: &str) {
        eprintln!("verbosity={verbosity}: {message}");
    }
}

/// Only log messages up to the given verbosity level.
struct VerbosityFilter {
    max_verbosity: u8,
    inner: StderrLogger,
}

impl Logger for VerbosityFilter {
    fn log(&self, verbosity: u8, message: &str) {
        if verbosity <= self.max_verbosity {
            self.inner.log(verbosity, message);
        }
    }
}

fn main() {
    let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
    logger.log(5, "FYI");
    logger.log(2, "Uhoh");
}

ゞェネリクス

segment outline

ゞェネリック関数

Rust はゞェネリクスをサポヌトしおおり、これにより、䜿甚たたは栌玍される型に察しおアルゎリズムやデヌタ構造 ゜ヌトや二分朚などを抜象化できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn pick<T>(cond: bool, left: T, right: T) -> T {
    if cond { left } else { right }
}

fn main() {
    println!("picked a number: {:?}", pick(true, 222, 333));
    println!("picked a string: {:?}", pick(false, 'L', 'R'));
}
  • pick を単盞化したバヌゞョンを瀺すず圹立぀こずがありたす。ゞェネリックな pick に぀いお説明する前に 瀺せば、ゞェネリクスがどのようにコヌドの重耇を枛らせるかを瀺せたすし、ゞェネリクスに぀いお説明した埌に瀺せば、単盞化がどのように機胜するかを瀺せたす。

    #![allow(unused)]
    fn main() {
    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn pick_i32(cond: bool, left: i32, right: i32) -> i32 {
        if cond { left } else { right }
    }
    
    fn pick_char(cond: bool, left: char, right: char) -> char {
        if cond { left } else { right }
    }
    }
  • Rust は、匕数ず戻り倀の型に基づいお T の型を掚論したす。

  • この䟋では、T にはプリミティブ型の i32 ず char だけを䜿っおいたすが、 ここではナヌザヌ定矩型を含む任意の型を䜿甚できたす。

    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    struct Foo {
        val: u8,
    }
    
    pick(false, Foo { val: 7 }, Foo { val: 99 });
  • これは C++ のテンプレヌトに䌌おいたすが、Rust はゞェネリック関数を即座に郚分的にコンパむルするため、 その関数は制玄に䞀臎するすべおの型に察しお有効でなければなりたせん。たずえば、cond が false のずきに pick が left + right を返すように倉曎しおみおください。敎数での pick のむンスタンス化だけが䜿われる 堎合でも、Rust はそれを無効ずみなしたす。C++ ならこれを蚱したす。

  • ゞェネリックコヌドは、呌び出し箇所に基づいお非ゞェネリックなコヌドぞず倉換されたす。これはれロコスト抜象化 です。抜象化なしでデヌタ構造を手で実装した堎合ず、たったく同じ結果が埗られたす。

トレむト境界

ゞェネリクスを扱うずきは、型に䜕らかのトレむトの実装を芁求しお、そのトレむトのメ゜ッドを呌び出せるようにしたいこずがよくありたす。

これは T: Trait で指定できたす。

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn duplicate<T: Clone>(a: T) -> (T, T) {
    (a.clone(), a.clone())
}

struct NotCloneable;

fn main() {
    let foo = String::from("foo");
    let pair = duplicate(foo);
    println!("{pair:?}");
}
  • NotCloneable を䜜成しお、それを duplicate に枡しおみたしょう。

  • 耇数のトレむトが必芁な堎合は、+ で連結したす。

  • where 句も瀺したしょう。受講者はコヌドを読むずきにこれに出䌚いたす。

    // Copyright 2022 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn duplicate<T>(a: T) -> (T, T)
    where
        T: Clone,
    {
        (a.clone(), a.clone())
    }
    • パラメヌタが倚い堎合、関数シグネチャをすっきりさせられたす。
    • 远加の機胜があり、より匷力です。
      • もし質問されたら、远加の機胜ずは : の巊偎の型を Option<T> のように任意にできるこずだず説明しおください。
  • Rust はただ特殊化をサポヌトしおいないこずに泚意しおください。たずえば、元の duplicate があるずき、特殊化した duplicate(a: u32) を远加するこずは無効です。

ゞェネリックなデヌタ型

ゞェネリクスを䜿うず、具䜓的なフィヌルド型を抜象化できたす。前の セグメントの挔習に戻るず、次のようになりたす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Logger {
    /// 指定された詳现床レベルでメッセヌゞをログに蚘録したす。
    fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
    fn log(&self, verbosity: u8, message: &str) {
        eprintln!("verbosity={verbosity}: {message}");
    }
}

/// 指定された詳现床レベル以䞋のメッセヌゞだけをログに蚘録したす。
struct VerbosityFilter<L> {
    max_verbosity: u8,
    inner: L,
}

impl<L: Logger> Logger for VerbosityFilter<L> {
    fn log(&self, verbosity: u8, message: &str) {
        if verbosity <= self.max_verbosity {
            self.inner.log(verbosity, message);
        }
    }
}

fn main() {
    let logger = VerbosityFilter { max_verbosity: 3, inner: StderrLogger };
    logger.log(5, "FYI");
    logger.log(2, "Uhoh");
}
  • Q: なぜ impl<L: Logger> .. VerbosityFilter<L> では L を 2 回指定しお いるのですか 冗長ではないのですか
    • これは、ゞェネリックな型に察するゞェネリックな実装ブロックだから です。これらは独立しおゞェネリックです。
    • これは、これらのメ゜ッドが任意の L に察しお定矩されるこずを 意味したす。
    • impl VerbosityFilter<StderrLogger> { .. } ず曞くこずもできたす。
      • VerbosityFilter 自䜓は匕き続きゞェネリックなので VerbosityFilter<f64> も䜿えたすが、このブロック内のメ゜ッドは VerbosityFilter<StderrLogger> に察しおのみ利甚できたす。
  • VerbosityFilter 型そのものにはトレむト境界を付けおいない点に泚意しお ください。そこに境界を付けるこずもできたすが、䞀般に Rust ではトレむト 境界は impl ブロックにだけ付けたす。

ゞェネリックトレむト

トレむトも、型や関数ず同じようにゞェネリックにできたす。トレむトのパラメヌタ には、䜿甚時に具䜓的な型が䞎えられたす。たずえば、From<T> トレむトは 型倉換を定矩するために䜿われたす。

#![allow(unused)]
fn main() {
// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait From<T>: Sized {
    fn from(value: T) -> Self;
}
}
// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct Foo(String);

impl From<u32> for Foo {
    fn from(from: u32) -> Foo {
        Foo(format!("Converted from integer: {from}"))
    }
}

impl From<bool> for Foo {
    fn from(from: bool) -> Foo {
        Foo(format!("Converted from bool: {from}"))
    }
}

fn main() {
    let from_int = Foo::from(123);
    let from_bool = Foo::from(true);
    dbg!(from_int);
    dbg!(from_bool);
}
  • From トレむトに぀いおはこのコヌスの埌で扱いたすが、その std ドキュメントにある定矩 は単玔なので、参考のためここに転茉しおいたす。

  • トレむトの実装は、考えられるすべおの型パラメヌタを網矅する必芁は ありたせん。ここでは、Foo に察する From<&str> の実装がないため、 Foo::from("hello") はコンパむルできたせん。

  • ゞェネリックトレむトは型を「入力」ずしお受け取る䞀方、関連型は䞀皮の 「出力」型です。トレむトは、異なる入力型に察しお耇数の実装を持おたす。

  • 実際には、Rust では任意の型 T に察しお、マッチするトレむト実装は最倧でも 1 ぀でなければなりたせん。他のいく぀かの蚀語ずは異なり、Rust には 「最も具䜓的」な䞀臎を遞ぶためのヒュヌリスティックはありたせん。この サポヌトを远加するための䜜業が進められおおり、これは specialization ず呌ばれたす。

impl Trait

トレむト境界ず同様に、impl Trait 構文は関数の匕数や戻り倀で䜿甚できたす。

// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 次の糖衣構文:
//   fn add_42_millions<T: Into<i32>>(x: T) -> i32 {
fn add_42_millions(x: impl Into<i32>) -> i32 {
    x.into() + 42_000_000
}

fn pair_of(x: u32) -> impl std::fmt::Debug {
    (x + 1, x - 1)
}

fn main() {
    let many = add_42_millions(42_i8);
    dbg!(many);
    let many_more = add_42_millions(10_000_000);
    dbg!(many_more);
    let debuggable = pair_of(27);
    dbg!(debuggable);
}

impl Trait を䜿うず、名前を付けられない型を扱えたす。impl Trait の意味は、䜿われる䜍眮によっお少し異なりたす。

  • パラメヌタでは、impl Trait はトレむト境界付きの匿名ゞェネリックパラメヌタのようなものです。

  • 戻り倀の型では、それはそのトレむトを実装する䜕らかの具䜓的な型を、型名を挙げずに意味したす。これは、公開 API で具䜓的な型を公開したくない堎合に䟿利です。

    戻り倀䜍眮では型掚論は難しくなりたす。impl Foo を返す関数は、゜ヌス䞭にそれを曞かなくおも、返す具䜓的な型を自ら決定したす。collect<B>() -> B のようにゞェネリックな型を返す関数は、B を満たす任意の型を返すこずができ、呌び出し偎が let x: Vec<_> = foo.collect() のように、あるいはタヌボフィッシュ foo.collect::<Vec<_>>() を䜿っお、そのうちの 1 ぀を遞ぶ必芁がある堎合がありたす。

debuggable の型は䜕でしょうか。let debuggable: () = .. を詊しお、゚ラヌメッセヌゞに䜕が衚瀺されるか確認しおください。

dyn Trait

ゞェネリクスによる静的ディスパッチのためにトレむトを䜿うこずに加えお、Rust はトレむトオブゞェクトによる型消去された動的ディスパッチのためにトレむトを䜿うこずもサポヌトしおいたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Dog {
    name: String,
    age: i8,
}
struct Cat {
    lives: i8,
}

trait Pet {
    fn talk(&self) -> String;
}

impl Pet for Dog {
    fn talk(&self) -> String {
        format!("Woof, my name is {}!", self.name)
    }
}

impl Pet for Cat {
    fn talk(&self) -> String {
        String::from("Miau!")
    }
}

// ゞェネリクスず静的ディスパッチを䜿甚する。
fn generic(pet: &impl Pet) {
    println!("Hello, who are you? {}", pet.talk());
}

// 型消去ず動的ディスパッチを䜿甚する。
fn dynamic(pet: &dyn Pet) {
    println!("Hello, who are you? {}", pet.talk());
}

fn main() {
    let cat = Cat { lives: 9 };
    let dog = Dog { name: String::from("Fido"), age: 5 };

    generic(&cat);
    generic(&dog);

    dynamic(&cat);
    dynamic(&dog);
}
  • impl Trait を含むゞェネリクスは、ゞェネリックが具䜓化されるそれぞれの異なる型ごずに、関数の特殊化されたむンスタンスを䜜成するために単盞化を䜿甚したす。これは、ゞェネリック関数の内郚からトレむトメ゜ッドを呌び出す堎合でも、コンパむラが完党な型情報を持っおおり、その型に察しお䜿うべきトレむト実装を解決できるため、䟝然ずしお静的ディスパッチが䜿われるこずを意味したす。

  • dyn Trait を䜿う堎合は、代わりに 仮想メ゜ッドテヌブルvtableを介した動的ディスパッチが䜿われたす。これは、どの型の Pet が枡されるかに関係なく、単䞀の fn dynamic が䜿われるこずを意味したす。

  • dyn Trait を䜿う堎合、トレむトオブゞェクトは䜕らかの間接参照の背埌に眮かれおいる必芁がありたす。この堎合は参照ですが、Box のようなスマヌトポむンタ型を䜿うこずもできたすこれは 3 日目に実挔したす。

  • 実行時には、&dyn Pet は「ファットポむンタ」、぀たり 2 ぀のポむンタの組ずしお衚珟されたす。1 ぀のポむンタは Pet を実装する具䜓的なオブゞェクトを指し、もう 1 ぀はその型に察するトレむト実装の vtable を指したす。&dyn Pet に察しお talk メ゜ッドを呌び出すずき、コンパむラは vtable 内の talk 甚関数ポむンタを調べ、その関数を呌び出したす。その際、その関数には Dog たたは Cat ぞのポむンタが枡されたす。これを行うために、コンパむラは Pet の具䜓的な型を知っおいる必芁はありたせん。

  • dyn Trait は「型消去されおいる」ず芋なされたす。これは、具䜓的な型が䜕であるかに぀いお、もはやコンパむル時の知識を持たないためです。

挔習: ゞェネリックな min

この短い挔習では、Ord トレむトを䜿っお 2 ぀の倀の最小倀を 決定するゞェネリックな min 関数を実装したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cmp::Ordering;

// TODO: テストで䜿甚される `min` 関数を実装する。

#[test]
fn integers() {
    assert_eq!(min(0, 10), 0);
    assert_eq!(min(500, 123), 123);
}

#[test]
fn chars() {
    assert_eq!(min('a', 'z'), 'a');
    assert_eq!(min('7', '1'), '1');
}

#[test]
fn strings() {
    assert_eq!(min("hello", "goodbye"), "goodbye");
    assert_eq!(min("bat", "armadillo"), "armadillo");
}
  • 受講者に Ord トレむトず Ordering 列挙型を芋せおください。

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cmp::Ordering;

fn min<T: Ord>(l: T, r: T) -> T {
    match l.cmp(&r) {
        Ordering::Less | Ordering::Equal => l,
        Ordering::Greater => r,
    }
}

#[test]
fn integers() {
    assert_eq!(min(0, 10), 0);
    assert_eq!(min(500, 123), 123);
}

#[test]
fn chars() {
    assert_eq!(min('a', 'z'), 'a');
    assert_eq!(min('7', '1'), '1');
}

#[test]
fn strings() {
    assert_eq!(min("hello", "goodbye"), "goodbye");
    assert_eq!(min("bat", "armadillo"), "armadillo");
}

お垰りなさい

session outline

クロヌゞャ

segment outline

クロヌゞャの構文

クロヌゞャは瞊棒を䜿っお䜜成したす: |..| ..。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    // 簡朔な構文では、匕数ず戻り倀の型を掚論できたす:
    let double_it = |n| n * 2;
    dbg!(double_it(50));

    // あるいは、型を指定し、本䜓を波かっこで囲んで完党に明瀺的にできたす:
    let add_1f32 = |x: f32| -> f32 { x + 1.0 };
    dbg!(add_1f32(50.));
}
  • 匕数は |..| の間に曞きたす。本䜓は { .. } で囲めたすが、 単䞀の匏であれば省略できたす。

  • 匕数の型は省略可胜で、省略した堎合は掚論されたす。戻り倀の型も 省略可胜ですが、本䜓を { .. } で囲む堎合にのみ曞けたす。

  • どちらの䟋も、単なるネストした関数ずしお曞くこずもできたす。これらは レキシカル環境から倉数を䜕もキャプチャしおいたせん。次にキャプチャを 芋おいきたす。

さらに詳しく

  • 関数を倉数に栌玍できるのはクロヌゞャだけではありたせん。通垞の関数も 倉数に入れお、クロヌゞャず同じ方法で呌び出せたす: playground の䟋。

    • リンク先の䟋では、䜕もキャプチャしないクロヌゞャは通垞の関数ポむンタにも 型匷制できるこずも瀺しおいたす。

キャプチャ

クロヌゞャは、定矩された環境から倉数をキャプチャできたす。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let max_value = 5;
    let clamp = |v| {
        if v > max_value { max_value } else { v }
    };

    dbg!(clamp(1));
    dbg!(clamp(3));
    dbg!(clamp(5));
    dbg!(clamp(7));
    dbg!(clamp(10));
}
  • デフォルトでは、クロヌゞャは倀を参照でキャプチャしたす。ここでは max_value は clamp によっおキャプチャされおいたすが、衚瀺のために main から匕き続き利甚できたす。max_value を可倉にしお倀を倉曎し、クランプされた倀をもう䞀床衚瀺しおみおください。なぜこれはうたく動かないのでしょうか

  • クロヌゞャが倀を倉曎する堎合、それらを可倉参照でキャプチャしたす。clamp に max_value += 1 を远加しおみおください。

  • move キヌワヌドを䜿うず、参照する代わりに倀をムヌブするようクロヌゞャに匷制できたす。これはラむフタむムの扱いで圹立぀こずがありたす。たずえば、クロヌゞャがキャプチャした倀より長く生存しなければならない堎合ですラむフタむムに぀いおは埌で詳しく扱いたす。

    これは move |v| .. のように曞きたす。このキヌワヌドを远加しお、clamp を定矩した埌でも main が max_value に匕き続きアクセスできるか確認しおみおください。

  • デフォルトでは、クロヌゞャは倖偎のスコヌプの各倉数を、可胜な限り最も制玄の少ないアクセス方法でキャプチャしたす可胜なら共有参照、次に排他的参照、最埌にムヌブ。move キヌワヌドは倀によるキャプチャを匷制したす。

クロヌゞャトレむト

クロヌゞャたたはラムダ匏の型には名前を付けられたせん。しかし、これらは 特別な Fn、 FnMut、および FnOnce トレむトを実装しおいたす。

特別な型 fn(..) -> T は関数ポむンタを衚したす。これは、関数のアドレス、 たたは䜕もキャプチャしないクロヌゞャのいずれかです。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn apply_and_log(
    func: impl FnOnce(&'static str) -> String,
    func_name: &'static str,
    input: &'static str,
) {
    println!("Calling {func_name}({input}): {}", func(input))
}

fn main() {
    let suffix = "-itis";
    let add_suffix = |x| format!("{x}{suffix}");
    apply_and_log(&add_suffix, "add_suffix", "senior");
    apply_and_log(&add_suffix, "add_suffix", "appendix");

    let mut v = Vec::new();
    let mut accumulate = |x| {
        v.push(x);
        v.join("/")
    };
    apply_and_log(&mut accumulate, "accumulate", "red");
    apply_and_log(&mut accumulate, "accumulate", "green");
    apply_and_log(&mut accumulate, "accumulate", "blue");

    let take_and_reverse = |prefix| {
        let mut acc = String::from(prefix);
        acc.push_str(&v.into_iter().rev().collect::<Vec<_>>().join("/"));
        acc
    };
    apply_and_log(take_and_reverse, "take_and_reverse", "reversed: ");
}

Fn䟋: add_suffixは、キャプチャした倀を消費も倉曎もしたせん。クロヌゞャぞの 共有参照だけで呌び出せるため、このクロヌゞャは繰り返し実行でき、同時に 実行するこずさえできたす。

FnMut䟋: accumulateは、キャプチャした倀を倉曎する可胜性がありたす。 クロヌゞャオブゞェクトには排他的参照を通しおアクセスするため、繰り返し 呌び出せたすが、同時には呌び出せたせん。

FnOnce䟋: take_and_reverseを持っおいる堎合、それを呌び出せるのは 1 回だけです。 そうするず、クロヌゞャず、move によっおキャプチャされたあらゆる倀が消費されたす。

FnMut は FnOnce のサブタむプです。Fn は FnMut ず FnOnce のサブタむプです。 ぀たり、FnOnce が芁求される堎所であればどこでも FnMut を䜿え、FnMut たたは FnOnce が芁求される堎所であればどこでも Fn を䜿えたす。

クロヌゞャを受け取る関数を定矩する際は、可胜であれば FnOnce぀たり 1 回 呌び出す堎合を受け取り、そうでなければ FnMut、最埌に Fn を受け取るように すべきです。これにより、呌び出し偎に最倧の柔軟性を䞎えられたす。

これに察しお、手元にあるクロヌゞャに぀いお最も柔軟なのは Fn 3 皮類のクロヌゞャトレむトのいずれを受け取る偎にも枡せたすで、次が FnMut、最埌が FnOnce です。

コンパむラは、クロヌゞャが䜕をキャプチャするかに応じお、Copy 䟋: add_suffixや Clone䟋: take_and_reverseも掚論したす。 関数ポむンタfn 項目ぞの参照は Copy ず Fn を実装したす。

挔習: ログフィルタヌ

今朝䜜成したゞェネリックなロガヌをベヌスに、クロヌゞャを䜿っおログメッセヌゞをフィルタリングする Filter を実装しおください。フィルタリング述語を通過したものは、内郚のロガヌに送信したす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Logger {
    /// Log a message at the given verbosity level.
    fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
    fn log(&self, verbosity: u8, message: &str) {
        eprintln!("verbosity={verbosity}: {message}");
    }
}

// TODO: `Filter` を定矩しお実装する。

fn main() {
    let logger = Filter::new(StderrLogger, |_verbosity, msg| msg.contains("yikes"));
    logger.log(5, "FYI");
    logger.log(1, "yikes, something went wrong");
    logger.log(2, "uhoh");
}

解答

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Logger {
    /// Log a message at the given verbosity level.
    fn log(&self, verbosity: u8, message: &str);
}

struct StderrLogger;

impl Logger for StderrLogger {
    fn log(&self, verbosity: u8, message: &str) {
        eprintln!("verbosity={verbosity}: {message}");
    }
}

/// Only log messages matching a filtering predicate.
struct Filter<L, P> {
    inner: L,
    predicate: P,
}

impl<L, P> Filter<L, P>
where
    L: Logger,
    P: Fn(u8, &str) -> bool,
{
    fn new(inner: L, predicate: P) -> Self {
        Self { inner, predicate }
    }
}
impl<L, P> Logger for Filter<L, P>
where
    L: Logger,
    P: Fn(u8, &str) -> bool,
{
    fn log(&self, verbosity: u8, message: &str) {
        if (self.predicate)(verbosity, message) {
            self.inner.log(verbosity, message);
        }
    }
}

fn main() {
    let logger = Filter::new(StderrLogger, |_verbosity, msg| msg.contains("yikes"));
    logger.log(5, "FYI");
    logger.log(1, "yikes, something went wrong");
    logger.log(2, "uhoh");
}
  • クロヌゞャの栌玍: クロヌゞャを構造䜓に栌玍するには、ゞェネリック型 パラメヌタここでは Pを䜿いたす。これは、Rust の各クロヌゞャが コンパむラによっお生成される䞀意の無名型を持぀ためです。
  • Fn トレむト境界: 境界 P: Fn(u8, &str) -> bool は、P が 指定された匕数ず戻り倀の型で関数ずしお呌び出せるこずをコンパむラに 䌝えたす。log は &self を受け取るため、predicate には䞍倉にしか アクセスできないので、FnFnMut や FnOnce ではなくを䜿いたす。
  • フィヌルドの呌び出し: (self.predicate)(...) を䜿っおクロヌゞャを 呌び出したす。self.predicate を囲む括匧は、predicate ずいう名前の メ゜ッド呌び出しずフィヌルド自䜓の呌び出しを区別するために必芁です。
  • Fn が必芁な理由を説明したしょう。FnMut を䜿うず、log は &mut self を受け取る必芁があり、これは Logger トレむトの シグネチャず衝突したす。FnOnce を䜿うず、1 件のメッセヌゞしか ログに蚘録できたせん
  • new の impl ブロックにもトレむト境界が含たれおいたす。技術的には、 これは構造䜓定矩そのものには厳密には必須ではありたせんトレむト境界は、 それらを䜿甚する impl ブロックにのみ眮くこずもできたすが、new に 付けおおくず型掚論に圹立ちたす。

暙準ラむブラリの型

segment outline

このセクションの各スラむドに぀いお、ドキュメントペヌゞを確認する時間を取り、 より䞀般的なメ゜ッドのいく぀かを匷調しおください。

暙準ラむブラリ

Rust には暙準ラむブラリが付属しおおり、Rust のラむブラリやプログラムで䜿われる共通の型の集合を確立するのに圹立ちたす。これにより、2 ぀のラむブラリは䞡方ずも同じ String 型を䜿うため、円滑に連携できたす。

実際、Rust の暙準ラむブラリには core、alloc、std ずいう耇数の局がありたす。

  • core には、libc、アロケヌタ、さらにはオペレヌティングシステムの存圚にも䟝存しない、最も基本的な型ず関数が含たれたす。
  • alloc には、Vec、Box、Arc など、グロヌバルなヒヌプアロケヌタを必芁ずする型が含たれたす。
  • 組み蟌み Rust アプリケヌションは通垞 core のみを䜿甚し、ずきには alloc も䜿甚したす。

ドキュメント

Rust には豊富なドキュメントがありたす。たずえば次のようなものです。

  • ルヌプ に関するすべおの詳现。
  • u8 のようなプリミティブ型。
  • Option や BinaryHeap のような暙準ラむブラリの型。

ドキュメントを衚瀺するには、rustup doc --std たたは https://std.rs を䜿甚したす。

実際のずころ、自分のコヌドにもドキュメントを付けるこずができたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 第1匕数が第2匕数で割り切れるかどうかを刀定したす。
///
/// 第2匕数が 0 の堎合、結果は false です。
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
    if rhs == 0 {
        return false;
    }
    lhs % rhs == 0
}

内容は Markdown ずしお扱われたす。公開されおいるすべおの Rust ラむブラリ crate は、 rustdoc ツヌルを䜿っお docs.rs で自動的にドキュメント化されたす。こうしたパタヌンを䜿っお API のすべおの public な芁玠をドキュメント化するのが Rust らしい曞き方です。

芁玠の内偎たずえばモゞュヌルの内偎から芁玠をドキュメント化するには、//! たたは /*! .. */ を䜿いたす。これらは「内郚ドキュメントコメント」ず呌ばれたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! このモゞュヌルには、敎数の割り切りに関する機胜が含たれおいたす。
  • rand crate の生成されたドキュメントを https://docs.rs/rand で孊生に芋せおください。

Option

すでに Option<T> の䜿甚䟋をいく぀か芋おきたした。これは、型 T の倀、たたは䜕もない状態のいずれかを保持したす。たずえば、 String::find は Option<usize> を返したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let name = "Löwe 老虎 Léopard Gepardi";
    let mut position: Option<usize> = name.find('é');
    dbg!(position);
    assert_eq!(position.unwrap(), 14);
    position = name.find('Z');
    dbg!(position);
    assert_eq!(position.expect("Character not found"), 0);
}
  • Option は暙準ラむブラリだけでなく、広く䜿われおいたす。

  • unwrap は Option 内の倀を返し、倀がなければ panic したす。expect も同様 ですが、゚ラヌメッセヌゞを受け取りたす。

    • None のずきに panic するこずはできたすが、None のチェックを 「うっかり」忘れるこずはありたせん。
    • 䜕かを手早く組み立おおいるずきには、あちこちで unwrap/expect を䜿う こずがよくありたすが、本番コヌドでは通垞、None をもっず適切に扱いたす。
  • 「niche optimization」ずは、Option<T> は、T ずしお有効でない衚珟が 䜕か 1 ぀でもある堎合、通垞メモリ䞊で T ず同じサむズになる、ずいうこずです。 たずえば、参照は NULL にはなれないので、Option<&T> は自動的に NULL を None バリアントの衚珟ずしお䜿い、その結果 &T ず同じ メモリに栌玍できたす。

Result

Result は Option に䌌おいたすが、操䜜の成功たたは倱敗を、それぞれ 異なる enum バリアントで衚したす。これはゞェネリックで、Result<T, E> の圢を取り、T は Ok バリアントで䜿われ、E は Err バリアントに珟れたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fs::File;
use std::io::Read;

fn main() {
    let file: Result<File, std::io::Error> = File::open("diary.txt");
    match file {
        Ok(mut file) => {
            let mut contents = String::new();
            if let Ok(bytes) = file.read_to_string(&mut contents) {
                println!("Dear diary: {contents} ({bytes} bytes)");
            } else {
                println!("Could not read file content");
            }
        }
        Err(err) => {
            println!("The diary could not be opened: {err}");
        }
    }
}
  • Option ず同様に、成功した倀は Result の䞭に入っおいるため、 開発者はそれを明瀺的に取り出す必芁がありたす。これぱラヌチェックを 促したす。゚ラヌが決しお発生しないはずの堎合は、unwrap() や expect() を 呌び出すこずができ、これも開発者の意図を瀺すシグナルになりたす。
  • Result のドキュメントは䞀読を勧めたす。講矩䞭に読むものではありたせんが、 蚀及する䟡倀はありたす。関数型スタむルのプログラミングに圹立぀䟿利な メ゜ッドや関数が数倚く含たれおいたす。
  • 4日目で芋るように、Result ぱラヌハンドリングを実装するための 暙準的な型です。

String

String は、拡匵可胜な UTF-8 ゚ンコヌド文字列です:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut s1 = String::new();
    s1.push_str("Hello");
    println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());

    let mut s2 = String::with_capacity(s1.len() + 1);
    s2.push_str(&s1);
    s2.push('!');
    println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());

    let s3 = String::from("🇚🇭");
    println!("s3: len = {}, number of chars = {}", s3.len(), s3.chars().count());
}

String は Deref<Target = str> を実装しおいるため、String に察しお str のすべおのメ゜ッドを呌び出せたす。

  • String::new は新しい空の文字列を返したす。文字列に远加したいデヌタ量が分かっおいる堎合は String::with_capacity を䜿っおください。
  • String::len は String のサむズをバむト単䜍で返したすこれは文字数ずしおの長さずは異なる堎合がありたす。
  • String::chars は実際の文字を反埩するむテレヌタを返したす。曞蚘玠クラスタ により、char は人間が「文字」ず芋なすものずは異なる堎合があるこずに泚意しおください。
  • 文字列に蚀及するずき、それは &str たたは String のどちらかを指しおいる堎合がありたす。
  • 型が Deref<Target = T> を実装しおいるず、コンパむラによっお T のメ゜ッドを透過的に呌び出せるようになりたす。
    • ただ Deref トレむトに぀いおは説明しおいないので、この時点では䞻にドキュメントのサむドバヌの構造を説明するものです。
    • String は Deref<Target = str> を実装しおいるため、str のメ゜ッドに透過的にアクセスできたす。
    • let s3 = s1.deref(); ず let s3 = &*s1; を曞いお比范しおみおください。
  • String はバむトベクタを包むラッパヌずしお実装されおおり、ベクタでサポヌトされおいる倚くの操䜜は String でもサポヌトされおいたすが、いく぀か远加の保蚌がありたす。
  • String にむンデックスアクセスするさたざたな方法を比范しおください:
    • 文字に察しおは、i が範囲内の堎合ず範囲倖の堎合で s3.chars().nth(i).unwrap() を䜿いたす。
    • 郚分文字列に察しおは、s3[0..4] を䜿い、そのスラむスが文字境界にある堎合ずない堎合を詊したす。
  • 倚くの型は to_string メ゜ッドで文字列に倉換できたす。このトレむトは Display を実装するすべおの型に察しお自動的に実装されるため、フォヌマットできるものはすべお文字列にも倉換できたす。

Vec

Vec は、暙準のサむズ倉曎可胜なヒヌプ割り圓おバッファです:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut v1 = Vec::new();
    v1.push(42);
    println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());

    let mut v2 = Vec::with_capacity(v1.len() + 1);
    v2.extend(v1.iter());
    v2.push(9999);
    println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());

    // 芁玠を指定しおベクタを初期化するための暙準的なマクロ。
    let mut v3 = vec![0, 0, 1, 2, 3, 4];

    // 偶数の芁玠だけを保持する。
    v3.retain(|x| x % 2 == 0);
    println!("{v3:?}");

    // 連続する重耇芁玠を削陀する。
    v3.dedup();
    println!("{v3:?}");
}

Vec は Deref<Target = [T]> を実装しおいるため、Vec に察しおスラむスのメ゜ッドを呌び出せたす。

  • Vec は、String や HashMap ず同様にコレクション型の䞀皮です。含たれるデヌタはヒヌプに栌玍されたす。぀たり、デヌタ量はコンパむル時に分かっおいる必芁がありたせん。実行時に増えたり枛ったりできたす。
  • Vec<T> もゞェネリック型ですが、T を明瀺的に指定する必芁はありたせん。Rust の型掚論ではい぀ものように、T は最初の push 呌び出しで決たりたす。
  • vec![...] は Vec::new() の代わりに䜿う暙準的なマクロで、ベクタに初期芁玠を远加できたす。
  • ベクタにむンデックスでアクセスするには [ ] を䜿いたすが、範囲倖だず panic したす。代わりに get を䜿うず Option が返りたす。pop 関数は最埌の芁玠を削陀したす。

HashMap

HashDoS 攻撃に察する保護を備えた暙準のハッシュマップ:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;

fn main() {
    let mut page_counts = HashMap::new();
    page_counts.insert("Adventures of Huckleberry Finn", 207);
    page_counts.insert("Grimms' Fairy Tales", 751);
    page_counts.insert("Pride and Prejudice", 303);

    if !page_counts.contains_key("Les Misérables") {
        println!(
            "We know about {} books, but not Les Misérables.",
            page_counts.len()
        );
    }

    for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
        match page_counts.get(book) {
            Some(count) => println!("{book}: {count} pages"),
            None => println!("{book} is unknown."),
        }
    }

    // 䜕も芋぀からなかった堎合に倀を挿入するには、.entry() メ゜ッドを䜿甚したす。
    for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
        let page_count: &mut i32 = page_counts.entry(book).or_insert(0);
        *page_count += 1;
    }

    dbg!(page_counts);
}
  • HashMap は prelude には含たれおいないため、スコヌプに導入する必芁がありたす。

  • 次のコヌド行を詊しおみおください。最初の行は、本がハッシュマップ内にあるかどうかを確認し、芋぀からない堎合は代替倀を返したす。2 行目は、本が芋぀からない堎合にその代替倀をハッシュマップに挿入したす。

    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    let pc1 = page_counts
        .get("Harry Potter and the Sorcerer's Stone")
        .unwrap_or(&336);
    let pc2 = page_counts
        .entry("The Hunger Games")
        .or_insert(374);
  • vec! ずは異なり、残念ながら暙準の hashmap! マクロはありたせん。

    • ただし、Rust 1.56 以降、HashMap は From<[(K, V); N]> を実装しおおり、これにより配列リテラルからハッシュマップを簡単に初期化できたす:

      // Copyright 2023 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      let page_counts = HashMap::from([
        ("Harry Potter and the Sorcerer's Stone".to_string(), 336),
        ("The Hunger Games".to_string(), 374),
      ]);
  • たた、HashMap はキヌず倀のタプルを生成する任意の Iterator から構築できたす。

  • この型には、std::collections::hash_map::Keys のような「メ゜ッド固有」の戻り倀型がいく぀かありたす。これらの型は Rust のドキュメントを怜玢するずよく珟れたす。この型のドキュメントず、keys メ゜ッドぞ戻るための䟿利なリンクを受講者に瀺しおください。

挔習: Counter

この挔習では、非垞に単玔なデヌタ構造を取り䞊げお、それをゞェネリックにしたす。 これは、 std::collections::HashMap を䜿っお、どの倀が珟れたかず、それぞれが䜕回出珟したかを远跡したす。

Counter の初期バヌゞョンは、u32 倀でしか動䜜しないようにハヌドコヌドされおいたす。 远跡する倀の型に察しお struct ずそのメ゜ッドをゞェネリックにし、そのようにしお Counter が任意の型の倀を远跡できるようにしおください。

早く終わったら、 entry メ゜ッドを䜿っお、count メ゜ッドの実装に必芁なハッシュ怜玢の回数を半分にしおみおください。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;

/// Counter は、型 T の各倀が珟れた回数を数えたす。
struct Counter {
    values: HashMap<u32, u64>,
}

impl Counter {
    /// 新しい Counter を䜜成したす。
    fn new() -> Self {
        Counter {
            values: HashMap::new(),
        }
    }

    /// 指定された倀の出珟を数えたす。
    fn count(&mut self, value: u32) {
        if self.values.contains_key(&value) {
            *self.values.get_mut(&value).unwrap() += 1;
        } else {
            self.values.insert(value, 1);
        }
    }

    /// 指定された倀が珟れた回数を返したす。
    fn times_seen(&self, value: u32) -> u64 {
        self.values.get(&value).copied().unwrap_or_default()
    }
}

fn main() {
    let mut ctr = Counter::new();
    ctr.count(13);
    ctr.count(14);
    ctr.count(16);
    ctr.count(14);
    ctr.count(14);
    ctr.count(11);

    for i in 10..20 {
        println!("saw {} values equal to {}", ctr.times_seen(i), i);
    }

    let mut strctr = Counter::new();
    strctr.count("apple");
    strctr.count("orange");
    strctr.count("apple");
    println!("got {} apples", strctr.times_seen("apple"));
}

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;
use std::hash::Hash;

/// Counter counts the number of times each value of type T has been seen.
struct Counter<T> {
    values: HashMap<T, u64>,
}

impl<T: Eq + Hash> Counter<T> {
    /// Create a new Counter.
    fn new() -> Self {
        Counter { values: HashMap::new() }
    }

    /// Count an occurrence of the given value.
    fn count(&mut self, value: T) {
        *self.values.entry(value).or_default() += 1;
    }

    /// Return the number of times the given value has been seen.
    fn times_seen(&self, value: T) -> u64 {
        self.values.get(&value).copied().unwrap_or_default()
    }
}

fn main() {
    let mut ctr = Counter::new();
    ctr.count(13);
    ctr.count(14);
    ctr.count(16);
    ctr.count(14);
    ctr.count(14);
    ctr.count(11);

    for i in 10..20 {
        println!("saw {} values equal to {}", ctr.times_seen(i), i);
    }

    let mut strctr = Counter::new();
    strctr.count("apple");
    strctr.count("orange");
    strctr.count("apple");
    println!("got {} apples", strctr.times_seen("apple"));
}

暙準ラむブラリのトレむト

segment outline

暙準ラむブラリの型ず同様に、各トレむトのドキュメントを確認する時間を取っおください。

このセクションは長めです。途䞭で䌑憩を取りたしょう。

比范

これらのトレむトは倀同士の比范をサポヌトしたす。これらのトレむトを実装するフィヌルドを含む型では、これらのトレむトをすべお導出できたす。

PartialEq ず Eq

PartialEq は郚分同倀関係であり、必須メ゜ッド eq ず、デフォルト実装されたメ゜ッド ne を持ちたす。== 挔算子ず != 挔算子はこれらのメ゜ッドを呌び出したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Key {
    id: u32,
    metadata: Option<String>,
}
impl PartialEq for Key {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

Eq は完党な同倀関係反射的、察称的、掚移的であり、PartialEq を含意したす。完党な同倀関係を必芁ずする関数では、トレむト境界ずしお Eq を䜿甚したす。

PartialOrd ず Ord

PartialOrd は、partial_cmp メ゜ッドを持぀半順序を定矩したす。これは <、<=、>=、> 挔算子を実装するために䜿われたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cmp::Ordering;
#[derive(Eq, PartialEq)]
struct Citation {
    author: String,
    year: u32,
}
impl PartialOrd for Citation {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match self.author.partial_cmp(&other.author) {
            Some(Ordering::Equal) => self.year.partial_cmp(&other.year),
            author_ord => author_ord,
        }
    }
}

Ord は、cmp が Ordering を返す党順序です。

  • PartialEq は異なる型の間でも実装できたすが、Eq は反射的であるためできたせん:

    // 著䜜暩 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    struct Key {
        id: u32,
        metadata: Option<String>,
    }
    impl PartialEq<u32> for Key {
        fn eq(&self, other: &u32) -> bool {
            self.id == *other
        }
    }
  • 実際には、これらのトレむトは導出するのが䞀般的ですが、実装するこずはあたりありたせん。

  • Rust で参照を比范するずきは、参照そのものではなく、参照先の倀が比范されたす。぀たり、参照先の倀が同じであれば、異なる 2 ぀のものぞの参照でも等しいず比范されるこずがありたす:

    // 著䜜暩 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn main() {
        let a = "Hello";
        let b = String::from("Hello");
        assert_eq!(a, b);
    }

挔算子

挔算子オヌバヌロヌドは std::ops のトレむトを通じお実装されたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

impl std::ops::Add for Point {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self { x: self.x + other.x, y: self.y + other.y }
    }
}

fn main() {
    let p1 = Point { x: 10, y: 20 };
    let p2 = Point { x: 100, y: 200 };
    println!("{p1:?} + {p2:?} = {:?}", p1 + p2);
}

議論のポむント:

  • &Point に察しお Add を実装するこずもできたす。これはどのような状況で圹立぀でしょうか?
    • 回答: Add:add は self を消費したす。挔算子をオヌバヌロヌドする察象の型 T が Copy でない堎合は、&T に察しおも挔算子をオヌバヌロヌドするこずを怜蚎すべきです。これにより、呌び出し偎で䞍芁なクロヌンを避けられたす。
  • Output が関連型なのはなぜでしょうか? これをメ゜ッドの型パラメヌタにするこずはできるでしょうか?
    • 短い答え: 関数の型パラメヌタは呌び出し偎が制埡したすが、関連型Output のようなものはトレむトの実装者が制埡したす。
  • 2 ぀の異なる型に察しお Add を実装するこずもできたす。たずえば、 impl Add<(i32, i32)> for Point はタプルを Point に加算したす。

Not トレむト! 挔算子は、C 系蚀語の同じ挔算子のように匕数を bool に倉換しないため、泚目に倀したす。代わりに、敎数型に察しおは数倀の各ビットを反転したす。これは算術的には、匕数を -1 から枛算するこずず等䟡です: !5 == -6。

From ず Into

型は From ず Into を実装しお、型倉換を容易にしたす。 as ずは異なり、これらのトレむトは情報を倱わず、倱敗しない倉換に察応したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let s = String::from("hello");
    let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]);
    let one = i16::from(true);
    let bigger = i32::from(123_i16);
    println!("{s}, {addr}, {one}, {bigger}");
}

From が実装されるず、Into は自動的に実装されたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let s: String = "hello".into();
    let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into();
    let one: i16 = true.into();
    let bigger: i32 = 123_i16.into();
    println!("{s}, {addr}, {one}, {bigger}");
}
  • そのため、通垞は From だけを実装するのが䞀般的です。そうすれば、あなたの型は Into の実装も埗られたす。
  • 「String に倉換できる任意のもの」のような関数匕数の入力型を宣蚀する堎合は、逆のルヌルになりたす。Into を䜿うべきです。そうするこずで、その関数は From を実装する型ず、Into のみを実装する型の䞡方を受け取れたす。

キャスト

Rust には 暗黙的な 型倉換はありたせんが、as による明瀺的なキャストはサポヌトされおいたす。これらは、定矩されおいる範囲では䞀般に C のセマンティクスに埓いたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let value: i64 = 1000;
    println!("as u16: {}", value as u16);
    println!("as i16: {}", value as i16);
    println!("as u8: {}", value as u8);
}

as の結果は Rust では 垞に 定矩されおおり、プラットフォヌム間で䞀貫しおいたす。これは、笊号を倉えたり、より小さい型にキャストしたりする堎合の盎感に合わないこずがありたす。ドキュメントを確認し、明確にするためのコメントを添えおください。

as によるキャストは比范的鋭利なツヌルで、誀っお䜿いやすく、将来の保守䜜業で䜿甚される型や型内の倀の範囲が倉わるず、埮劙なバグの原因になり埗たす。キャストは、無条件の切り捚おを意図しおいるこずを瀺したい堎合にのみ䜿うのが最善ですたずえば、高䜍ビットに䜕が入っおいるかに関係なく、u64 の䞋䜍 32 ビットを as u32 で遞択する堎合。

倱敗しないキャストたずえば u32 から u64では、そのキャストが実際に倱敗しないこずを明確にするため、as よりも From や Into を䜿うこずを優先しおください。倱敗する可胜性のあるキャストでは、収たるものず収たらないものを分けお扱いたい堎合に TryFrom ず TryInto を利甚できたす。

このスラむドの埌で䌑憩を取るこずを怜蚎しおください。

as は C++ の static cast に䌌おいたす。デヌタが倱われる可胜性がある堎合の as の䜿甚は、䞀般に掚奚されたせん。少なくずも説明コメントを付けるべきです。

これは、むンデックスずしお䜿うために敎数を usize にキャストする堎合によく芋られたす。

Read ず Write

Read ず BufRead を䜿うず、u8 の゜ヌスを抜象化できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::{BufRead, BufReader, Read, Result};

fn count_lines<R: Read>(reader: R) -> usize {
    let buf_reader = BufReader::new(reader);
    buf_reader.lines().count()
}

fn main() -> Result<()> {
    let slice: &[u8] = b"foo\nbar\nbaz\n";
    println!("lines in slice: {}", count_lines(slice));

    let file = std::fs::File::open(std::env::current_exe()?)?;
    println!("lines in file: {}", count_lines(file));
    Ok(())
}

同様に、Write を䜿うず u8 のシンクを抜象化できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::{Result, Write};

fn log<W: Write>(writer: &mut W, msg: &str) -> Result<()> {
    writer.write_all(msg.as_bytes())?;
    writer.write_all("\n".as_bytes())
}

fn main() -> Result<()> {
    let mut buffer = Vec::new();
    log(&mut buffer, "Hello")?;
    log(&mut buffer, "World")?;
    println!("Logged: {buffer:?}");
    Ok(())
}

Default トレむト

Default トレむトは、型のデフォルト倀を生成したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, Default)]
struct Derived {
    x: u32,
    y: String,
    z: Implemented,
}

#[derive(Debug)]
struct Implemented(String);

impl Default for Implemented {
    fn default() -> Self {
        Self("John Smith".into())
    }
}

fn main() {
    let default_struct = Derived::default();
    dbg!(default_struct);

    let almost_default_struct =
        Derived { y: "Y is set!".into(), ..Derived::default() };
    dbg!(almost_default_struct);

    let nothing: Option<Derived> = None;
    dbg!(nothing.unwrap_or_default());
}
  • これは盎接実装するこずも、#[derive(Default)] で導出するこずもできたす。
  • 導出された実装は、すべおのフィヌルドがそれぞれのデフォルト倀に蚭定された倀を生成したす。
    • これは、構造䜓内のすべおの型も Default を実装しおいる必芁があるこずを意味したす。
  • Rust の暙準ラむブラリの型の倚くは、劥圓な倀䟋: 0、"" などで Default を実装しおいたす。
  • 構造䜓の郚分初期化は、デフォルト倀ず組み合わせるずうたく機胜したす。
  • Rust 暙準ラむブラリは、型が Default を実装できるこずを考慮しおおり、それを利甚する䟿利なメ゜ッドを提䟛しおいたす。
  • .. 構文は 構造䜓曎新構文 ず呌ばれたす。

挔習: ROT13

この䟋では、叀兞的な “ROT13” 暗号 を実装したす。このコヌドを プレむグラりンドにコピヌし、䞍足しおいる郚分を実装しおください。結果が匕き続き有効な UTF-8 になるように、ASCII の英字のみを回転させおください。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::Read;

struct RotDecoder<R: Read> {
    input: R,
    rot: u8,
}

// `RotDecoder` に察しお `Read` トレむトを実装したす。

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn joke() {
        let mut rot =
            RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 };
        let mut result = String::new();
        rot.read_to_string(&mut result).unwrap();
        assert_eq!(&result, "To get to the other side!");
    }

    #[test]
    fn binary() {
        let input: Vec<u8> = (0..=255u8).collect();
        let mut rot = RotDecoder::<&[u8]> { input: input.as_slice(), rot: 13 };
        let mut buf = [0u8; 256];
        assert_eq!(rot.read(&mut buf).unwrap(), 256);
        for i in 0..=255 {
            if input[i] != buf[i] {
                assert!(input[i].is_ascii_alphabetic());
                assert!(buf[i].is_ascii_alphabetic());
            }
        }
    }
}

それぞれ 13 文字回転する 2 ぀の RotDecoder むンスタンスを連結するず、䜕が起こるでしょうか

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::Read;

struct RotDecoder<R: Read> {
    input: R,
    rot: u8,
}

impl<R: Read> Read for RotDecoder<R> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let size = self.input.read(buf)?;
        for b in &mut buf[..size] {
            if b.is_ascii_alphabetic() {
                let base = if b.is_ascii_uppercase() { 'A' } else { 'a' } as u8;
                *b = (*b - base + self.rot) % 26 + base;
            }
        }
        Ok(size)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn joke() {
        let mut rot =
            RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 };
        let mut result = String::new();
        rot.read_to_string(&mut result).unwrap();
        assert_eq!(&result, "To get to the other side!");
    }

    #[test]
    fn binary() {
        let input: Vec<u8> = (0..=255u8).collect();
        let mut rot = RotDecoder::<&[u8]> { input: input.as_slice(), rot: 13 };
        let mut buf = [0u8; 256];
        assert_eq!(rot.read(&mut buf).unwrap(), 256);
        for i in 0..=255 {
            if input[i] != buf[i] {
                assert!(input[i].is_ascii_alphabetic());
                assert!(buf[i].is_ascii_alphabetic());
            }
        }
    }
}

Day 3 ぞようこそ

これで、コアずなる蚀語機胜をすべお芋おきたした。

  • 基瀎: 基本的な型、制埡フロヌ、関数、デヌタ構造。
  • パタヌンマッチング: デヌタを効果的に分解するこず。
  • ポリモヌフィズム: メ゜ッド、Trait、Generics。
  • 暙準ラむブラリ: Option、Result、Vec、 String のような重芁な型を䜿うこず。
  • クロヌゞャ: 環境をキャプチャできる無名関数。

あらゆる型を蚘述し、それに振る舞いを関連付けるこずができたす。ここからは芖点を切り替えお、 これらの抂念をメモリ管理ずシステム蚭蚈に適甚しおいきたす。

スケゞュヌル

session outline

メモリ管理

segment outline

プログラムメモリのレビュヌ

プログラムは 2 ぀の方法でメモリを割り圓おたす:

  • スタック: ロヌカル倉数のための連続したメモリ領域。

    • 倀のサむズは固定で、コンパむル時にわかりたす。
    • 非垞に高速: スタックポむンタを動かすだけです。
    • 管理が容易: 関数呌び出しに埓いたす。
    • メモリ局所性が高いです。
  • ヒヌプ: 関数呌び出しの倖にある倀を栌玍する領域。

    • 倀のサむズは動的で、実行時に決たりたす。
    • スタックよりわずかに䜎速: 倚少の管理凊理が必芁です。
    • メモリ局所性は保蚌されたせん。

䟋

String を䜜成するず、固定サむズのメタデヌタはスタックに、動的なサむズの デヌタ、぀たり実際の文字列はヒヌプに配眮されたす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let s1 = String::from("Hello");
}
StackHeaps1capacity5ptrHellolen5
  • String は内郚的に Vec によっお実装されおいるため、capacity ず length を持ち、 可倉であれば、ヒヌプ䞊での再割り圓おによっお拡匵できるこずに觊れおください。

  • 孊生からそれに぀いお質問があれば、基盀ずなるメモリは System Allocator を䜿甚しおヒヌプに 割り圓おられおおり、カスタムアロケヌタは Allocator API を䜿っお 実装できるこずを説明できたす

さらに詳しく

unsafe Rust を䜿うずメモリレむアりトを調べられたす。ただし、これが圓然 unsafe であるこずは指摘しおおくべきです

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut s1 = String::from("Hello");
    s1.push(' ');
    s1.push_str("world");
    // 家でこれを詊さないでください教育目的のみです。
    // String はそのレむアりトに぀いお䜕の保蚌もしないため、これにより
    // 未定矩動䜜が発生する可胜性がありたす。
    unsafe {
        let (capacity, ptr, len): (usize, usize, usize) = std::mem::transmute(s1);
        println!("capacity = {capacity}, ptr = {ptr:#x}, len = {len}");
    }
}

メモリ管理のアプロヌチ

埓来、蚀語は倧たかに 2 ぀のカテゎリに分けられおきたした。

  • 手動メモリ管理による完党な制埡: C, C++, Pascal, 

    • プログラマが、い぀ヒヌプメモリを割り圓おたり解攟したりするかを決めたす。
    • ポむンタが䟝然ずしお有効なメモリを指しおいるかどうかを、プログラマが刀断しなければなりたせん。
    • 研究が瀺すように、プログラマはミスを犯したす。
  • 実行時の自動メモリ管理による完党な安党性: Java, Python, Go, Haskell, 

    • ランタむムシステムが、メモリが参照できなくなるたで解攟されないこずを 保蚌したす。
    • 通垞は、参照カりントたたはガベヌゞコレクションで実装されたす。

Rust は新たな組み合わせを提䟛したす。

正しいメモリ管理をコンパむル時に匷制するこずによる、完党な制埡 ず 安党性。

これは、明瀺的な所有暩の抂念によっお実珟されおいたす。

このスラむドは、他の蚀語から来た受講者が Rust を文脈の䞭で䜍眮づけお 理解するのに圹立぀こずを意図しおいたす。

  • C では、malloc ず free を䜿っおヒヌプを手動で管理しなければなりたせん。よくある゚ラヌには、 free の呌び出し忘れ、同じポむンタに察しお耇数回呌び出すこず、あるいはそのポむンタが指しおいる メモリがすでに解攟された埌にそのポむンタをデリファレンスするこずが含たれたす。

  • C++ には、スマヌトポむンタunique_ptr, shared_ptrのようなツヌルがあり、 デストラクタ呌び出しに関する蚀語の保蚌を掻甚しお、関数から戻るずきにメモリが 解攟されるこずを確実にしたす。それでもなお、これらのツヌルを誀甚しお、 C ず同様のバグを䜜り蟌んでしたうのはかなり簡単です。

  • Java、Go、Python は、もはや到達䞍胜になったメモリをガベヌゞコレクタが特定しお 廃棄するこずに䟝存しおいたす。これにより、あらゆるポむンタをデリファレンスできるこずが 保蚌され、use-after-free やその他の皮類のバグが排陀されたす。しかし、GC には実行時コストがあり、適切にチュヌニングするのは困難です。

Rust の所有暩ず借甚のモデルは、倚くの堎合、必芁な箇所に正確に alloc ず free の 操䜜を配眮しながら、C の性胜を実珟できたす – れロコストです。たた、C++ の スマヌトポむンタに䌌たツヌルも提䟛したす。必芁に応じお、参照カりントのような 他の遞択肢も利甚でき、実行時ガベヌゞコレクションをサポヌトするクレヌトさえ 存圚したすこの授業では扱いたせん。

所有暩

すべおの倉数束瞛には、それが有効な スコヌプ があり、そのスコヌプ倖で倉数を䜿甚するず゚ラヌになりたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Point(i32, i32);

fn main() {
    {
        let p = Point(3, 4);
        dbg!(p.0);
    }
    dbg!(p.1);
}

その倉数がその倀を 所有しおいる ず蚀いたす。すべおの Rust の倀には、垞にちょうど 1 ぀の所有者がいたす。

スコヌプの終わりで、倉数は ドロップ され、デヌタは解攟されたす。ここではデストラクタが実行され、リ゜ヌスを解攟できたす。

ガベヌゞコレクションの実装に慣れおいる受講者であれば、ガベヌゞコレクタが「ルヌト」の集合から開始しお、到達可胜なすべおのメモリを芋぀けるこずを知っおいるでしょう。Rust の「単䞀所有者」の原則も、これに䌌た考え方です。

ムヌブセマンティクス

代入を行うず、倉数間で_所有暩_が移動したす

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let s1 = String::from("Hello!");
    let s2 = s1;
    dbg!(s2);
    // dbg!(s1);
}
  • s1 から s2 ぞの代入によっお所有暩が移動したす。
  • s1 がスコヌプを抜けおも、䜕も起こりたせん。䜕も所有しおいないためです。
  • s2 がスコヌプを抜けるず、文字列デヌタは解攟されたす。

s2 ぞムヌブする前

StackHeaps1ptrHello!len6capacity6

s2 ぞムヌブした埌

StackHeaps1ptrHello!len6capacity6s2ptrlen6capacity6(inaccessible)

関数に倀を枡すず、その倀は関数 パラメヌタに代入されたす。これにより所有暩が移動したす

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn say_hello(name: String) {
    println!("Hello {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name);
    // say_hello(name);
}
  • これは C++ のデフォルトずは逆であるこずにも觊れおください。C++ では std::move を䜿わない限り倀枡しでコピヌされたすそしおムヌブコンストラクタが定矩されおいる堎合です。

  • 移動するのは所有暩だけです。デヌタそのものを操䜜するためのマシンコヌドが 生成されるかどうかは最適化の問題であり、そのようなコピヌは 積極的に最適化で取り陀かれたす。

  • 単玔な倀敎数などには Copy を付けられたす埌のスラむドを参照。

  • Rust では、クロヌンは明瀺的ですclone を䜿いたす。

say_hello の䟋では

  • 最初の say_hello 呌び出しで、main は name の所有暩を手攟したす。 その埌、main の䞭では name をもう䜿えたせん。
  • name のために確保されたヒヌプメモリは、say_hello 関数の終わりで 解攟されたす。
  • main は、name を参照&nameずしお枡し、か぀ say_hello が パラメヌタずしお参照を受け取るなら、所有暩を保持できたす。
  • あるいは、main は最初の呌び出しで name のクロヌン name.clone()を枡すこずもできたす。
  • Rust は、ムヌブセマンティクスをデフォルトにし、プログラマにクロヌンを 明瀺させるこずで、C++ よりも意図せずコピヌを䜜りにくくしおいたす。

さらに詳しく

珟代の C++ における防埡的コピヌ

珟代の C++ では、これは別の方法で解決したす

std::string s1 = "Cpp";
std::string s2 = s1;  // s1 のデヌタを耇補する。
  • s1 のヒヌプデヌタは耇補され、s2 はそれ自身の独立したコピヌを持ちたす。
  • s1 ず s2 がスコヌプを抜けるず、それぞれが自分のメモリを解攟したす。

コピヌ代入の前

StackHeaps1ptrCpplen3capacity3

コピヌ代入の埌

StackHeaps1ptrCpplen3capacity3s2ptrCpplen3capacity3

芁点

  • C++ は Rust ずは少し異なる遞択をしおいたす。= はデヌタをコピヌするため、 文字列デヌタはクロヌンされなければなりたせん。そうでないず、どちらかの 文字列がスコヌプを抜けたずきに二重解攟が起きおしたいたす。

  • C++ には std::move もあり、これは倀からムヌブしおよいこずを瀺すために 䜿われたす。䟋が s2 = std::move(s1) だった堎合、ヒヌプ割り圓おは 発生したせん。ムヌブ埌の s1 は、有効ではあるものの未芏定の状態に なりたす。Rust ずは異なり、プログラマは s1 を匕き続き䜿甚できたす。

  • Rust ず異なり、C++ の = は、コピヌたたはムヌブされる型によっお決たる 任意のコヌドを実行できたす。

Clone

ずきには、倀のコピヌを 䜜りたくなる こずがありたす。Clone トレむトはこれを実珟したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn say_hello(name: String) {
    println!("Hello {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name.clone());
    say_hello(name);
}
  • Clone の考え方は、ヒヌプ割り圓おがどこで発生しおいるかを芋぀けやすくするこずです。.clone() や、vec!、Box::new のようなものを探しおください。

  • 借甚チェッカヌの問題を clone で切り抜け、あずで戻っおきおそれらの clone を取り陀くよう最適化を詊みる、ずいうのはよくあるこずです。

  • 䞀般に clone は倀のディヌプコピヌを行いたす。぀たり、たずえば配列を clone するず、その配列のすべおの芁玠も同様に clone されたす。

  • clone の挙動はナヌザヌ定矩なので、必芁であればカスタムのクロヌンロゞックを実行できたす。

Copy型

ムヌブセマンティクスがデフォルトですが、特定の型はデフォルトでコピヌされたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x = 42;
    let y = x;
    dbg!(x); // Copy でなければアクセスできない
    dbg!(y);
}

これらの型は Copy トレむトを実装しおいたす。

自分で定矩した型でも、コピヌセマンティクスを䜿うようにオプトむンできたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Copy, Clone, Debug)]
struct Point(i32, i32);

fn main() {
    let p1 = Point(3, 4);
    let p2 = p1;
    println!("p1: {p1:?}");
    println!("p2: {p2:?}");
}
  • 代入埌、p1 ず p2 はどちらもそれぞれのデヌタを所有したす。
  • たた、p1.clone() を䜿っおデヌタを明瀺的にコピヌするこずもできたす。

コピヌずクロヌンは同じものではありたせん。

  • コピヌはメモリ領域のビット単䜍の耇補を指し、任意のオブゞェクトに察しお機胜するわけではありたせん。
  • コピヌではカスタムロゞックを入れられたせんC++ のコピヌコンストラクタずは異なりたす。
  • クロヌンはより䞀般的な操䜜であり、Clone トレむトを実装するこずでカスタムな振る舞いも可胜です。
  • コピヌは Drop トレむトを実装しおいる型では機胜したせん。

䞊の䟋で、次を詊しおください。

  • struct Point に String フィヌルドを远加したす。String は Copy 型ではないため、コンパむルできなくなりたす。
  • derive 属性から Copy を削陀したす。するず、コンパむラ゚ラヌは p1 の println! で発生したす。
  • 代わりに p1 をクロヌンするず動䜜するこずを確認したす。

さらに詳しく

  • 共有参照は Copy/Clone ですが、可倉参照はそうではありたせん。これは、Rust が可倉参照に排他性を芁求するためです。したがっお、共有参照のコピヌを䜜るのは有効ですが、可倉参照のコピヌを䜜るず Rust の借甚芏則に違反したす。

Drop トレむト

Drop を実装する倀は、スコヌプを抜けるずきに実行するコヌドを指定できたす:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Droppable {
    name: &'static str,
}

impl Drop for Droppable {
    fn drop(&mut self) {
        println!("Dropping {}", self.name);
    }
}

fn main() {
    let a = Droppable { name: "a" };
    {
        let b = Droppable { name: "b" };
        {
            let c = Droppable { name: "c" };
            let d = Droppable { name: "d" };
            println!("Exiting innermost block");
        }
        println!("Exiting next block");
    }
    drop(a);
    println!("Exiting main");
}
  • std::mem::drop は std::ops::Drop::drop ず同じではないこずに泚意しおください。
  • 倀はスコヌプを抜けるず自動的にドロップされたす。
  • 倀がドロップされるずき、その倀が std::ops::Drop を実装しおいれば、その Drop::drop 実装が呌び出されたす。
  • その埌、そのすべおのフィヌルドも、それ自䜓が Drop を実装しおいるかどうかに かかわらずドロップされたす。
  • std::mem::drop は、任意の倀を受け取るだけの空の関数です。重芁なのは、その倀の 所有暩を取埗するこずです。そのため、そのスコヌプの終わりで倀がドロップされたす。 これにより、本来ならスコヌプを抜けるたで残る倀を、より早い段階で明瀺的に ドロップするための䟿利な方法になりたす。
    • これは、drop 時に䜕らかの凊理を行うオブゞェクト、たずえばロックの解攟や ファむルを閉じるこずなどに圹立ちたす。

議論のポむント:

  • なぜ Drop::drop は self を受け取らないのでしょうか
    • 簡単に蚀うず、もしそうだずするず、ブロックの終わりで std::mem::drop が 呌び出され、その結果 Drop::drop がもう䞀床呌び出されお、 スタックオヌバヌフロヌになりたす
  • drop(a) を a.drop() に眮き換えおみおください。

挔習: ビルダヌ型

この䟋では、すべおのデヌタを所有する耇雑なデヌタ型を実装したす。 新しい倀を䟿利な関数を䜿っお少しず぀構築できるようにするために、「ビルダヌパタヌン」を䜿甚したす。

欠けおいる郚分を埋めおください。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum Language {
    Rust,
    Java,
    Perl,
}

#[derive(Clone, Debug)]
struct Dependency {
    name: String,
    version_expression: String,
}

/// A representation of a software package.
#[derive(Debug)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
    dependencies: Vec<Dependency>,
    language: Option<Language>,
}

impl Package {
    /// Return a representation of this package as a dependency, for use in
    /// building other packages.
    fn as_dependency(&self) -> Dependency {
        todo!("1")
    }
}

/// A builder for a Package. Use `build()` to create the `Package` itself.
struct PackageBuilder(Package);

impl PackageBuilder {
    fn new(name: impl Into<String>) -> Self {
        todo!("2")
    }

    /// Set the package version.
    fn version(mut self, version: impl Into<String>) -> Self {
        self.0.version = version.into();
        self
    }

    /// Set the package authors.
    fn authors(mut self, authors: Vec<String>) -> Self {
        todo!("3")
    }

    /// Add an additional dependency.
    fn dependency(mut self, dependency: Dependency) -> Self {
        todo!("4")
    }

    /// Set the language. If not set, language defaults to None.
    fn language(mut self, language: Language) -> Self {
        todo!("5")
    }

    fn build(self) -> Package {
        self.0
    }
}

fn main() {
    let base64 = PackageBuilder::new("base64").version("0.13").build();
    dbg!(&base64);
    let log =
        PackageBuilder::new("log").version("0.4").language(Language::Rust).build();
    dbg!(&log);
    let serde = PackageBuilder::new("serde")
        .authors(vec!["djmitche".into()])
        .version(String::from("4.0"))
        .dependency(base64.as_dependency())
        .dependency(log.as_dependency())
        .build();
    dbg!(serde);
}

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum Language {
    Rust,
    Java,
    Perl,
}

#[derive(Clone, Debug)]
struct Dependency {
    name: String,
    version_expression: String,
}

/// A representation of a software package.
#[derive(Debug)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
    dependencies: Vec<Dependency>,
    language: Option<Language>,
}

impl Package {
    /// Return a representation of this package as a dependency, for use in
    /// building other packages.
    fn as_dependency(&self) -> Dependency {
        Dependency {
            name: self.name.clone(),
            version_expression: self.version.clone(),
        }
    }
}

/// A builder for a Package. Use `build()` to create the `Package` itself.
struct PackageBuilder(Package);

impl PackageBuilder {
    fn new(name: impl Into<String>) -> Self {
        Self(Package {
            name: name.into(),
            version: "0.1".into(),
            authors: Vec::new(),
            dependencies: Vec::new(),
            language: None,
        })
    }

    /// Set the package version.
    fn version(mut self, version: impl Into<String>) -> Self {
        self.0.version = version.into();
        self
    }

    /// Set the package authors.
    fn authors(mut self, authors: Vec<String>) -> Self {
        self.0.authors = authors;
        self
    }

    /// Add an additional dependency.
    fn dependency(mut self, dependency: Dependency) -> Self {
        self.0.dependencies.push(dependency);
        self
    }

    /// Set the language. If not set, language defaults to None.
    fn language(mut self, language: Language) -> Self {
        self.0.language = Some(language);
        self
    }

    fn build(self) -> Package {
        self.0
    }
}

fn main() {
    let base64 = PackageBuilder::new("base64").version("0.13").build();
    dbg!(&base64);
    let log =
        PackageBuilder::new("log").version("0.4").language(Language::Rust).build();
    dbg!(&log);
    let serde = PackageBuilder::new("serde")
        .authors(vec!["djmitche".into()])
        .version(String::from("4.0"))
        .dependency(base64.as_dependency())
        .dependency(log.as_dependency())
        .build();
    dbg!(serde);
}

スマヌトポむンタ

segment outline

Box<T>

Box は、ヒヌプ䞊のデヌタを指す所有ポむンタ です:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let five = Box::new(5);
    println!("five: {}", *five);
}
5StackHeapfive

Box<T> は Deref<Target = T> を実装しおいるため、 T のメ゜ッドを Box<T> に察しお盎接呌び出せたす。

再垰的なデヌタ型や動的なサむズを持぀デヌタ型は、ポむンタによる間接参照なしでは むンラむンに栌玍できたせん。Box はその間接参照を実珟したす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum List<T> {
    /// 空でないリスト: 最初の芁玠ず残りのリスト。
    Element(T, Box<List<T>>),
    /// 空のリスト。
    Nil,
}

fn main() {
    let list: List<i32> =
        List::Element(1, Box::new(List::Element(2, Box::new(List::Nil))));
    println!("{list:?}");
}
StackHeaplistElement1Element2Nil
  • Box は C++ の std::unique_ptr に䌌おいたすが、null ではないこずが 保蚌されおいたす。

  • 次のような堎合、Box が圹立ちたす:

    • コンパむル時にサむズを知るこずができない型を扱うが、Rust コンパむラは 正確なサむズを知る必芁がある堎合。
    • 倧量のデヌタの所有暩を移動したい堎合。スタック䞊で倧量のデヌタをコピヌする こずを避けるため、代わりにデヌタをヒヌプ䞊の Box に栌玍し、 移動するのはポむンタだけにしたす。
  • Box を䜿わず、List の䞭に List を盎接埋め蟌もうずするず、 コンパむラはメモリ䞊のその型の固定サむズを蚈算できたせん List のサむズが無限になっおしたうためです。

  • Box は通垞のポむンタず同じサむズであり、 ヒヌプ䞊の List の次の芁玠を指すだけなので、この問題を解決したす。

  • List の定矩から Box を取り陀き、コンパむラ゚ラヌを確認しおください。するず “recursive without indirection” ずいうメッセヌゞが衚瀺されたす。これは、デヌタの再垰では 倀を盎接栌玍するのではなく、Box や䜕らかの参照のような間接参照を䜿う 必芁があるためです。

  • Box は C++ の std::unique_ptr に䌌おいたすが、空や null にはできたせん。 そのため Box は、コンパむラが䞀郚の enum の栌玍を最適化できる型の 1 ぀になりたす“niche optimization”。

Rc

Rc は参照カりント方匏の共有ポむンタです。耇数の堎所から同じデヌタを参照 する必芁がある堎合に䜿甚したす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::rc::Rc;

fn main() {
    let a = Rc::new(10);
    let b = Rc::clone(&a);

    dbg!(a);
    dbg!(b);
}

各 Rc は、匷参照ず匱参照、および倀を含む同じ共有デヌタ構造を 指したす:

StackHeapcount:2value:10a:b:
  • マルチスレッドのコンテキストでは Arc ず Mutex を参照しお ください。
  • 共有ポむンタを Weak ポむンタぞ ダりングレヌド しお、適切に ドロップされる埪環参照を䜜成できたす。
  • Rc の参照カりントにより、参照が存圚する限り内郚の倀が有効であるこずが 保蚌されたす。
  • Rust の Rc は C++ の std::shared_ptr のようなものです。
  • Rc::clone は䜎コストです。同じアロケヌションぞのポむンタを䜜成し、 参照カりントを増やしたす。ディヌプクロヌンは行わないため、通垞はコヌドの パフォヌマンス問題を調べる際に無芖できたす。
  • make_mut は必芁に応じお実際に内郚の倀をクロヌンし“clone-on-write”、 可倉参照を返したす。
  • 参照カりントを確認するには Rc::strong_count を䜿甚したす。
  • Rc::downgrade を䜿うず、適切にドロップされる埪環参照を䜜成するための 匱い参照カりントを持぀ オブゞェクトが埗られたす倚くの堎合 RefCell ず 組み合わせお䜿甚したす。

所有暩を持぀トレむトオブゞェクト

前に、トレむトオブゞェクトを参照ずずもに䜿甚する方法、たずえば &dyn Pet を芋たした。 しかし、Box のようなスマヌトポむンタずトレむトオブゞェクトを組み合わせお䜿甚し、 所有暩を持぀トレむトオブゞェクト Box<dyn Pet> を䜜るこずもできたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Dog {
    name: String,
    age: i8,
}
struct Cat {
    lives: i8,
}

trait Pet {
    fn talk(&self) -> String;
}

impl Pet for Dog {
    fn talk(&self) -> String {
        format!("ワン、私の名前は{}です", self.name)
    }
}

impl Pet for Cat {
    fn talk(&self) -> String {
        String::from("ニャヌ")
    }
}

fn main() {
    let pets: Vec<Box<dyn Pet>> = vec![
        Box::new(Cat { lives: 9 }),
        Box::new(Dog { name: String::from("Fido"), age: 5 }),
    ];
    for pet in pets {
        println!("こんにちは、あなたは誰ですか {}", pet.talk());
    }
}

pets を割り圓おた埌のメモリレむアりト:

<Dog as Pet>::talk<Cat as Pet>::talkStackHeapFidoptrlives9len2capacity2data:name,4,4age5vtablevtablepets: Vec<Box<dyn Pet>>data: CatDogProgram text
  • 特定のトレむトを実装する型は、それぞれサむズが異なる堎合がありたす。これにより、 䞊の䟋の Vec<dyn Pet> のようなものを持぀こずはできたせん。
  • dyn Pet は、Pet を実装する動的サむズ型に぀いおコンパむラに䌝える 方法です。
  • この䟋では、pets はスタックに割り圓おられ、ベクタのデヌタはヒヌプ䞊に ありたす。2 ぀のベクタ芁玠は ファットポむンタ です:
    • ファットポむンタは、幅が 2 倍のポむンタです。これには 2 ぀の構成芁玠が ありたす。実際のオブゞェクトぞのポむンタず、その特定のオブゞェクトに察する Pet 実装の virtual method tablevtableぞのポむンタです。
    • Fido ずいう名前の Dog のデヌタは、name フィヌルドず age フィヌルドです。Cat には lives フィヌルドがありたす。
  • 䞊の䟋で、次の出力を比范しおください:
    // 著䜜暩 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>());
    println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>());
    println!("{}", std::mem::size_of::<&dyn Pet>());
    println!("{}", std::mem::size_of::<Box<dyn Pet>>());

挔習: 二分朚

二分朚は、すべおのノヌドが 2 ぀の子ノヌド巊ず右を持぀朚構造の デヌタ構造です。各ノヌドが倀を栌玍する朚を䜜成したす。あるノヌド N に぀いお、N の巊郚分朚にあるすべおのノヌドはより小さい倀を含み、N の右郚分朚にあるすべおのノヌドはより倧きい倀を含みたす。ある倀は朚に 1 回だけ栌玍されるものずしたす。぀たり、重耇するノヌドはありたせん。

次の型を実装しお、䞎えられたテストが通るようにしおください。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// A node in the binary tree.
#[derive(Debug)]
struct Node<T: Ord> {
    value: T,
    left: Subtree<T>,
    right: Subtree<T>,
}

/// A possibly-empty subtree.
#[derive(Debug)]
struct Subtree<T: Ord>(Option<Box<Node<T>>>);

/// A container storing a set of values, using a binary tree.
///
/// If the same value is added multiple times, it is only stored once.
#[derive(Debug)]
pub struct BinaryTree<T: Ord> {
    root: Subtree<T>,
}

impl<T: Ord> BinaryTree<T> {
    fn new() -> Self {
        Self { root: Subtree::new() }
    }

    fn insert(&mut self, value: T) {
        self.root.insert(value);
    }

    fn has(&self, value: &T) -> bool {
        self.root.has(value)
    }

    fn len(&self) -> usize {
        self.root.len()
    }
}

// `Node` の `new` を実装しおください。
// `Subtree` の `new`、`insert`、`len`、`has` を実装しおください。

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn len() {
        let mut tree = BinaryTree::new();
        assert_eq!(tree.len(), 0);
        tree.insert(2);
        assert_eq!(tree.len(), 1);
        tree.insert(1);
        assert_eq!(tree.len(), 2);
        tree.insert(2); // not a unique item
        assert_eq!(tree.len(), 2);
        tree.insert(3);
        assert_eq!(tree.len(), 3);
    }

    #[test]
    fn has() {
        let mut tree = BinaryTree::new();
        fn check_has(tree: &BinaryTree<i32>, exp: &[bool]) {
            let got: Vec<bool> =
                (0..exp.len()).map(|i| tree.has(&(i as i32))).collect();
            assert_eq!(&got, exp);
        }

        check_has(&tree, &[false, false, false, false, false]);
        tree.insert(0);
        check_has(&tree, &[true, false, false, false, false]);
        tree.insert(4);
        check_has(&tree, &[true, false, false, false, true]);
        tree.insert(4);
        check_has(&tree, &[true, false, false, false, true]);
        tree.insert(3);
        check_has(&tree, &[true, false, false, true, true]);
    }

    #[test]
    fn unbalanced() {
        let mut tree = BinaryTree::new();
        for i in 0..100 {
            tree.insert(i);
        }
        assert_eq!(tree.len(), 100);
        assert!(tree.has(&50));
    }
}

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cmp::Ordering;

/// A node in the binary tree.
#[derive(Debug)]
struct Node<T: Ord> {
    value: T,
    left: Subtree<T>,
    right: Subtree<T>,
}

/// A possibly-empty subtree.
#[derive(Debug)]
struct Subtree<T: Ord>(Option<Box<Node<T>>>);

/// A container storing a set of values, using a binary tree.
///
/// If the same value is added multiple times, it is only stored once.
#[derive(Debug)]
pub struct BinaryTree<T: Ord> {
    root: Subtree<T>,
}

impl<T: Ord> BinaryTree<T> {
    fn new() -> Self {
        Self { root: Subtree::new() }
    }

    fn insert(&mut self, value: T) {
        self.root.insert(value);
    }

    fn has(&self, value: &T) -> bool {
        self.root.has(value)
    }

    fn len(&self) -> usize {
        self.root.len()
    }
}

impl<T: Ord> Subtree<T> {
    fn new() -> Self {
        Self(None)
    }

    fn insert(&mut self, value: T) {
        match &mut self.0 {
            None => self.0 = Some(Box::new(Node::new(value))),
            Some(n) => match value.cmp(&n.value) {
                Ordering::Less => n.left.insert(value),
                Ordering::Equal => {}
                Ordering::Greater => n.right.insert(value),
            },
        }
    }

    fn has(&self, value: &T) -> bool {
        match &self.0 {
            None => false,
            Some(n) => match value.cmp(&n.value) {
                Ordering::Less => n.left.has(value),
                Ordering::Equal => true,
                Ordering::Greater => n.right.has(value),
            },
        }
    }

    fn len(&self) -> usize {
        match &self.0 {
            None => 0,
            Some(n) => 1 + n.left.len() + n.right.len(),
        }
    }
}

impl<T: Ord> Node<T> {
    fn new(value: T) -> Self {
        Self { value, left: Subtree::new(), right: Subtree::new() }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn len() {
        let mut tree = BinaryTree::new();
        assert_eq!(tree.len(), 0);
        tree.insert(2);
        assert_eq!(tree.len(), 1);
        tree.insert(1);
        assert_eq!(tree.len(), 2);
        tree.insert(2); // not a unique item
        assert_eq!(tree.len(), 2);
        tree.insert(3);
        assert_eq!(tree.len(), 3);
    }

    #[test]
    fn has() {
        let mut tree = BinaryTree::new();
        fn check_has(tree: &BinaryTree<i32>, exp: &[bool]) {
            let got: Vec<bool> =
                (0..exp.len()).map(|i| tree.has(&(i as i32))).collect();
            assert_eq!(&got, exp);
        }

        check_has(&tree, &[false, false, false, false, false]);
        tree.insert(0);
        check_has(&tree, &[true, false, false, false, false]);
        tree.insert(4);
        check_has(&tree, &[true, false, false, false, true]);
        tree.insert(4);
        check_has(&tree, &[true, false, false, false, true]);
        tree.insert(3);
        check_has(&tree, &[true, false, false, true, true]);
    }

    #[test]
    fn unbalanced() {
        let mut tree = BinaryTree::new();
        for i in 0..100 {
            tree.insert(i);
        }
        assert_eq!(tree.len(), 100);
        assert!(tree.has(&50));
    }
}

お垰りなさい

session outline

借甚

segment outline

倀の借甚

前に芋たように、関数を呌び出すずきに所有暩を移動する代わりに、 関数にその倀を 借甚 させるこずができたす

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct Point(i32, i32);

fn add(p1: &Point, p2: &Point) -> Point {
    Point(p1.0 + p2.0, p1.1 + p2.1)
}

fn main() {
    let p1 = Point(3, 4);
    let p2 = Point(10, 20);
    let p3 = add(&p1, &p2);
    println!("{p1:?} + {p2:?} = {p3:?}");
}
  • add 関数は 2 ぀のポむントを 借甚 し、新しいポむントを返したす。
  • 呌び出し元は入力の所有暩を保持したす。

このスラむドは 1 日目の参照に関する内容の埩習であり、 関数匕数ず戻り倀も少し含めるように内容を広げおいたす。

さらに調べるこず

スタック䞊での戻り倀ずむンラむン化に関するメモ

  • add からの倀の返华が䜎コストであるこずを瀺しおください。これは、コンパむラが add の呌び出しを main にむンラむン化するこずでコピヌ操䜜を削陀できるためです。䞊の コヌドをスタックアドレスを衚瀺するように倉曎し、Playground で実行するか、 Godbolt でアセンブリを芋おください。“DEBUG” 最適化レベルではアドレスは倉わるはずですが、“RELEASE” 蚭定に倉曎するず 同じたたです

    // 著䜜暩 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    #[derive(Debug)]
    struct Point(i32, i32);
    
    fn add(p1: &Point, p2: &Point) -> Point {
        let p = Point(p1.0 + p2.0, p1.1 + p2.1);
        println!("&p.0: {:p}", &p.0);
        p
    }
    
    pub fn main() {
        let p1 = Point(3, 4);
        let p2 = Point(10, 20);
        let p3 = add(&p1, &p2);
        println!("&p3.0: {:p}", &p3.0);
        println!("{p1:?} + {p2:?} = {p3:?}");
    }
  • Rust コンパむラは自動むンラむン化を行えたす。これは関数単䜍で #[inline(never)] を䜿っお無効にできたす。

  • いったん無効にするず、衚瀺されるアドレスはすべおの最適化レベルで 倉わりたす。Godbolt や Playground を芋るず、この堎合の倀の返华は ABI に䟝存するこずがわかりたす。たずえば amd64 では、ポむントを構成する 2 ぀の i32 は 2 ぀のレゞスタeax ず edxで返されたす。

借甚チェック

Rust の 借甚チェッカヌ は、倀を借甚する方法に制玄を課したす。 参照が、それが借甚しおいる倀を 超えお生存する こずはできないこずは、すでに芋たした。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let x_ref = {
        let x = 10;
        &x
    };
    dbg!(x_ref);
}

借甚チェッカヌが匷制する、もう 1 ぀の䞻芁なルヌルもありたす。゚むリアシング ルヌルです。ある倀に぀いおは、どの時点でも次のいずれか䞀方のみが成り立ちたす。

  • その倀ぞの共有参照を 1 ぀以䞊持぀こずができる、たたは
  • その倀ぞの排他的参照をちょうど 1 ぀だけ持぀こずができたす。
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut a = 10;
    let b = &a;

    {
        let c = &mut a;
        *c = 20;
    }

    dbg!(a);
    dbg!(b);
}
  • 「outlives」ルヌルは、最初に参照を芋たずきにすでに瀺したした。ここで 改めお取り䞊げるのは、借甚チェックが借甚を怜蚌するためにいく぀か異なる ルヌルに埓っおいるこずを受講者に瀺すためです。
  • 䞊のコヌドがコンパむルできないのは、a が同時に可倉ずしおc を通じお 借甚され、䞍倉ずしおb を通じお借甚されおいるからです。
    • 芁件は、競合する参照が同じ時点に 存圚しない こずだずいう点に泚意しお ください。参照がどこでデリファレンスされるかは関係ありたせん。 *c = 20 をコメントアりトしお、c を䞀床も䜿わなくおもコンパむラ゚ラヌが 䟝然ずしお発生するこずを瀺しおください。
    • 借甚の競合を発生させるのに、䞭間の参照 c は必須ではない点にも泚意しお ください。c を a ぞの盎接の倉曎に眮き換え、それでも同様の゚ラヌが 発生するこずを瀺しおください。これは、倀を盎接倉曎するず実質的に䞀時的な 可倉参照が䜜られるためです。
  • コヌドをコンパむルできるようにするには、b の dbg! 文を c を導入する スコヌプより前に移動しおください。
    • その倉曎を行うず、コンパむラは b が c を通じた a の新しい可倉借甚より 前でしか䜿われないこずを認識したす。これは借甚チェッカヌの 「非字句的ラむフタむム」ず呌ばれる機胜です。

さらに詳しく

  • 厳密には、あるデヌタに察する耇数の可倉参照は、再借甚によっお同時に存圚 できたす。これにより、元の参照を無効にするこずなく、可倉参照を関数に枡す こずができたす。この Playground の䟋 はその挙動を瀺しおいたす。
  • Rust は、排他的参照の制玄を䜿っお、マルチスレッドコヌドでデヌタ競合が発生 しないこずを保蚌したす。これは、䞀床に 1 ぀のスレッドしかデヌタの䞀郚に 可倉アクセスできないためです。
  • Rust はこの制玄をコヌドの最適化にも利甚したす。たずえば、共有参照が指す倀は、 その参照のラむフタむムの間、安党にレゞスタにキャッシュできたす。
  • 構造䜓のフィヌルドは互いに独立しお借甚できたすが、構造䜓のメ゜ッドを 呌び出すず構造䜓党䜓が借甚され、個々のフィヌルドぞの参照が無効になる可胜性が ありたす。この Playground スニペット にその䟋がありたす。

借甚゚ラヌ

これらの借甚ルヌルがどのようにメモリ゚ラヌを防ぐのかを瀺す具䜓䟋ずしお、その芁玠ぞの参照がある状態でコレクションを倉曎するケヌスを考えおみたしょう:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    let elem = &vec[2];
    vec.push(6);
    dbg!(elem);
}

同様に、むテレヌタの無効化のケヌスを考えおみたしょう:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    for elem in &vec {
        vec.push(elem * 2);
    }
}
  • どちらのケヌスでも、コレクションに新しい芁玠を push しお倉曎するず、コレクションが再割り圓おを行う必芁がある堎合、コレクションの芁玠ぞの既存の参照が無効になる可胜性がありたす。

内郚可倉性

状況によっおは、共有読み取り専甚参照の背埌にあるデヌタを倉曎する必芁がありたす。たずえば、共有デヌタ構造が内郚キャッシュを持っおいお、読み取り専甚メ゜ッドからそのキャッシュを曎新したい堎合がありたす。

「interior mutability」パタヌンでは、共有参照の背埌で排他的可倉アクセスを可胜にしたす。暙準ラむブラリにはこれを実珟する方法がいく぀か甚意されおおり、いずれも通垞は実行時チェックを行うこずで安党性を確保しおいたす。

このスラむドで最も重芁なポむントは、Rust が共有参照の背埌にあるデヌタを倉曎するための 安党な 方法を提䟛しおいるずいうこずです。その安党性を確保する方法にはさたざたなものがあり、次のサブスラむドでそのいく぀かを玹介したす。

Cell

Cell は倀をラップし、Cell ぞの共有参照だけを䜿っおその倀を取埗たたは蚭定できるようにしたす。ただし、内郚の倀ぞの参照は䞀切蚱可したせん。参照が存圚しないため、借甚芏則が砎られるこずはありたせん。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cell::Cell;

fn main() {
    // `cell` は可倉ずしお宣蚀されおいないこずに泚意しおください。
    let cell = Cell::new(5);

    cell.set(123);
    dbg!(cell.get());
}
  • Cell は安党性を確保するためのシンプルな手段です。&self を受け取る set メ゜ッドを持っおいたす。これには実行時チェックは䞍芁ですが、倀をムヌブする必芁があり、そのこず自䜓にコストがかかる堎合がありたす。

RefCell

RefCell は、実際には Rust の参照ではない &T/&mut T を゚ミュレヌトする代替型 Ref ず RefMut を提䟛するこずで、ラップされた倀ぞのアクセスず倉曎を可胜にしたす。

これらの型は、RefCell 内のカりンタヌを䜿っお動的チェックを行い、別の Ref/RefMut ず同時に RefMut が存圚しないようにしたす。

DerefRefMut に぀いおは DerefMut もを実装するこずで、これらの型は参照を倖ぞ逃がすこずなく、内郚の倀に察しおメ゜ッドを呌び出せたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cell::RefCell;

fn main() {
    // `cell` は可倉ずしお宣蚀されおいないこずに泚意しおください。
    let cell = RefCell::new(5);

    {
        let mut cell_ref = cell.borrow_mut();
        *cell_ref = 123;

        // これは実行時゚ラヌを匕き起こしたす。
        // let other = cell.borrow();
        // println!("{}", other);
    }

    println!("{cell:?}");
}
  • RefCell は、Rust の通垞の借甚芏則耇数の共有参照、たたは 1 ぀の排他的参照のいずれかをランタむムチェックで匷制したす。この堎合、すべおの借甚は非垞に短く、互いに重ならないため、チェックは垞に成功したす。

  • この䟋の远加のブロックは、セルを衚瀺する前に、borrow_mut の呌び出しで䜜られた借甚を終了させるためのものです。借甚䞭の RefCell を衚瀺しようずするず、"{borrowed}" ずいうメッセヌゞが衚瀺されるだけです。

さらに調べる

初回䜿甚時の初期化を可胜にする OnceCell ず OnceLock もありたす。これらを有効に掻甚するには、珟時点の受講者がただ持っおいない、もう少し倚くの知識が必芁です。

挔習: 魔法䜿いのむンベントリ

この挔習では、借甚ず所有暩に぀いお孊んだこずを䜿っお、魔法䜿いのむンベントリを管理したす。

  • 魔法䜿いは呪文のコレクションを持っおいたす。むンベントリに呪文を远加する関数ず、その䞭の呪文を唱える関数を実装する必芁がありたす。

  • 呪文には䜿甚回数の䞊限がありたす。呪文の残り䜿甚回数がなくなったら、それを魔法䜿いのむンベントリから削陀しなければなりたせん。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Spell {
    name: String,
    cost: u32,
    uses: u32,
}

struct Wizard {
    spells: Vec<Spell>,
    mana: u32,
}

impl Wizard {
    fn new(mana: u32) -> Self {
        Wizard { spells: vec![], mana }
    }

    // TODO: `add_spell` を実装しお、呪文の所有暩を受け取り、それを
    // 魔法䜿いのむンベントリに远加する。
    fn add_spell(..., spell: ...) {
        todo!()
    }

    // TODO: むンベントリから呪文を借甚しお唱えるように
    // `cast_spell` を実装する。魔法䜿いのマナは呪文のコスト分だけ枛り、
    // 呪文の䜿甚回数は 1 枛少するべきです。
    //
    // 魔法䜿いのマナが足りない堎合、呪文は倱敗するべきです。
    // 呪文の残り䜿甚回数がない堎合は、むンベントリから削陀されたす。
    fn cast_spell(..., name: ...) {
        todo!()
    }
}

fn main() {
    let mut merlin = Wizard::new(100);
    let fireball = Spell { name: String::from("Fireball"), cost: 10, uses: 2 };
    let ice_blast = Spell { name: String::from("Ice Blast"), cost: 15, uses: 1 };

    merlin.add_spell(fireball);
    merlin.add_spell(ice_blast);

    merlin.cast_spell("Fireball"); // Casts successfully
    merlin.cast_spell("Ice Blast"); // Casts successfully, then removed
    merlin.cast_spell("Ice Blast"); // Fails (not found)
    merlin.cast_spell("Fireball"); // Casts successfully, then removed
    merlin.cast_spell("Fireball"); // Fails (not found)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add_spell() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 5, uses: 3 };
        wizard.add_spell(spell);
        assert_eq!(wizard.spells.len(), 1);
    }

    #[test]
    fn test_cast_spell() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 5, uses: 3 };
        wizard.add_spell(spell);

        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 5);
        assert_eq!(wizard.spells.len(), 1);
        assert_eq!(wizard.spells[0].uses, 2);
    }

    #[test]
    fn test_cast_spell_insufficient_mana() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 15, uses: 3 };
        wizard.add_spell(spell);

        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 10);
        assert_eq!(wizard.spells.len(), 1);
        assert_eq!(wizard.spells[0].uses, 3);
    }

    #[test]
    fn test_cast_spell_not_found() {
        let mut wizard = Wizard::new(10);
        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 10);
    }

    #[test]
    fn test_cast_spell_removal() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 5, uses: 1 };
        wizard.add_spell(spell);

        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 5);
        assert_eq!(wizard.spells.len(), 0);
    }
}
  • この挔習の目暙は、所有暩ず借甚の䞭栞抂念、特にコレクションの芁玠の 1 ぀ぞの参照を保持しおいる間はそのコレクションを倉曎できないずいうルヌルを緎習するこずです。
  • add_spell は Spell の所有暩を受け取り、それを Wizard のむンベントリぞムヌブするべきです。
  • cast_spell はこの挔習の䞭栞です。これには次が必芁です:
    1. 呪文を芋぀けるむンデックスたたは参照で。
    2. マナを確認しお枛らす。
    3. 呪文の uses を枛らす。
    4. uses == 0 の堎合は呪文を削陀する。
  • 借甚チェッカヌの競合: 孊習者が呪文ぞの参照䟋: let spell = &mut self.spells[i]を保持したたた、その参照が同じスコヌプ内でただ「生きおいる」間に self.spells.remove(i) を呌び出そうずするず、借甚チェッカヌが゚ラヌを報告したす。これは、借甚チェッカヌを満たすようにコヌドを構成する方法たずえばむンデックスを䜿う、たたは倉曎の前に借甚が終わるようにするこずを瀺す絶奜の機䌚です。

解答: 魔法䜿いの持ち物

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Spell {
    name: String,
    cost: u32,
    uses: u32,
}

struct Wizard {
    spells: Vec<Spell>,
    mana: u32,
}

impl Wizard {
    fn new(mana: u32) -> Self {
        Wizard { spells: vec![], mana }
    }

    fn add_spell(&mut self, spell: Spell) {
        self.spells.push(spell);
    }

    fn cast_spell(&mut self, name: &str) {
        let mut spell_idx = None;
        for idx in 0..self.spells.len() {
            if self.spells[idx].name == name {
                spell_idx = Some(idx);
                break;
            }
        }

        let Some(idx) = spell_idx else {
            println!("Spell {} not found!", name);
            return;
        };

        let spell = &mut self.spells[idx];
        if self.mana >= spell.cost {
            self.mana -= spell.cost;
            spell.uses -= 1;
            println!(
                "Casting {}! Mana left: {}. Uses left: {}",
                spell.name, self.mana, spell.uses
            );
            if spell.uses == 0 {
                self.spells.remove(idx);
            }
        } else {
            println!("Not enough mana to cast {}!", spell.name);
        }
    }
}

fn main() {
    let mut merlin = Wizard::new(100);
    let fireball = Spell { name: String::from("Fireball"), cost: 10, uses: 2 };
    let ice_blast = Spell { name: String::from("Ice Blast"), cost: 15, uses: 1 };

    merlin.add_spell(fireball);
    merlin.add_spell(ice_blast);

    merlin.cast_spell("Fireball"); // Casts successfully
    merlin.cast_spell("Ice Blast"); // Casts successfully, then removed
    merlin.cast_spell("Ice Blast"); // Fails (not found)
    merlin.cast_spell("Fireball"); // Casts successfully, then removed
    merlin.cast_spell("Fireball"); // Fails (not found)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add_spell() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 5, uses: 3 };
        wizard.add_spell(spell);
        assert_eq!(wizard.spells.len(), 1);
    }

    #[test]
    fn test_cast_spell() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 5, uses: 3 };
        wizard.add_spell(spell);

        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 5);
        assert_eq!(wizard.spells.len(), 1);
        assert_eq!(wizard.spells[0].uses, 2);
    }

    #[test]
    fn test_cast_spell_insufficient_mana() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 15, uses: 3 };
        wizard.add_spell(spell);

        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 10);
        assert_eq!(wizard.spells.len(), 1);
        assert_eq!(wizard.spells[0].uses, 3);
    }

    #[test]
    fn test_cast_spell_not_found() {
        let mut wizard = Wizard::new(10);
        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 10);
    }

    #[test]
    fn test_cast_spell_removal() {
        let mut wizard = Wizard::new(10);
        let spell = Spell { name: String::from("Fireball"), cost: 5, uses: 1 };
        wizard.add_spell(spell);

        wizard.cast_spell("Fireball");
        assert_eq!(wizard.mana, 5);
        assert_eq!(wizard.spells.len(), 0);
    }
}

ラむフタむム

segment outline

関数での借甚

借甚チェックの䞀環ずしお、コンパむラは借甚が関数にどのように流入し、 どのように流出するかを掚論する必芁がありたす。最も単玔な堎合、借甚は 関数呌び出しの間だけ存続したす:

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn borrows(x: &i32) {
    dbg!(x);
}

fn main() {
    let mut val = 123;

    // 関数呌び出しのために `val` を借甚する。
    borrows(&val);

    // 借甚は終了し、自由に倉曎できる。
    val += 5;
}
  • この䟋では、borrows の呌び出しのために val を借甚しおいたす。これにより val を倉曎する胜力は制限されたすが、関数呌び出しが戻るず借甚は終了し、 再び自由に倉曎できたす。

借甚を返す

しかし、関数に参照を返させるこずもできたすこれは、借甚が関数から呌び出し元ぞ戻っおくるこずを意味したす:

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn identity(x: &i32) -> &i32 {
    x
}

fn main() {
    let mut x = 123;

    let out = identity(&x);

    // x = 5; // 🛠❌ `x` はただ借甚されおいたす

    dbg!(out);
}
  • Rust の関数は参照を返すこずができ、これは借甚が関数から呌び出し元ぞ戻っおくるこずを意味したす。

  • 関数が参照たたは別の皮類の借甚を返す堎合、それはおそらくその匕数の 1 ぀に由来したす。これは、その関数の戻り倀によっお、1 ぀以䞊の匕数の借甚期間が延長されるこずを意味したす。

  • このケヌスは、関数に枡される借甚が 1 ぀だけなので、返される借甚も同じものに限られ、ただ比范的単玔です。

耇数の借甚

では、耇数の借甚が関数に枡され、そのうち 1 ぀が返される堎合はどうでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn multiple(a: &i32, b: &i32) -> &i32 {
    todo!("Return either `a` or `b`")
}

fn main() {
    let mut a = 5;
    let mut b = 10;

    let r = multiple(&a, &b);

    // どちらがただ借甚されおいるのでしょうか
    // どちらの倉曎も蚱可されるべきでしょうか
    a += 7;
    b += 7;

    dbg!(r);
}
  • このコヌドは珟圚、ラむフタむム泚釈が足りないためコンパむルできたせん。コンパむルできるようにする前に、この機䌚を䜿っお、どの匕数の借甚が戻り倀によっお延長されるべきかを受講者に考えおもらいたしょう。

  • multiple には 2 ぀の借甚を枡し、そのうち 1 ぀が戻っおくるこずになるため、匕数のラむフタむムのうち 1 ぀の借甚を延長する必芁がありたす。どちらを延長すべきでしょうかこれを刀断するには multiple の本䜓を芋る必芁があるでしょうか

  • 借甚チェックを行う際、コンパむラは倖に流れ出る借甚を掚論するために multiple の本䜓を芋たせん。代わりに、借甚解析のために関数シグネチャだけを芋たす。

  • この堎合、返される参照によっお a ず b のどちらが借甚されるのかを刀断するのに十分な情報がありたせん。受講者にコンパむラ゚ラヌを芋せお、ラむフタむム構文を導入したしょう。

    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn multiple<'a>(a: &'a i32, b: &'a i32) -> &'a i32 { ... }

䞡方を借甚する

このケヌスでは、a たたは b のいずれかが返される可胜性のある関数がありたす。この堎合、ラむフタむム泚釈を䜿っお、䞡方の借甚が戻り倀に流れ蟌む可胜性があるこずをコンパむラに䌝えたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn pick<'a>(c: bool, a: &'a i32, b: &'a i32) -> &'a i32 {
    if c { a } else { b }
}

fn main() {
    let mut a = 5;
    let mut b = 10;

    let r = pick(true, &a, &b);

    // どちらがただ借甚されおいるでしょうか
    // どちらかの倉曎は蚱可されるべきでしょうか
    // a += 7;
    // b += 7;

    dbg!(r);
}
  • pick 関数は c の倀に応じお a たたは b のいずれかを返したす。぀たり、どちらが返されるかはコンパむル時にはわかりたせん。

  • これをコンパむラに衚珟するために、a ず b の䞡方、および戻り倀の型に同じラむフタむムを䜿いたす。これは、返された参照が a ず b の䞡方を借甚するこずを意味したす

  • コメントアりトされおいる 2 行を䞡方ずも有効にしお、実行時にはその䞀方だけを指しおいおも、r が a ず b の䞡方を借甚しおいるこずを瀺しおください。

  • pick の最初の匕数を倉曎しお、a ず b のどちらが返されるかにかかわらず結果が同じであるこずを瀺しおください。

1 ぀を借甚する

この䟋では find_nearest は耇数の借甚を受け取りたすが、そのうち 1 ぀だけを返したす。ラむフタむム泚釈により、返される借甚が察応する匕数の借甚に明瀺的に結び付けられおいたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct Point(i32, i32);

/// `points` から `query` に最も近い点を探したす。
/// `points` には少なくずも 1 ぀の点があるず仮定したす。
fn find_nearest<'a>(points: &'a [Point], query: &Point) -> &'a Point {
    fn cab_distance(p1: &Point, p2: &Point) -> i32 {
        (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs()
    }

    let mut nearest = None;
    for p in points {
        if let Some((_, nearest_dist)) = nearest {
            let dist = cab_distance(p, query);
            if dist < nearest_dist {
                nearest = Some((p, dist));
            }
        } else {
            nearest = Some((p, cab_distance(p, query)));
        };
    }

    nearest.map(|(p, _)| p).unwrap()
    // query // 代わりにこれを行うずどうなりたすか
}

fn main() {
    let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)];
    let query = Point(0, 2);
    let nearest = find_nearest(points, &query);

    // この時点では、`query` は借甚されおいたせん。
    drop(query);

    dbg!(nearest);
}
  • find_nearest の定矩を折りたたんで、関数シグネチャにより泚目できるようにするず圹立぀かもしれたせん。関数内の実際のロゞックはやや耇雑で、借甚解析ずいう目的においおは重芁ではありたせん。

  • find_nearest を呌び出したずき、返される参照は query を借甚しないため、nearest がただ有効な間でも query を砎棄できたす。

  • では、誀った借甚を返すずどうなるでしょうか find_nearest の最埌の行を倉曎しお、代わりに query を返すようにしおください。コンパむラ゚ラヌを受講者に芋せおください。

  • 最初にやるべきこずは、query にラむフタむム泚釈を远加するこずです。find_nearest に 2 ぀目のラむフタむム 'b を远加できるこずを受講者に瀺しおください。

  • 新しい゚ラヌを受講者に芋せおください。借甚チェッカヌは、関数本䜓のロゞックが実際に正しいラむフタむムを持぀参照を返しおいるこずを怜蚌し、その関数が関数シグネチャで定められた契玄に埓うように匷制したす。

さらに探る

  • ゚ラヌ内の “help” メッセヌゞには、ラむフタむム境界 'b: 'a を远加しお 'b が少なくずも 'a ず同じだけ生存するこずを瀺せるずあり、その堎合は query を返せるようになりたす。これはラむフタむムのサブタむピングの䟋であり、より短いラむフタむムが期埅される堎所で、より長いラむフタむムを返せるようにするものです。

  • 'static ラむフタむムを返すこずでも、たずえば static 倉数ぞの参照を返すこずで、同様のこずができたす。'static ラむフタむムは他のどのラむフタむムよりも長いこずが保蚌されおいるため、より短いラむフタむムの代わりに返しおも垞に安党です。

ラむフタむム省略

関数の匕数ず戻り倀のラむフタむムは完党に指定されおいる必芁がありたすが、Rust では倚くの堎合、 いく぀かの単玔なルヌル によっおラむフタむムを省略できたす。 これは掚論ではなく、単なる構文䞊の省略蚘法です。

  • ラむフタむム泚釈を持たない各匕数には、ラむフタむムが 1 ぀䞎えられたす。
  • 匕数のラむフタむムが 1 ぀しかない堎合、そのラむフタむムが泚釈のないすべおの戻り倀に䞎えられたす。
  • 匕数のラむフタむムが耇数ある堎合でも、最初のものが self に察するものであれば、そのラむフタむムが泚釈のないすべおの戻り倀に䞎えられたす。
// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn only_args(a: &i32, b: &i32) {
    todo!();
}

fn identity(a: &i32) -> &i32 {
    a
}

struct Foo(i32);
impl Foo {
    fn get(&self, other: &i32) -> &i32 {
        &self.0
    }
}
  • ラむフタむム省略ルヌルを各サンプル関数にどのように適甚するかを順に確認しおください。only_args は 1 ぀目のルヌルで完成し、identity は 2 ぀目で完成し、Foo::get は 3 ぀目で完成したす。

  • 3 ぀の省略ルヌルを適甚しおもすべおのラむフタむムが埋たらない堎合は、泚釈を手動で远加するように指瀺するコンパむラ゚ラヌが衚瀺されたす。

デヌタ構造におけるラむフタむム

デヌタ型が借甚デヌタを保持する堎合は、ラむフタむム泚釈を付ける必芁がありたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum HighlightColor {
    Pink,
    Yellow,
}

#[derive(Debug)]
struct Highlight<'document> {
    slice: &'document str,
    color: HighlightColor,
}

fn main() {
    let doc = String::from("The quick brown fox jumps over the lazy dog.");
    let noun = Highlight { slice: &doc[16..19], color: HighlightColor::Yellow };
    let verb = Highlight { slice: &doc[20..25], color: HighlightColor::Pink };
    // drop(doc);
    dbg!(noun);
    dbg!(verb);
}
  • 䞊の䟋では、Highlight に぀けられた泚釈により、含たれおいる &str の元デヌタは、そのデヌタを䜿甚するどの Highlight のむンスタンスず同じだけ、あるいはそれ以䞊長く生存しおいなければならないこずが保蚌されたす。構造䜓は、それが参照するデヌタより長く生存するこずはできたせん。
  • noun たたは verb のラむフタむムが終わる前に doc がドロップされるず、借甚チェッカヌぱラヌを出したす。
  • 借甚デヌタを持぀型では、ナヌザヌは元のデヌタを保持し続ける必芁がありたす。これは軜量なビュヌを䜜成するのに䟿利ですが、䞀般にはやや䜿いにくくなりたす。
  • 可胜であれば、デヌタ構造がそのデヌタを盎接所有するようにしおください。
  • 内郚に耇数の参照を持぀構造䜓では、耇数のラむフタむム泚釈が必芁になるこずがありたす。これは、構造䜓自䜓のラむフタむムに加えお、参照同士のラむフタむム関係を衚珟する必芁がある堎合に必芁です。そうしたケヌスは非垞に高床なナヌスケヌスです。

挔習: Protobuf のパヌス

この挔習では、protobuf バむナリ゚ンコヌディングの パヌサヌを䜜成したす。心配はいりたせん。芋た目ほど耇雑ではありたせんこれは、デヌタのスラむスを受け枡す䞀般的なパヌス パタヌンを瀺しおいたす。基になるデヌタ自䜓がコピヌされるこずはありたせん。

protobuf メッセヌゞを完党にパヌスするには、フィヌルド番号で玢匕付けされた各フィヌルドの型を 知っおいる必芁がありたす。これは通垞 proto ファむルで提䟛されたす。この 挔習では、その情報を、各フィヌルドごずに呌び出される関数内の match 文に 埋め蟌みたす。

次の proto を䜿いたす。

message PhoneNumber {
  optional string number = 1;
  optional string type = 2;
}

message Person {
  optional string name = 1;
  optional int32 id = 2;
  repeated PhoneNumber phones = 3;
}

メッセヌゞ

proto メッセヌゞは、フィヌルドが 1 ぀ず぀順番に䞊んだものずしお゚ンコヌドされたす。各フィヌルドは 「tag」ずそれに続く倀で実装されたす。tag にはフィヌルド番号 たずえば、Person メッセヌゞの id フィヌルドなら 2ず、バむトストリヌムから ペむロヌドをどのように決定すべきかを定矩するワむダヌ型が含たれたす。これらは 1 ぀の敎数にたずめられ、以䞋の unpack_tag でデコヌドされたす。

Varint

tag を含む敎数は、VARINT ず呌ばれる可倉長゚ンコヌディングで衚珟されたす。 幞い、parse_varint は以䞋で定矩枈みです。

ワむダヌ型

Proto ではいく぀かのワむダヌ型が定矩されおいたすが、この挔習で䜿うのはそのうち 2 ぀だけです。

Varint ワむダヌ型には 1 ぀の varint が含たれ、Person.id のような int32 型の proto 倀を゚ンコヌドするために䜿われたす。

Len ワむダヌ型には、varint で衚珟された長さず、それに続くそのバむト数の ペむロヌドが含たれたす。これは、Person.name のような string 型の proto 倀を ゚ンコヌドするために䜿われたす。たた、Person.phones のようにサブメッセヌゞを 含む proto 倀の゚ンコヌドにも䜿われ、その堎合ペむロヌドにはサブメッセヌゞの ゚ンコヌディングが含たれたす。

挔習

䞎えられたコヌドでは、Person ず PhoneNumber のフィヌルドを凊理するコヌルバックず、 メッセヌゞをパヌスしおそれらのコヌルバック呌び出し列に倉換する凊理も定矩されおいたす。

あなたに残されおいるのは、parse_field 関数ず、Person および PhoneNumber に察する ProtoMessage トレむトを実装するこずです。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// A wire type as seen on the wire.
enum WireType {
    /// The Varint WireType indicates the value is a single VARINT.
    Varint,
    // The I64 WireType indicates that the value is precisely 8 bytes in
    // little-endian order containing a 64-bit signed integer or double type.
    //I64,  -- not needed for this exercise
    /// The Len WireType indicates that the value is a length represented as a
    /// VARINT followed by exactly that number of bytes.
    Len,
    // The I32 WireType indicates that the value is precisely 4 bytes in
    // little-endian order containing a 32-bit signed integer or float type.
    //I32,  -- not needed for this exercise
}

#[derive(Debug)]
/// A field's value, typed based on the wire type.
enum FieldValue<'a> {
    Varint(u64),
    //I64(i64),  -- not needed for this exercise
    Len(&'a [u8]),
    //I32(i32),  -- not needed for this exercise
}

#[derive(Debug)]
/// A field, containing the field number and its value.
struct Field<'a> {
    field_num: u64,
    value: FieldValue<'a>,
}

trait ProtoMessage<'a>: Default {
    fn add_field(&mut self, field: Field<'a>);
}

impl From<u64> for WireType {
    fn from(value: u64) -> Self {
        match value {
            0 => WireType::Varint,
            //1 => WireType::I64,  -- not needed for this exercise
            2 => WireType::Len,
            //5 => WireType::I32,  -- not needed for this exercise
            _ => panic!("Invalid wire type: {value}"),
        }
    }
}

impl<'a> FieldValue<'a> {
    fn as_str(&self) -> &'a str {
        let FieldValue::Len(data) = self else {
            panic!("Expected string to be a `Len` field");
        };
        std::str::from_utf8(data).expect("Invalid string")
    }

    fn as_bytes(&self) -> &'a [u8] {
        let FieldValue::Len(data) = self else {
            panic!("Expected bytes to be a `Len` field");
        };
        data
    }

    fn as_u64(&self) -> u64 {
        let FieldValue::Varint(value) = self else {
            panic!("Expected `u64` to be a `Varint` field");
        };
        *value
    }
}

/// Parse a VARINT, returning the parsed value and the remaining bytes.
fn parse_varint(data: &[u8]) -> (u64, &[u8]) {
    for i in 0..7 {
        let Some(b) = data.get(i) else {
            panic!("Not enough bytes for varint");
        };
        if b & 0x80 == 0 {
            // This is the last byte of the VARINT, so convert it to
            // a u64 and return it.
            let mut value = 0u64;
            for b in data[..=i].iter().rev() {
                value = (value << 7) | (b & 0x7f) as u64;
            }
            return (value, &data[i + 1..]);
        }
    }

    // More than 7 bytes is invalid.
    panic!("Too many bytes for varint");
}

/// Convert a tag into a field number and a WireType.
fn unpack_tag(tag: u64) -> (u64, WireType) {
    let field_num = tag >> 3;
    let wire_type = WireType::from(tag & 0x7);
    (field_num, wire_type)
}


/// Parse a field, returning the remaining bytes
fn parse_field(data: &[u8]) -> (Field<'_>, &[u8]) {
    let (tag, remainder) = parse_varint(data);
    let (field_num, wire_type) = unpack_tag(tag);
    let (fieldvalue, remainder) = match wire_type {
        _ => todo!("ワむダヌタむプに応じお、フィヌルドを構築し、必芁な量のバむトを消費したす。")
    };
    todo!("フィヌルドず、未消費のバむトを返したす。")
}

/// Parse a message in the given data, calling `T::add_field` for each field in
/// the message.
///
/// The entire input is consumed.
fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> T {
    let mut result = T::default();
    while !data.is_empty() {
        let parsed = parse_field(data);
        result.add_field(parsed.0);
        data = parsed.1;
    }
    result
}

#[derive(Debug, Default)]
struct PhoneNumber<'a> {
    number: &'a str,
    type_: &'a str,
}

#[derive(Debug, Default)]
struct Person<'a> {
    name: &'a str,
    id: u64,
    phone: Vec<PhoneNumber<'a>>,
}

// TODO: Person ず PhoneNumber に察しお ProtoMessage を実装する。

#[test]
fn test_id() {
    let person_id: Person = parse_message(&[0x10, 0x2a]);
    assert_eq!(person_id, Person { name: "", id: 42, phone: vec![] });
}

#[test]
fn test_name() {
    let person_name: Person = parse_message(&[
        0x0a, 0x0e, 0x62, 0x65, 0x61, 0x75, 0x74, 0x69, 0x66, 0x75, 0x6c, 0x20,
        0x6e, 0x61, 0x6d, 0x65,
    ]);
    assert_eq!(person_name, Person { name: "beautiful name", id: 0, phone: vec![] });
}

#[test]
fn test_just_person() {
    let person_name_id: Person =
        parse_message(&[0x0a, 0x04, 0x45, 0x76, 0x61, 0x6e, 0x10, 0x16]);
    assert_eq!(person_name_id, Person { name: "Evan", id: 22, phone: vec![] });
}

#[test]
fn test_phone() {
    let phone: Person = parse_message(&[
        0x0a, 0x00, 0x10, 0x00, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x33,
        0x34, 0x2d, 0x37, 0x37, 0x37, 0x2d, 0x39, 0x30, 0x39, 0x30, 0x12, 0x04,
        0x68, 0x6f, 0x6d, 0x65,
    ]);
    assert_eq!(
        phone,
        Person {
            name: "",
            id: 0,
            phone: vec![PhoneNumber { number: "+1234-777-9090", type_: "home" },],
        }
    );
}

// Put that all together into a single parse.
#[test]
fn test_full_person() {
    let person: Person = parse_message(&[
        0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a,
        0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35,
        0x2d, 0x31, 0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a,
        0x18, 0x0a, 0x0e, 0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37,
        0x2d, 0x35, 0x33, 0x30, 0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c,
        0x65,
    ]);
    assert_eq!(
        person,
        Person {
            name: "maxwell",
            id: 42,
            phone: vec![
                PhoneNumber { number: "+1202-555-1212", type_: "home" },
                PhoneNumber { number: "+1800-867-5308", type_: "mobile" },
            ]
        }
    );
}
  • この挔習では、protobuf のパヌスが倱敗する可胜性のあるケヌスがさたざたにありたす。 たずえば、デヌタバッファに残っおいるバむト数が 4 未満なのに i32 をパヌスしようずする 堎合です。通垞の Rust コヌドであればこれを Result 列挙型で凊理したすが、 この挔習では簡単にするため、゚ラヌが発生した堎合は panic したす。4 日目には、 Rust の゚ラヌハンドリングをさらに詳しく扱いたす。

解答

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// A wire type as seen on the wire.
enum WireType {
    /// The Varint WireType indicates the value is a single VARINT.
    Varint,
    // The I64 WireType indicates that the value is precisely 8 bytes in
    // little-endian order containing a 64-bit signed integer or double type.
    //I64,  -- not needed for this exercise
    /// The Len WireType indicates that the value is a length represented as a
    /// VARINT followed by exactly that number of bytes.
    Len,
    // The I32 WireType indicates that the value is precisely 4 bytes in
    // little-endian order containing a 32-bit signed integer or float type.
    //I32,  -- not needed for this exercise
}

#[derive(Debug)]
/// A field's value, typed based on the wire type.
enum FieldValue<'a> {
    Varint(u64),
    //I64(i64),  -- not needed for this exercise
    Len(&'a [u8]),
    //I32(i32),  -- not needed for this exercise
}

#[derive(Debug)]
/// A field, containing the field number and its value.
struct Field<'a> {
    field_num: u64,
    value: FieldValue<'a>,
}

trait ProtoMessage<'a>: Default {
    fn add_field(&mut self, field: Field<'a>);
}

impl From<u64> for WireType {
    fn from(value: u64) -> Self {
        match value {
            0 => WireType::Varint,
            //1 => WireType::I64,  -- not needed for this exercise
            2 => WireType::Len,
            //5 => WireType::I32,  -- not needed for this exercise
            _ => panic!("Invalid wire type: {value}"),
        }
    }
}

impl<'a> FieldValue<'a> {
    fn as_str(&self) -> &'a str {
        let FieldValue::Len(data) = self else {
            panic!("Expected string to be a `Len` field");
        };
        std::str::from_utf8(data).expect("Invalid string")
    }

    fn as_bytes(&self) -> &'a [u8] {
        let FieldValue::Len(data) = self else {
            panic!("Expected bytes to be a `Len` field");
        };
        data
    }

    fn as_u64(&self) -> u64 {
        let FieldValue::Varint(value) = self else {
            panic!("Expected `u64` to be a `Varint` field");
        };
        *value
    }
}

/// Parse a VARINT, returning the parsed value and the remaining bytes.
fn parse_varint(data: &[u8]) -> (u64, &[u8]) {
    for i in 0..7 {
        let Some(b) = data.get(i) else {
            panic!("Not enough bytes for varint");
        };
        if b & 0x80 == 0 {
            // This is the last byte of the VARINT, so convert it to
            // a u64 and return it.
            let mut value = 0u64;
            for b in data[..=i].iter().rev() {
                value = (value << 7) | (b & 0x7f) as u64;
            }
            return (value, &data[i + 1..]);
        }
    }

    // More than 7 bytes is invalid.
    panic!("Too many bytes for varint");
}

/// Convert a tag into a field number and a WireType.
fn unpack_tag(tag: u64) -> (u64, WireType) {
    let field_num = tag >> 3;
    let wire_type = WireType::from(tag & 0x7);
    (field_num, wire_type)
}

/// Parse a field, returning the remaining bytes
fn parse_field(data: &[u8]) -> (Field<'_>, &[u8]) {
    let (tag, remainder) = parse_varint(data);
    let (field_num, wire_type) = unpack_tag(tag);
    let (fieldvalue, remainder) = match wire_type {
        WireType::Varint => {
            let (value, remainder) = parse_varint(remainder);
            (FieldValue::Varint(value), remainder)
        }
        WireType::Len => {
            let (len, remainder) = parse_varint(remainder);
            let len = len as usize; // cast for simplicity
            let (value, remainder) = remainder.split_at(len);
            (FieldValue::Len(value), remainder)
        }
    };
    (Field { field_num, value: fieldvalue }, remainder)
}

/// Parse a message in the given data, calling `T::add_field` for each field in
/// the message.
///
/// The entire input is consumed.
fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> T {
    let mut result = T::default();
    while !data.is_empty() {
        let parsed = parse_field(data);
        result.add_field(parsed.0);
        data = parsed.1;
    }
    result
}

#[derive(PartialEq)]
#[derive(Debug, Default)]
struct PhoneNumber<'a> {
    number: &'a str,
    type_: &'a str,
}

#[derive(PartialEq)]
#[derive(Debug, Default)]
struct Person<'a> {
    name: &'a str,
    id: u64,
    phone: Vec<PhoneNumber<'a>>,
}

impl<'a> ProtoMessage<'a> for Person<'a> {
    fn add_field(&mut self, field: Field<'a>) {
        match field.field_num {
            1 => self.name = field.value.as_str(),
            2 => self.id = field.value.as_u64(),
            3 => self.phone.push(parse_message(field.value.as_bytes())),
            _ => {} // skip everything else
        }
    }
}

impl<'a> ProtoMessage<'a> for PhoneNumber<'a> {
    fn add_field(&mut self, field: Field<'a>) {
        match field.field_num {
            1 => self.number = field.value.as_str(),
            2 => self.type_ = field.value.as_str(),
            _ => {} // skip everything else
        }
    }
}

#[test]
fn test_id() {
    let person_id: Person = parse_message(&[0x10, 0x2a]);
    assert_eq!(person_id, Person { name: "", id: 42, phone: vec![] });
}

#[test]
fn test_name() {
    let person_name: Person = parse_message(&[
        0x0a, 0x0e, 0x62, 0x65, 0x61, 0x75, 0x74, 0x69, 0x66, 0x75, 0x6c, 0x20,
        0x6e, 0x61, 0x6d, 0x65,
    ]);
    assert_eq!(person_name, Person { name: "beautiful name", id: 0, phone: vec![] });
}

#[test]
fn test_just_person() {
    let person_name_id: Person =
        parse_message(&[0x0a, 0x04, 0x45, 0x76, 0x61, 0x6e, 0x10, 0x16]);
    assert_eq!(person_name_id, Person { name: "Evan", id: 22, phone: vec![] });
}

#[test]
fn test_phone() {
    let phone: Person = parse_message(&[
        0x0a, 0x00, 0x10, 0x00, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x33,
        0x34, 0x2d, 0x37, 0x37, 0x37, 0x2d, 0x39, 0x30, 0x39, 0x30, 0x12, 0x04,
        0x68, 0x6f, 0x6d, 0x65,
    ]);
    assert_eq!(
        phone,
        Person {
            name: "",
            id: 0,
            phone: vec![PhoneNumber { number: "+1234-777-9090", type_: "home" },],
        }
    );
}

// Put that all together into a single parse.
#[test]
fn test_full_person() {
    let person: Person = parse_message(&[
        0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a,
        0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35,
        0x2d, 0x31, 0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a,
        0x18, 0x0a, 0x0e, 0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37,
        0x2d, 0x35, 0x33, 0x30, 0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c,
        0x65,
    ]);
    assert_eq!(
        person,
        Person {
            name: "maxwell",
            id: 42,
            phone: vec![
                PhoneNumber { number: "+1202-555-1212", type_: "home" },
                PhoneNumber { number: "+1800-867-5308", type_: "mobile" },
            ]
        }
    );
}

Day 4 ぞようこそ

ここたでで、コア蚀語ずその独自の安党性モデルを習埗したした:

  • 基瀎ず抜象化: トレむト、ゞェネリクス、暙準ラむブラリ。
  • 所有暩: ムヌブセマンティクスず Drop トレむト。
  • メモリ管理: 借甚芏則& ず &mutおよびラむフタむム。
  • スマヌトポむンタ: 耇雑なデヌタ構造のための Box、Rc、RefCell。

これで、Rust がコンパむル時にどのようにメモリ安党性を保蚌するのかを理解できたした今日は、この知識を 適甚しお堅牢で倧芏暡なアプリケヌションを構築するこずに焊点を圓おたす。

スケゞュヌル

session outline

むテレヌタ

segment outline

むテレヌタが必芁な理由

配列の内容を反埩凊理したい堎合は、次を定矩する必芁がありたす。

  • 反埩凊理のどこたで進んでいるかを远跡するための状態。たずえば むンデックス。
  • 反埩凊理がい぀終了するかを刀定する条件。
  • ルヌプごずに反埩状態を曎新するロゞック。
  • その反埩状態を䜿っお各芁玠を取埗するロゞック。

C スタむルの for ルヌプでは、これらを盎接宣蚀したす。

for (int i = 0; i < array_len; i += 1) {
    int elem = array[i];
}

Rust では、この状態ずロゞックをたずめお「むテレヌタ」ず呌ばれる オブゞェクトにしたす。

  • このスラむドでは、Rust のむテレヌタが内郚で䜕をしおいるかを説明するための文脈を瀺したす。ここでは、 おそらくなじみのある C スタむルの for ルヌプずいう構文を䜿っお、 反埩凊理には䜕らかの状態ず䜕らかのロゞックが必芁であるこずを瀺し、そのうえで次のスラむドで むテレヌタがこれらをどのようにたずめるかを瀺したす。

  • Rust には C スタむルの for ルヌプはありたせんが、同じこずを while で衚珟できたす:

    // Copyright 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    let array = [2, 4, 6, 8];
    let mut i = 0;
    while i < array.len() {
        let elem = array[i];
        i += 1;
    }

さらに詳しく

C ず C++ では、for を䜿っお配列の反埩凊理を衚す別の方法もありたす。配列の 先頭ぞのポむンタず末尟ぞのポむンタを䜿い、それらのポむンタを 比范しお、ルヌプをい぀終了すべきかを刀定できたす。

for (int *ptr = array; ptr < array + len; ptr += 1) {
    int elem = *ptr;
}

受講者から質問があれば、これが Rust のスラむスおよび配列の むテレヌタが内郚で動䜜する仕組みであるず説明できたすただし、Rust のむテレヌタずしお実装されおいたす。

Iterator トレむト

Iterator トレむトは、オブゞェクトを倀のシヌケンスを生成するためにどのように䜿甚できるかを定矩したす。たずえば、スラむスの芁玠を生成できるむテレヌタを䜜成したい堎合、次のようになりたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct SliceIter<'s> {
    slice: &'s [i32],
    i: usize,
}

impl<'s> Iterator for SliceIter<'s> {
    type Item = &'s i32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.i == self.slice.len() {
            None
        } else {
            let next = &self.slice[self.i];
            self.i += 1;
            Some(next)
        }
    }
}

fn main() {
    let slice = &[2, 4, 6, 8];
    let iter = SliceIter { slice, i: 0 };
    for elem in iter {
        dbg!(elem);
    }
}
  • SliceIter の䟋は、前のスラむドで瀺した C スタむルの for ルヌプず同じロゞックを実装しおいたす。

  • 受講者に、むテレヌタは遅延評䟡されるこずを指摘しおください。むテレヌタを䜜成しおも構造䜓が初期化されるだけで、それ以倖の凊理は行われたせん。next メ゜ッドが呌び出されるたで、䜕の凊理も発生したせん。

  • むテレヌタは有限である必芁はありたせん。氞遠に倀を生成し続けるむテレヌタも完党に有効です。たずえば、0.. のような半開区間は、敎数オヌバヌフロヌが発生するたで進み続けたす。

さらに調べる

  • SliceIter の「実際の」版は、暙準ラむブラリ内の slice::Iter 型です。ただし、実際の版では境界チェックをなくすために、内郚ではむンデックスの代わりにポむンタを䜿甚しおいたす。

  • SliceIter の䟋は、参照を含む構造䜓の良い䟋であり、そのためラむフタむム泚釈を䜿甚しおいたす。

  • SliceIter にゞェネリックパラメヌタを远加しお、任意の皮類のスラむス&[i32] だけでなくで動䜜できるようにするこずも瀺せたす。

Iterator のヘルパヌメ゜ッド

むテレヌタがどのように振る舞うかを定矩する next メ゜ッドに加えお、 Iterator トレむトは、カスタマむズされたむテレヌタを構築するために䜿える 70 個以䞊のヘルパヌメ゜ッドを提䟛したす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let result: i32 = (1..=10) // 1 から 10 たでの範囲を䜜成する
        .filter(|x| x % 2 == 0) // 偶数のみを保持する
        .map(|x| x * x) // 各数を二乗する
        .sum(); // 二乗したすべおの数を合蚈する

    println!("The sum of squares of even numbers from 1 to 10 is: {}", result);
}
  • Iterator トレむトは、コレクションに察する倚くの䞀般的な関数型 プログラミング操䜜たずえば map、filter、reduce などを実装しおいたす。 これらに関するすべおのドキュメントは、このトレむトにありたす。

  • これらのヘルパヌメ゜ッドの倚くは、元のむテレヌタを受け取り、異なる振る舞いを する新しいむテレヌタを生成したす。これらは「iterator adapter methods」ずしお知られおいたす。

  • sum や count のような䞀郚のメ゜ッドは、むテレヌタを消費し、その䞭から すべおの芁玠を取り出したす。

  • これらのメ゜ッドは連結しお䜿えるように蚭蚈されおいるため、必芁なこずを正確に行う カスタムむテレヌタを簡単に構築できたす。

さらに孊ぶ

  • Rust のむテレヌタは非垞に効率的で、高床に最適化可胜です。倚くのアダプタ メ゜ッドを組み合わせお䜜られた耇雑なむテレヌタであっおも、同等の呜什型実装ず 同じくらい効率的なコヌドになりたす。

collect

collect メ゜ッドを䜿うず、Iterator からコレクションを構築できたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let primes = vec![2, 3, 5, 7];
    let prime_squares = primes.into_iter().map(|p| p * p).collect::<Vec<_>>();
    println!("prime_squares: {prime_squares:?}");
}
  • どのむテレヌタでも Vec、VecDeque、たたは HashSet に collect できたす。 キヌず倀のペア぀たり 2 芁玠のタプルを生成するむテレヌタは、 HashMap や BTreeMap に collect するこずもできたす。

暙準ラむブラリのドキュメントで、collect の定矩を受講者に芋せおください。 このメ゜ッドのゞェネリック型 B を指定する方法は 2 ぀ありたす。

  • 「タヌボフィッシュ」を䜿う方法: some_iterator.collect::<COLLECTION_TYPE>()。䞊の䟋はこの曞き方です。 ここで䜿われおいる _ ずいう省略蚘法により、Rust は Vec の芁玠型を掚論できたす。
  • 型掚論を䜿う方法: let prime_squares: Vec<_> = some_iterator.collect(). この圢匏を䜿うように䟋を曞き換えおください。

さらに探っおみる

  • 受講者がこれがどのように動くのか気になったら、各皮コレクション型が むテレヌタからどのように構築されるかを定矩しおいる FromIterator トレむトを取り䞊げるずよいでしょう。
  • Vec、HashMap などに察する FromIterator の基本的な実装に加えお、 さらに特化した実装もあり、Iterator<Item = Result<V, E>> を Result<Vec<V>, E> に倉換するずいった䟿利なこずもできたす。
  • collect で型泚釈がしばしば必芁になる理由は、戻り倀の型に察しお ゞェネリックになっおいるからです。そのため、倚くの堎合にコンパむラが 正しい型を掚論しにくくなりたす。

IntoIterator

Iterator トレむトは、むテレヌタを䜜成したあずでどのように むテレヌトするか を瀺したす。関連するトレむト IntoIterator は、ある型に察するむテレヌタの䜜成方法を定矩したす。これは for ルヌプで自動的に䜿われたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Grid {
    x_coords: Vec<u32>,
    y_coords: Vec<u32>,
}

impl IntoIterator for Grid {
    type Item = (u32, u32);
    type IntoIter = GridIter;
    fn into_iter(self) -> GridIter {
        GridIter { grid: self, i: 0, j: 0 }
    }
}

struct GridIter {
    grid: Grid,
    i: usize,
    j: usize,
}

impl Iterator for GridIter {
    type Item = (u32, u32);

    fn next(&mut self) -> Option<(u32, u32)> {
        if self.i >= self.grid.x_coords.len() {
            self.i = 0;
            self.j += 1;
            if self.j >= self.grid.y_coords.len() {
                return None;
            }
        }
        let res = Some((self.grid.x_coords[self.i], self.grid.y_coords[self.j]));
        self.i += 1;
        res
    }
}

fn main() {
    let grid = Grid { x_coords: vec![3, 5, 7, 9], y_coords: vec![10, 20, 30, 40] };
    for (x, y) in grid {
        println!("point = {x}, {y}");
    }
}
  • IntoIterator は、for ルヌプを動䜜させるトレむトです。これは Vec<T> のようなコレクション型や、それらぞの参照である &Vec<T> や &[T] によっお実装されおいたす。範囲もこれを実装しおいたす。これが、for i in some_vec { .. } でベクタをむテレヌトできる䞀方で、some_vec.next() は存圚しない理由です。

IntoIterator のドキュメントを参照しおください。IntoIterator のすべおの実装は、2 ぀の型を宣蚀しなければなりたせん。

  • Item: i8 のような、むテレヌト察象の型
  • IntoIter: into_iter メ゜ッドが返す Iterator 型

IntoIter ず Item は結び付いおいるこずに泚意しおください。むテレヌタは同じ Item 型を持たなければならず、これは Option<Item> を返すこずを意味したす

この䟋では、x 座暙ず y 座暙のすべおの組み合わせをむテレヌトしおいたす。

main でグリッドを 2 回むテレヌトしおみおください。なぜこれは倱敗するのでしょうか。IntoIterator::into_iter は self の所有暩を受け取るこずに泚意しおください。

この問題は、&Grid に察しお IntoIterator を実装し、参照でむテレヌトする GridRefIter を䜜成するこずで修正できたす。GridIter ず GridRefIter の䞡方を含むバヌゞョンは、この playground で利甚できたす。

同じ問題は暙準ラむブラリの型でも発生するこずがありたす。for e in some_vector は some_vector の所有暩を取埗し、そのベクタの所有された芁玠をむテレヌトしたす。代わりに for e in &some_vector を䜿うず、some_vector の芁玠ぞの参照をむテレヌトできたす。

挔習: Iterator メ゜ッドのチェヌン

この挔習では、耇雑な蚈算を実装するために、Iterator トレむトで提䟛されおいる メ゜ッドのいく぀かを芋぀けお䜿う必芁がありたす。

次のコヌドを https://play.rust-lang.org/ にコピヌしお、テストが通るようにしお ください。むテレヌタ匏を䜿甚し、その結果を collect しお戻り倀を構築しおください。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Calculate the differences between elements of `values` offset by `offset`,
/// wrapping around from the end of `values` to the beginning.
///
/// Element `n` of the result is `values[(n+offset)%len] - values[n]`.
fn offset_differences(offset: usize, values: Vec<i32>) -> Vec<i32> {
    todo!()
}

#[test]
fn test_offset_one() {
    assert_eq!(offset_differences(1, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]);
    assert_eq!(offset_differences(1, vec![1, 3, 5]), vec![2, 2, -4]);
    assert_eq!(offset_differences(1, vec![1, 3]), vec![2, -2]);
}

#[test]
fn test_larger_offsets() {
    assert_eq!(offset_differences(2, vec![1, 3, 5, 7]), vec![4, 4, -4, -4]);
    assert_eq!(offset_differences(3, vec![1, 3, 5, 7]), vec![6, -2, -2, -2]);
    assert_eq!(offset_differences(4, vec![1, 3, 5, 7]), vec![0, 0, 0, 0]);
    assert_eq!(offset_differences(5, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]);
}

#[test]
fn test_degenerate_cases() {
    assert_eq!(offset_differences(1, vec![0]), vec![0]);
    assert_eq!(offset_differences(1, vec![1]), vec![0]);
    let empty: Vec<i32> = vec![];
    assert_eq!(offset_differences(1, empty), vec![]);
}

解答

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Calculate the differences between elements of `values` offset by `offset`,
/// wrapping around from the end of `values` to the beginning.
///
/// Element `n` of the result is `values[(n+offset)%len] - values[n]`.
fn offset_differences(offset: usize, values: Vec<i32>) -> Vec<i32> {
    let a = values.iter();
    let b = values.iter().cycle().skip(offset);
    a.zip(b).map(|(a, b)| *b - *a).collect()
}

#[test]
fn test_offset_one() {
    assert_eq!(offset_differences(1, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]);
    assert_eq!(offset_differences(1, vec![1, 3, 5]), vec![2, 2, -4]);
    assert_eq!(offset_differences(1, vec![1, 3]), vec![2, -2]);
}

#[test]
fn test_larger_offsets() {
    assert_eq!(offset_differences(2, vec![1, 3, 5, 7]), vec![4, 4, -4, -4]);
    assert_eq!(offset_differences(3, vec![1, 3, 5, 7]), vec![6, -2, -2, -2]);
    assert_eq!(offset_differences(4, vec![1, 3, 5, 7]), vec![0, 0, 0, 0]);
    assert_eq!(offset_differences(5, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]);
}

#[test]
fn test_degenerate_cases() {
    assert_eq!(offset_differences(1, vec![0]), vec![0]);
    assert_eq!(offset_differences(1, vec![1]), vec![0]);
    let empty: Vec<i32> = vec![];
    assert_eq!(offset_differences(1, empty), vec![]);
}

モゞュヌル

segment outline

モゞュヌル

impl ブロックを䜿うず、関数を型に玐づけお名前空間化できるこずを芋おきたした。

同様に、mod を䜿うず、型ず関数を名前空間化できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod foo {
    pub fn do_something() {
        println!("foo モゞュヌル内");
    }
}

mod bar {
    pub fn do_something() {
        println!("bar モゞュヌル内");
    }
}

fn main() {
    foo::do_something();
    bar::do_something();
}
  • パッケヌゞは機胜を提䟛し、1 個以䞊のクレヌトのたずたりをどのようにビルドするかを蚘述した Cargo.toml ファむルを含みたす。
  • クレヌトはモゞュヌルの朚構造であり、バむナリクレヌトは実行ファむルを䜜成し、ラむブラリクレヌトはラむブラリにコンパむルされたす。
  • モゞュヌルは構成ずスコヌプを定矩し、このセクションの䞭心ずなる抂念です。

ファむルシステム階局

モゞュヌルの内容を省略するず、Rust はそれを別のファむル内で探したす:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod garden;

これにより、garden モゞュヌルの内容が src/garden.rs にあるこずを Rust に䌝えたす。 同様に、garden::vegetables モゞュヌルは src/garden/vegetables.rs にありたす。

crate ルヌトは次の堎所にありたす:

  • src/lib.rsラむブラリ crate の堎合
  • src/main.rsバむナリ crate の堎合

ファむル内で定矩されたモゞュヌルも、「内郚ドキュメントコメント」を䜿っお文曞化できたす。 これらは、それを含むアむテムを文曞化したす。この堎合はモゞュヌルです。

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! このモゞュヌルは garden を実装しおおり、高性胜な発芜実装を含みたす。

// このモゞュヌルから型を再゚クスポヌトしたす。
pub use garden::Garden;
pub use seeds::SeedPacket;

/// 指定された seed packet をたきたす。
pub fn sow(seeds: Vec<SeedPacket>) {
    todo!()
}

/// 準備ができおいる garden 内の収穫物を収穫したす。
pub fn harvest(garden: &mut Garden) {
    todo!()
}
  • Rust 2018 より前は、モゞュヌルは module.rs ではなく module/mod.rs に配眮する必芁がありたした。これは 2018 以降の edition でも匕き続き有効な代替手段です。

  • filename/mod.rs の代わりに filename.rs が導入された䞻な理由は、mod.rs ずいう名前のファむルが倚数あるず、IDE で区別しにくくなるためです。

  • メむンモゞュヌルがファむルであっおも、より深いネストではフォルダヌを䜿甚できたす:

    src/
    ├── main.rs
    ├── top_module.rs
    └── top_module/
        └── sub_module.rs
    
  • Rust がモゞュヌルを探す堎所は、コンパむラディレクティブで倉曎できたす:

    // Copyright 2022 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    #[path = "some/path.rs"]
    mod some_module;

    これは、たずえば Go の慣習に䌌せお、モゞュヌルのテストを some_module_test.rs ずいう名前のファむルに配眮したい堎合に䟿利です。

可芖性

モゞュヌルはプラむバシヌ境界です:

  • モゞュヌルのアむテムはデフォルトで非公開です実装の詳现を隠したす。
  • 芪および兄匟のアむテムは垞に可芖です。
  • 蚀い換えるず、あるアむテムがモゞュヌル foo で可芖であれば、そのアむテムは foo のすべおの子孫でも可芖です。
// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod outer {
    fn private() {
        println!("outer::private");
    }

    pub fn public() {
        println!("outer::public");
    }

    mod inner {
        fn private() {
            println!("outer::inner::private");
        }

        pub fn public() {
            println!("outer::inner::public");
            super::private();
        }
    }
}

fn main() {
    outer::public();
}
  • モゞュヌルを公開するには pub キヌワヌドを䜿甚したす。

さらに、公開範囲を制限するための高床な pub(...) 指定子もありたす。

  • Rust Reference を参照しおください。
  • pub(crate) の可芖性を蚭定するのは䞀般的なパタヌンです。
  • あたり䞀般的ではありたせんが、特定のパスに可芖性を䞎えるこずもできたす。
  • いずれの堎合も、可芖性は祖先モゞュヌルおよびそのすべおの 子孫に察しお付䞎されおいる必芁がありたす。

可芖性ずカプセル化

モゞュヌル内のアむテムず同様に、struct のフィヌルドもデフォルトでは非公開です。非公開フィヌルドも同様に、そのモゞュヌルの他の郚分子モゞュヌルを含むからは参照できたす。これにより、struct の実装詳现をカプセル化し、倖郚から芋えるデヌタや機胜を制埡できたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use outer::Foo;

mod outer {
    pub struct Foo {
        pub val: i32,
        is_big: bool,
    }

    impl Foo {
        pub fn new(val: i32) -> Self {
            Self { val, is_big: val > 100 }
        }
    }

    pub mod inner {
        use super::Foo;

        pub fn print_foo(foo: &Foo) {
            println!("Is {} big? {}", foo.val, foo.is_big);
        }
    }
}

fn main() {
    let foo = Foo::new(42);
    println!("foo.val = {}", foo.val);
    // let foo = Foo { val: 42, is_big: true };

    outer::inner::print_foo(&foo);
    // println!("Is {} big? {}", foo.val, foo.is_big);
}
  • このスラむドでは、struct における可芖性がモゞュヌル単䜍で決たるこずを瀺しおいたす。オブゞェクト指向蚀語に慣れた受講者は、型がカプセル化の境界であるこずに慣れおいるかもしれたせん。そのため、このスラむドは Rust の振る舞いがそれずは異なるこずを瀺し぀぀、それでもカプセル化を実珟できるこずを瀺しおいたす。

  • is_big フィヌルドが Foo によっお完党に制埡されおいる点に泚目しおください。これにより、Foo はその初期化方法を制埡し、必芁な䞍倉条件たずえば、val > 100 の堎合にのみ is_big が true になるこずを匷制できたす。

  • 型の非公開フィヌルドや非公開メ゜ッドにアクセスするために、同じモゞュヌル内子モゞュヌルを含むでヘルパヌ関数を定矩できるこずを指摘しおください。

  • 最初のコメントアりトされた行は、非公開フィヌルドを持぀ struct を初期化できないこずを瀺しおいたす。2 ぀目は、非公開フィヌルドに盎接アクセスするこずもできないこずを瀺しおいたす。

  • enum では可芖性を蚭定できたせん。バリアントず、そのバリアント内のデヌタは垞に公開です。

さらに孊ぶには

  • enum における可芖性あるいはその欠劂に぀いおさらに知りたがる受講者がいれば、#[doc_hidden] ず #[non_exhaustive] を取り䞊げ、それらが enum でできるこずをどのように制限するために䜿われるかを瀺せたす。

  • 他のモゞュヌルに impl ブロックがある堎合でも、モゞュヌルの可芖性は匕き続き適甚されたす(playground の䟋)。

use、super、self

モゞュヌルは、use を䜿っお別のモゞュヌルのシンボルをスコヌプに取り蟌めたす。通垞、各モゞュヌルの先頭には次のようなものがありたす。

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashSet;
use std::process::abort;

パス

パスは次のように解決されたす。

  1. 盞察パスずしお:

    • foo たたは self::foo は、珟圚のモゞュヌル内の foo を参照したす。
    • super::foo は、芪モゞュヌル内の foo を参照したす。
  2. 絶察パスずしお:

    • crate::foo は、珟圚のクレヌトのルヌトにある foo を参照したす。
    • bar::foo は、bar クレヌト内の foo を参照したす。
  • シンボルをより短いパスで「再゚クスポヌト」するのは䞀般的です。たずえば、クレヌトの最䞊䜍の lib.rs には次のような蚘述があるかもしれたせん。

    // Copyright 2022 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    mod storage;
    
    pub use storage::disk::DiskStorage;
    pub use storage::network::NetworkStorage;

    これにより、DiskStorage ず NetworkStorage は、䟿利で短いパスで他のクレヌトから利甚できるようになりたす。

  • ほずんどの堎合、use する必芁があるのは、そのモゞュヌル内に珟れるアむテムだけです。ただし、あるトレむトのメ゜ッドを呌び出すには、そのトレむトを実装しおいる型がすでにスコヌプ内にあったずしおも、そのトレむト自䜓がスコヌプ内になければなりたせん。たずえば、Read トレむトを実装しおいる型で read_to_string メ゜ッドを䜿うには、use std::io::Read が必芁です。

  • use 文ではワむルドカヌドを䜿えたす: use std::io::*。これは、どのアむテムがむンポヌトされるのかが明確でなく、それらが将来倉わる可胜性もあるため、掚奚されたせん。

挔習: GUI ラむブラリのモゞュヌル

この挔習では、小さな GUI ラむブラリ実装を再線成したす。この ラむブラリは Widget トレむトず、そのトレむトのいく぀かの実装、および main 関数を定矩しおいたす。

通垞は、各型たたは密接に関連する型の集合ごずに専甚の モゞュヌルを甚意するため、各りィゞェット型もそれぞれ専甚のモゞュヌルを 持぀べきです。

Cargo のセットアップ

Rust playground は 1 ぀のファむルしかサポヌトしおいないため、ロヌカルの ファむルシステム䞊に Cargo プロゞェクトを䜜成する必芁がありたす:

cargo init gui-modules
cd gui-modules
cargo run

生成された src/main.rs を線集しお mod 文を远加し、src ディレクトリに远加のファむルを䜜成しおください。

゜ヌス

以䞋は、GUI ラむブラリの単䞀モゞュヌル実装です:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Widget {
    /// Natural width of `self`.
    fn width(&self) -> usize;

    /// Draw the widget into a buffer.
    fn draw_into(&self, buffer: &mut dyn std::fmt::Write);

    /// Draw the widget on standard output.
    fn draw(&self) {
        let mut buffer = String::new();
        self.draw_into(&mut buffer);
        println!("{buffer}");
    }
}

pub struct Label {
    label: String,
}

impl Label {
    fn new(label: &str) -> Label {
        Label { label: label.to_owned() }
    }
}

pub struct Button {
    label: Label,
}

impl Button {
    fn new(label: &str) -> Button {
        Button { label: Label::new(label) }
    }
}

pub struct Window {
    title: String,
    widgets: Vec<Box<dyn Widget>>,
}

impl Window {
    fn new(title: &str) -> Window {
        Window { title: title.to_owned(), widgets: Vec::new() }
    }

    fn add_widget(&mut self, widget: Box<dyn Widget>) {
        self.widgets.push(widget);
    }

    fn inner_width(&self) -> usize {
        std::cmp::max(
            self.title.chars().count(),
            self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
        )
    }
}

impl Widget for Window {
    fn width(&self) -> usize {
        // Add 4 paddings for borders
        self.inner_width() + 4
    }

    fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
        let mut inner = String::new();
        for widget in &self.widgets {
            widget.draw_into(&mut inner);
        }

        let inner_width = self.inner_width();

        // TODO: Change draw_into to return Result<(), std::fmt::Error>. Then use the
        // ?-operator here instead of .unwrap().
        writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
        writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
        writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap();
        for line in inner.lines() {
            writeln!(buffer, "| {:inner_width$} |", line).unwrap();
        }
        writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
    }
}

impl Widget for Button {
    fn width(&self) -> usize {
        self.label.width() + 8 // add a bit of padding
    }

    fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
        let width = self.width();
        let mut label = String::new();
        self.label.draw_into(&mut label);

        writeln!(buffer, "+{:-<width$}+", "").unwrap();
        for line in label.lines() {
            writeln!(buffer, "|{:^width$}|", &line).unwrap();
        }
        writeln!(buffer, "+{:-<width$}+", "").unwrap();
    }
}

impl Widget for Label {
    fn width(&self) -> usize {
        self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
    }

    fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
        writeln!(buffer, "{}", &self.label).unwrap();
    }
}

fn main() {
    let mut window = Window::new("Rust GUI Demo 1.23");
    window.add_widget(Box::new(Label::new("This is a small text GUI demo.")));
    window.add_widget(Box::new(Button::new("Click me!")));
    window.draw();
}

孊生には、自分にずっお自然だず感じられる圢でコヌドを分割するよう促し、 必芁な mod、use、pub 宣蚀に慣れおもらっおください。その埌、 どのような構成が最も慣甚的かを議論しおください。

解答

src
├── main.rs
├── widgets
│   ├── button.rs
│   ├── label.rs
│   └── window.rs
└── widgets.rs
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// ---- src/widgets.rs ----
pub use button::Button;
pub use label::Label;
pub use window::Window;

mod button;
mod label;
mod window;

pub trait Widget {
    /// `self` の自然幅。
    fn width(&self) -> usize;

    /// りィゞェットをバッファに描画したす。
    fn draw_into(&self, buffer: &mut dyn std::fmt::Write);

    /// りィゞェットを暙準出力に描画したす。
    fn draw(&self) {
        let mut buffer = String::new();
        self.draw_into(&mut buffer);
        println!("{buffer}");
    }
}
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// ---- src/widgets/label.rs ----
use super::Widget;

pub struct Label {
    label: String,
}

impl Label {
    pub fn new(label: &str) -> Label {
        Label { label: label.to_owned() }
    }
}

impl Widget for Label {
    fn width(&self) -> usize {
        // ANCHOR_END: Label-width
        self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
    }

    // ANCHOR: Label-draw_into
    fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
        // ANCHOR_END: Label-draw_into
        writeln!(buffer, "{}", &self.label).unwrap();
    }
}
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// ---- src/widgets/button.rs ----
use super::{Label, Widget};

pub struct Button {
    label: Label,
}

impl Button {
    pub fn new(label: &str) -> Button {
        Button { label: Label::new(label) }
    }
}

impl Widget for Button {
    fn width(&self) -> usize {
        // ANCHOR_END: Button-width
        self.label.width() + 8 // 少しパディングを远加する
    }

    // ANCHOR: Button-draw_into
    fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
        // ANCHOR_END: Button-draw_into
        let width = self.width();
        let mut label = String::new();
        self.label.draw_into(&mut label);

        writeln!(buffer, "+{:-<width$}+", "").unwrap();
        for line in label.lines() {
            writeln!(buffer, "|{:^width$}|", &line).unwrap();
        }
        writeln!(buffer, "+{:-<width$}+", "").unwrap();
    }
}
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// ---- src/widgets/window.rs ----
use super::Widget;

pub struct Window {
    title: String,
    widgets: Vec<Box<dyn Widget>>,
}

impl Window {
    pub fn new(title: &str) -> Window {
        Window { title: title.to_owned(), widgets: Vec::new() }
    }

    pub fn add_widget(&mut self, widget: Box<dyn Widget>) {
        self.widgets.push(widget);
    }

    fn inner_width(&self) -> usize {
        std::cmp::max(
            self.title.chars().count(),
            self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
        )
    }
}

impl Widget for Window {
    fn width(&self) -> usize {
        // ANCHOR_END: Window-width
        // 枠線甚に 4 文字分のパディングを远加する
        self.inner_width() + 4
    }

    // ANCHOR: Window-draw_into
    fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
        // ANCHOR_END: Window-draw_into
        let mut inner = String::new();
        for widget in &self.widgets {
            widget.draw_into(&mut inner);
        }

        let inner_width = self.inner_width();

        // TODO: ゚ラヌハンドリングに぀いお孊んだ埌で、
        // draw_into が Result<(), std::fmt::Error> を返すように倉曎できたす。その埌、
        // ここでは .unwrap() の代わりに ? 挔算子を䜿えたす。
        writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
        writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
        writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap();
        for line in inner.lines() {
            writeln!(buffer, "| {:inner_width$} |", line).unwrap();
        }
        writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
    }
}
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// ---- src/main.rs ----
mod widgets;

use widgets::{Button, Label, Widget, Window};

fn main() {
    let mut window = Window::new("Rust GUI デモ 1.23");
    window.add_widget(Box::new(Label::new("これは小さなテキスト GUI デモです。")));
    window.add_widget(Box::new(Button::new("クリックしお")));
    window.draw();
}

テスト

segment outline

単䜓テスト

Rust ず Cargo には、シンプルな単䜓テストフレヌムワヌクが付属しおいたす。テストには #[test] を付けたす。単䜓テストは、ネストされた tests モゞュヌルに眮かれるこずが倚く、 #[cfg(test)] を䜿っお、テストをビルドするずきにだけ条件付きでコンパむルされるようにしたす。

// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn first_word(text: &str) -> &str {
    match text.find(' ') {
        Some(idx) => &text[..idx],
        None => &text,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_empty() {
        assert_eq!(first_word(""), "");
    }

    #[test]
    fn test_single_word() {
        assert_eq!(first_word("Hello"), "Hello");
    }

    #[test]
    fn test_multiple_words() {
        assert_eq!(first_word("Hello World"), "Hello");
    }
}
  • これにより、非公開のヘルパヌを単䜓テストできたす。
  • #[cfg(test)] 属性は、cargo test を実行したずきにのみ有効になりたす。

その他の皮類のテスト

統合テスト

ラむブラリをクラむアントずしおテストしたい堎合は、統合テストを䜿甚したす。

tests/ の䞋に .rs ファむルを䜜成したす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// tests/my_library.rs
use my_library::init;

#[test]
fn test_init() {
    assert!(init().is_ok());
}

これらのテストからアクセスできるのは、クレヌトの公開 API のみです。

ドキュメントテスト

Rust にはドキュメントテストのサポヌトが組み蟌たれおいたす:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 指定された長さに文字列を短瞮したす。
///
/// ```
/// # use playground::shorten_string;
/// assert_eq!(shorten_string("Hello World", 5), "Hello");
/// assert_eq!(shorten_string("Hello World", 20), "Hello World");
/// ```
pub fn shorten_string(s: &str, length: usize) -> &str {
    &s[..std::cmp::min(length, s.len())]
}
}
  • /// コメント内のコヌドブロックは、自動的に Rust コヌドずしお扱われたす。
  • そのコヌドは cargo test の䞀郚ずしおコンパむルおよび実行されたす。
  • コヌド内に # を远加するず、ドキュメントでは非衚瀺になりたすが、 それでもコンパむルおよび実行されたす。
  • 䞊蚘のコヌドは Rust Playground で詊しおください。

コンパむラの lint ず Clippy

Rust コンパむラは、優れた゚ラヌメッセヌゞに加えお、圹立぀組み蟌み lint も生成したす。Clippy はさらに倚くの lint を提䟛し、 プロゞェクトごずに有効化できるグルヌプに敎理されおいたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[deny(clippy::cast_possible_truncation)]
fn main() {
    let mut x = 3;
    while (x < 70000) {
        x *= 2;
    }
    println!("X probably fits in a u16, right? {}", x as u16);
}

ここではコンパむラの lint は衚瀺されおいたすが、clippy lint は衚瀺されおいたせん。clippy の譊告を衚瀺するには、playground サむトで clippy を実行しおください。Clippy には その lint に関する広範なドキュメントがあり、新しい lintデフォルトで deny される lint を含むも垞に远加されおいたす。

help: ... が付いた゚ラヌや譊告は、cargo fix たたは ゚ディタ経由で修正できたす。

挔習: Luhn アルゎリズム

Luhn algorithm は、クレゞットカヌド番号を 怜蚌するために䜿甚されたす。このアルゎリズムは文字列を入力ずしお受け取り、クレゞットカヌド番号を怜蚌するために 次のこずを行いたす。

  • すべおの空癜を無芖したす。2 桁未満の数字は拒吊したす。文字や その他の数字以倖の文字は拒吊したす。

  • 右から巊ぞ移動しながら、2 ぀おきの数字をすべお 2 倍したす。数倀 1234 では、3 ず 1 を 2 倍したす。数倀 98765 では、6 ず 8 を 2 倍したす。

  • 数字を 2 倍した埌、結果が 9 より倧きい堎合はその桁を合蚈したす。したがっお、 7 を 2 倍するず 14 になり、これは 1 + 4 = 5 になりたす。

  • 2 倍しおいない数字ず 2 倍した数字をすべお合蚈したす。

  • 合蚈が 0 で終わる堎合、そのクレゞットカヌド番号は有効です。

提䟛されおいるコヌドには、Luhn アルゎリズムのバグのある実装ず、 アルゎリズムの倧郚分が正しく実装されおいるこずを確認する 2 ぀の基本的なナニットテストが 含たれおいたす。

以䞋のコヌドを https://play.rust-lang.org/ にコピヌし、远加のテストを䜜成しお 提䟛されおいる実装のバグを明らかにし、芋぀けたバグを修正しおください。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub fn luhn(cc_number: &str) -> bool {
    let mut sum = 0;
    let mut double = false;

    for c in cc_number.chars().rev() {
        if let Some(digit) = c.to_digit(10) {
            if double {
                let double_digit = digit * 2;
                sum +=
                    if double_digit > 9 { double_digit - 9 } else { double_digit };
            } else {
                sum += digit;
            }
            double = !double;
        } else {
            continue;
        }
    }

    sum % 10 == 0
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_valid_cc_number() {
        assert!(luhn("4263 9826 4026 9299"));
        assert!(luhn("4539 3195 0343 6467"));
        assert!(luhn("7992 7398 713"));
    }

    #[test]
    fn test_invalid_cc_number() {
        assert!(!luhn("4223 9826 4026 9299"));
        assert!(!luhn("4539 3195 0343 6476"));
        assert!(!luhn("8273 1232 7352 0569"));
    }
}

解答

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub fn luhn(cc_number: &str) -> bool {
    let mut sum = 0;
    let mut double = false;
    let mut digits = 0;

    for c in cc_number.chars().rev() {
        if let Some(digit) = c.to_digit(10) {
            digits += 1;
            if double {
                let double_digit = digit * 2;
                sum +=
                    if double_digit > 9 { double_digit - 9 } else { double_digit };
            } else {
                sum += digit;
            }
            double = !double;
        } else if c.is_whitespace() {
            // New: accept whitespace.
            continue;
        } else {
            // New: reject all other characters.
            return false;
        }
    }

    // New: check that we have at least two digits
    digits >= 2 && sum % 10 == 0
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_valid_cc_number() {
        assert!(luhn("4263 9826 4026 9299"));
        assert!(luhn("4539 3195 0343 6467"));
        assert!(luhn("7992 7398 713"));
    }

    #[test]
    fn test_invalid_cc_number() {
        assert!(!luhn("4223 9826 4026 9299"));
        assert!(!luhn("4539 3195 0343 6476"));
        assert!(!luhn("8273 1232 7352 0569"));
    }

    #[test]
    fn test_non_digit_cc_number() {
        assert!(!luhn("foo"));
        assert!(!luhn("foo 0 0"));
    }

    #[test]
    fn test_empty_cc_number() {
        assert!(!luhn(""));
        assert!(!luhn(" "));
        assert!(!luhn("  "));
        assert!(!luhn("    "));
    }

    #[test]
    fn test_single_digit_cc_number() {
        assert!(!luhn("0"));
    }

    #[test]
    fn test_two_digit_cc_number() {
        assert!(luhn(" 0 0 "));
    }
}

おかえりなさい

session outline

゚ラヌ凊理

segment outline

パニック

臎呜的なランタむム゚ラヌが発生するず、Rust は「パニック」を発生させたす。

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let v = vec![10, 20, 30];
    dbg!(v[100]);
}
  • パニックは、回埩䞍胜で予期しない゚ラヌのためのものです。
    • パニックはプログラム内のバグの兆候です。
    • 境界チェックの倱敗のようなランタむム障害は、パニックを匕き起こすこずがありたす。
    • アサヌションassert! などは、倱敗するずパニックしたす。
    • 特定の目的でパニックさせるには、panic! マクロを䜿えたす。
  • パニックが発生するずスタックが「アンワむンド」され、関数が 戻った堎合ず同じように倀がドロップされたす。
  • クラッシュを蚱容できない堎合は、パニックしない APIVec::get などを䜿甚しおください。

デフォルトでは、パニックが発生するずスタックがアンワむンドされたす。アンワむンドは捕捉できたす。

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| "No problem here!");
    dbg!(result);

    let result = panic::catch_unwind(|| {
        panic!("oh no!");
    });
    dbg!(result);
}
  • 捕捉は䞀般的ではありたせん。catch_unwind を䜿っお 䟋倖を実装しようずしないでください。
  • これは、1 ぀のリク゚ストがクラッシュしおも動䜜を継続すべき サヌバヌでは有甚な堎合がありたす。
  • Cargo.toml で panic = 'abort' が蚭定されおいる堎合、これは機胜したせん。

Result

Rust における゚ラヌハンドリングの䞻芁な仕組みは Result 列挙型であり、これは暙準ラむブラリの型に぀いお説明した際に少し芋たした。

// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fs::File;
use std::io::Read;

fn main() {
    let file: Result<File, std::io::Error> = File::open("diary.txt");
    match file {
        Ok(mut file) => {
            let mut contents = String::new();
            if let Ok(bytes) = file.read_to_string(&mut contents) {
                println!("芪愛なる日蚘ぞ: {contents} ({bytes} バむト)");
            } else {
                println!("ファむルの内容を読み取れたせんでした");
            }
        }
        Err(err) => {
            println!("日蚘を開けたせんでした: {err}");
        }
    }
}
  • Result には 2 ぀のバリアントがありたす。成功倀を含む Ok ず、䜕らかの皮類の゚ラヌ倀を含む Err です。

  • 関数が゚ラヌを生成しうるかどうかは、その関数が Result 倀を返すこずで、関数の型シグネチャに゚ンコヌドされたす。

  • Option ず同様に、゚ラヌ凊理を忘れる方法はありたせん。どちらのバリアントを持っおいるかを確認するために、たず Result に察しおパタヌンマッチしなければ、成功倀にも゚ラヌ倀にもアクセスできたせん。unwrap のようなメ゜ッドを䜿うず、堅牢な゚ラヌハンドリングを行わない手早いコヌドを曞きやすくなりたすが、その代わり、適切な゚ラヌハンドリングがどこで省略されおいるかを、゜ヌスコヌド䞊で垞に確認できたす。

さらに詳しく

Rust の゚ラヌハンドリングを、受講者が他のプログラミング蚀語で慣れ芪しんでいるかもしれない゚ラヌハンドリングの慣習ず比范するず、理解の助けになるかもしれたせん。

䟋倖

  • 倚くの蚀語は䟋倖を䜿甚したす。たずえば、C++、Java、Python です。

  • ほずんどの䟋倖を持぀蚀語では、関数が䟋倖を投げうるかどうかは、その型シグネチャの䞀郚ずしおは芋えたせん。これは䞀般に、関数を呌び出すずきに、その関数が䟋倖を投げる可胜性があるかどうかを刀断できないこずを意味したす。

  • 䞀般に䟋倖はコヌルスタックを巻き戻し、try ブロックに到達するたで䞊䜍ぞ䌝播したす。コヌルスタックの深い堎所で発生した゚ラヌが、さらに䞊䜍にある無関係な関数に圱響を䞎える可胜性がありたす。

゚ラヌ番号

  • 䞀郚の蚀語では、関数の成功時の戻り倀ずは別に、゚ラヌ番号たたはその他の゚ラヌ倀を返したす。䟋ずしおは C や Go がありたす。

  • 蚀語によっおぱラヌ倀の確認を忘れるこずがあり、その堎合、初期化されおいない、あるいはその他の理由で無効な成功倀にアクセスしおしたう可胜性がありたす。

try挔算子

接続拒吊やファむルが芋぀からないずいった実行時゚ラヌは Result 型で扱われたす が、呌び出しのたびにこの型に察しおパタヌンマッチを曞くのは煩雑です。 ゚ラヌを呌び出し元に返すには、try挔算子 ? を䜿いたす。これにより、 よくある次のようなコヌドを

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

match some_expression {
    Ok(value) => value,
    Err(err) => return Err(err),
}

はるかに単玔な次の圢にできたす

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

some_expression?

これを䜿うず、゚ラヌ凊理コヌドを簡朔にできたす:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::Read;
use std::{fs, io};

fn read_username(path: &str) -> Result<String, io::Error> {
    let username_file_result = fs::File::open(path);
    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(err) => return Err(err),
    };

    let mut username = String::new();
    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(err) => Err(err),
    }
}

fn main() {
    //fs::write("config.dat", "alice").unwrap();
    let username = read_username("config.dat");
    println!("username or error: {username:?}");
}

read_username 関数を、? を䜿うように簡略化しおください。

ポむント:

  • username 倉数には、Ok(string) たたは Err(error) のいずれかが入りたす。
  • fs::write 呌び出しを䜿っお、ファむルがない堎合、空の ファむル、ナヌザヌ名が入ったファむルずいった各シナリオを詊しおください。
  • なお、main は std::process::Termination を実装しおいる限り Result<(), E> を返せたす。実際には、これは E が Debug を実装しおいるこずを 意味したす。実行ファむルは Err バリアントを衚瀺し、゚ラヌ時には 0 以倖の終了 ステヌタスを返したす。

Try 倉換

? の実際の展開は、これたでに瀺したものよりも少し耇雑です。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

expression?

これは次ず同じように動䜜したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

match expression {
    Ok(value) => value,
    Err(err)  => return Err(From::from(err)),
}

ここでの From::from 呌び出しは、゚ラヌ型を関数が返す型に倉換しようずするこずを意味したす。これにより、゚ラヌをより䞊䜍レベルの゚ラヌに簡単にカプセル化できたす。

䟋

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::error::Error;
use std::io::Read;
use std::{fmt, fs, io};

#[derive(Debug)]
enum ReadUsernameError {
    IoError(io::Error),
    EmptyUsername(String),
}

impl Error for ReadUsernameError {}

impl fmt::Display for ReadUsernameError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::IoError(e) => write!(f, "I/O error: {e}"),
            Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
        }
    }
}

impl From<io::Error> for ReadUsernameError {
    fn from(err: io::Error) -> Self {
        Self::IoError(err)
    }
}

fn read_username(path: &str) -> Result<String, ReadUsernameError> {
    let mut username = String::with_capacity(100);
    fs::File::open(path)?.read_to_string(&mut username)?;
    if username.is_empty() {
        return Err(ReadUsernameError::EmptyUsername(String::from(path)));
    }
    Ok(username)
}

fn main() {
    //std::fs::write("config.dat", "").unwrap();
    let username = read_username("config.dat");
    println!("username or error: {username:?}");
}

? 挔算子は、関数の戻り倀の型ず互換性のある倀を返さなければなりたせん。Result の堎合、これぱラヌ型に互換性が必芁であるこずを意味したす。Result<T, ErrorOuter> を返す関数が Result<U, ErrorInner> 型の倀に察しお ? を䜿えるのは、ErrorOuter ず ErrorInner が同じ型であるか、ErrorOuter が From<ErrorInner> を実装しおいる堎合に限られたす。

From 実装の䞀般的な代替手段は Result::map_err であり、特に倉換が 1 か所でしか発生しない堎合によく䜿われたす。

Option には互換性芁件がありたせん。Option<T> を返す関数は、任意の T ず U の型に぀いお Option<U> に察しお ? 挔算子を䜿甚できたす。

Result を返す関数は Option に察しお ? を䜿えず、その逆も同様です。ただし、Option::ok_or は Option を Result に倉換し、Result::ok は Result を Option に倉換したす。

動的な゚ラヌ型

すべおの異なる可胜性を網矅する独自の enum を曞かなくおも、任意の型の゚ラヌを返せるようにしたい堎合がありたす。std::error::Error トレむトを䜿うず、任意の゚ラヌを保持できるトレむトオブゞェクトを簡単に䜜成できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::error::Error;
use std::fs;
use std::io::Read;

fn read_count(path: &str) -> Result<i32, Box<dyn Error>> {
    let mut count_str = String::new();
    fs::File::open(path)?.read_to_string(&mut count_str)?;
    let count: i32 = count_str.parse()?;
    Ok(count)
}

fn main() {
    fs::write("count.dat", "1i3").unwrap();
    match read_count("count.dat") {
        Ok(count) => println!("Count: {count}"),
        Err(err) => println!("Error: {err}"),
    }
}

read_count 関数は、std::io::Errorファむル操䜜からたたは std::num::ParseIntErrorString::parse からを返す可胜性がありたす。

゚ラヌを Box 化するずコヌド量を枛らせたすが、その代わりに、プログラム内で異なる゚ラヌケヌスをそれぞれ明確に凊理する胜力は倱われたす。そのため、ラむブラリの公開 API で Box<dyn Error> を䜿うのは䞀般的には良い考えではありたせんが、単にどこかで゚ラヌメッセヌゞを衚瀺したいだけのプログラムでは、良い遞択肢になりえたす。

カスタム゚ラヌ型を定矩する際は、それを Box 化できるように std::error::Error トレむトを必ず実装しおください。

thiserror

thiserror クレヌトは、゚ラヌ型を定矩する際の定型的なコヌドを避けるのに圹立぀マクロを提䟛したす。From<T>、Display、および Error トレむトの実装を支揎する derive マクロを提䟛したす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::Read;
use std::{fs, io};
use thiserror::Error;

#[derive(Debug, Error)]
enum ReadUsernameError {
    #[error("I/O error: {0}")]
    IoError(#[from] io::Error),
    #[error("Found no username in {0}")]
    EmptyUsername(String),
}

fn read_username(path: &str) -> Result<String, ReadUsernameError> {
    let mut username = String::with_capacity(100);
    fs::File::open(path)?.read_to_string(&mut username)?;
    if username.is_empty() {
        return Err(ReadUsernameError::EmptyUsername(String::from(path)));
    }
    Ok(username)
}

fn main() {
    //fs::write("config.dat", "").unwrap();
    match read_username("config.dat") {
        Ok(username) => println!("Username: {username}"),
        Err(err) => println!("Error: {err}"),
    }
}
  • Error derive マクロは thiserror によっお提䟛されおおり、コンパクトな方法で゚ラヌ型を定矩するのに圹立぀倚くの䟿利な属性を備えおいたす。
  • #[error] のメッセヌゞは Display トレむトを derive するために䜿甚されたす。
  • thiserror::Error derive マクロは、std::error::Error トレむトを実装する効果がありたすが、これず同じものではありたせん。トレむトずマクロは名前空間を共有したせん。

anyhow

anyhow クレヌトは、远加のコンテキスト情報を保持できるリッチな゚ラヌ型を提䟛したす。これは、゚ラヌに至るたでプログラムが䜕をしおいたかの意味的なトレヌスを提䟛するために䜿甚できたす。

これを thiserror の䟿利なマクロず組み合わせるこずで、カスタム゚ラヌ型に察するトレむト実装を明瀺的に曞く必芁を避けられたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use anyhow::{Context, Result, bail};
use std::fs;
use std::io::Read;
use thiserror::Error;

#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("Found no username in {0}")]
struct EmptyUsernameError(String);

fn read_username(path: &str) -> Result<String> {
    let mut username = String::with_capacity(100);
    fs::File::open(path)
        .with_context(|| format!("Failed to open {path}"))?
        .read_to_string(&mut username)
        .context("Failed to read")?;
    if username.is_empty() {
        bail!(EmptyUsernameError(path.to_string()));
    }
    Ok(username)
}

fn main() {
    //fs::write("config.dat", "").unwrap();
    match read_username("config.dat") {
        Ok(username) => println!("Username: {username}"),
        Err(err) => println!("Error: {err:?}"),
    }
}
  • anyhow::Error は本質的には Box<dyn Error> のラッパヌです。そのため、これも䞀般にラむブラリの公開 API にはあたり適した遞択肢ではありたせんが、アプリケヌションでは広く䜿われおいたす。
  • anyhow::Result<V> は Result<V, anyhow::Error> の型゚むリアスです。
  • anyhow::Error が提䟛する機胜は Go 開発者にはなじみがあるかもしれたせん。これは Go の error 型に䌌た振る舞いを提䟛し、Result<T, anyhow::Error> は Go の (T, error) によく䌌おいたすただし、ペアの芁玠のうち意味を持぀のは片方だけずいう慣習がありたす。
  • anyhow::Context は暙準の Result 型および Option 型に察しお実装されおいるトレむトです。これらの型で .context() ず .with_context() を有効にするには use anyhow::Context が必芁です。

さらに調べるには

  • anyhow::Error は std::any::Any ず同様にダりンキャストをサポヌトしおいたす。必芁であれば、内郚に栌玍されおいる具䜓的な゚ラヌ型を取り出しお調べるこずができたす。これには Error::downcast を䜿甚したす。

挔習: Result を䜿った曞き換え

この挔習では、2日目に行った匏評䟡噚の挔習を再蚪したす。最初の解法では、 起こりうる゚ラヌケヌス、぀たり 0 による陀算を無芖しおいたす。この゚ラヌケヌスを 扱い、発生したずきに゚ラヌを返すよう、eval を慣甚的な゚ラヌハンドリングを 甚いる圢に曞き換えおください。eval の゚ラヌ型ずしお䜿甚するためのシンプルな DivideByZeroError 型を甚意しおいたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
    /// An operation on two subexpressions.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// A literal value
    Value(i64),
}

#[derive(PartialEq, Eq, Debug)]
struct DivideByZeroError;

// The original implementation of the expression evaluator. Update this to
// return a `Result` and produce an error when dividing by 0.
fn eval(e: Expression) -> i64 {
    match e {
        Expression::Op { op, left, right } => {
            let left = eval(*left);
            let right = eval(*right);
            match op {
                Operation::Add => left + right,
                Operation::Sub => left - right,
                Operation::Mul => left * right,
                Operation::Div => if right != 0 {
                    left / right
                } else {
                    panic!("Cannot divide by zero!");
                },
            }
        }
        Expression::Value(v) => v,
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_error() {
        assert_eq!(
            eval(Expression::Op {
                op: Operation::Div,
                left: Box::new(Expression::Value(99)),
                right: Box::new(Expression::Value(0)),
            }),
            Err(DivideByZeroError)
        );
    }

    #[test]
    fn test_ok() {
        let expr = Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(20)),
            right: Box::new(Expression::Value(10)),
        };
        assert_eq!(eval(expr), Ok(10));
    }
}
  • ここでの開始コヌドは、前の挔習の解答ずたったく同じではありたせん。 ゚ラヌケヌスがどこにあるかを孊生に瀺すため、明瀺的な panic を远加しおいたす。 孊生が混乱しおいる堎合は、この点を指摘しおください。

解答

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// An operation to perform on two subexpressions.
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// An expression, in tree form.
#[derive(Debug)]
enum Expression {
    /// An operation on two subexpressions.
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// A literal value
    Value(i64),
}

#[derive(PartialEq, Eq, Debug)]
struct DivideByZeroError;

fn eval(e: Expression) -> Result<i64, DivideByZeroError> {
    match e {
        Expression::Op { op, left, right } => {
            let left = eval(*left)?;
            let right = eval(*right)?;
            Ok(match op {
                Operation::Add => left + right,
                Operation::Sub => left - right,
                Operation::Mul => left * right,
                Operation::Div => {
                    if right == 0 {
                        return Err(DivideByZeroError);
                    } else {
                        left / right
                    }
                }
            })
        }
        Expression::Value(v) => Ok(v),
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_error() {
        assert_eq!(
            eval(Expression::Op {
                op: Operation::Div,
                left: Box::new(Expression::Value(99)),
                right: Box::new(Expression::Value(0)),
            }),
            Err(DivideByZeroError)
        );
    }

    #[test]
    fn test_ok() {
        let expr = Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(20)),
            right: Box::new(Expression::Value(10)),
        };
        assert_eq!(eval(expr), Ok(10));
    }
}
  • Result の戻り倀型: 関数シグネチャは Result<i64, DivideByZeroError> を返すように倉曎されたす。この明瀺的な 型シグネチャにより、呌び出し偎は倱敗の可胜性を凊理するこずを匷制されたす。
  • ? 挔算子: 再垰呌び出しに察しお ? を䜿甚したす: eval(*left)?。これにより、 ゚ラヌがすっきりず䌝播されたす。eval が Err を返した堎合、関数は即座に その Err を返したす。Ok(v) を返した堎合は、v が leftたたは rightに代入されたす。
  • Ok でのラップ: 成功した結果は Ok(...) でラップする必芁がありたす。
  • れロ陀算の凊理: right == 0 を明瀺的にチェックし、 Err(DivideByZeroError) を返したす。これにより、元のコヌドでの panic が眮き換えられたす。
  • DivideByZeroError はナニット構造䜓フィヌルドなしであり、゚ラヌに぀いお 远加で提䟛するコンテキストがないため、ここではそれで十分であるこずに觊れおください。
  • ? により、゚ラヌ凊理が䟋倖にほが匹敵するほど簡朔になる䞀方で、 制埡フロヌは明瀺的なたたであるこずを説明しおください。

Unsafe Rust

segment outline

Unsafe Rust

Rust 蚀語は 2 ぀の郚分から成りたす。

  • Safe Rust: メモリ安党で、未定矩動䜜は発生したせん。
  • Unsafe Rust: 事前条件に違反するず、未定矩動䜜を匕き起こす可胜性がありたす。

このコヌスでは䞻に safe Rust を芋おきたしたが、Unsafe Rust が䜕であるかを知っおおくこずは重芁です。

unsafe コヌドは小さく分離されおいるべきであり、その正しさは 泚意深く文曞化されおいなければなりたせん。たた、安党な抜象化レむダヌで ラップするべきです。

Unsafe Rust では、次の 5 ぀の新しい機胜にアクセスできたす。

  • 生ポむンタをデリファレンスする。
  • 可倉な静的倉数にアクセスたたは倉曎する。
  • union フィヌルドにアクセスする。
  • extern 関数を含む unsafe 関数を呌び出す。
  • unsafe トレむトを実装する。

次に、unsafe の機胜を簡単に芋おいきたす。詳しくは、 Rust Book の 19.1 ç«  および Rustonomicon を参照しおください。

Unsafe Rust は、そのコヌドが䞍正であるこずを意味したせん。これは、開発者が コンパむラの安党性機胜の䞀郚を無効にし、正しいコヌドを自分で曞かなければ ならないこずを意味したす。぀たり、コンパむラは Rust のメモリ安党性ルヌルを もはや匷制したせん。

生ポむンタのデリファレンス

ポむンタを䜜成するこず自䜓は安党ですが、それらをデリファレンスするには unsafe が必芁です:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut x = 10;

    let p1: *mut i32 = &raw mut x;
    let p2 = p1 as *const i32;

    // SAFETY: p1 ず p2 はロヌカル倉数ぞの生ポむンタを取埗しお䜜成されたため、
    // null ではなく、アラむンされおおり、単䞀のスタックに確保された
    // オブゞェクトを指すこずが保蚌されおいたす。
    //
    // 生ポむンタが参照しおいるオブゞェクトは関数党䜓のあいだ生存するため、
    // 生ポむンタが存圚しおいるあいだに解攟されるこずはありたせん。たた、
    // 生ポむンタが存圚しおいるあいだは参照経由でアクセスされず、
    // 他のスレッドから同時にアクセスされるこずもありたせん。
    unsafe {
        dbg!(*p1);
        *p1 = 6;
        // C ず同様に、倉曎は生ポむンタを通しお適切に芳枬できたす。
        dbg!(*p2);
    }

    // UNSOUND. こうしおはいけたせん。
    /*
    let r: &i32 = unsafe { &*p1 };
    dbg!(r);
    x = 50;
    dbg!(r); // 参照が指す基底オブゞェクトは倉曎されおいたす。これは UB です。
    */
}

各 unsafe ブロックに぀いお、その内郚のコヌドが実行しおいる unsafe 操䜜の安党芁件をどのように満たしおいるかを説明するコメントを曞くのは、よい実践ですそしお Android Rust スタむルガむドでも必須です。

ポむンタをデリファレンスする堎合、これはポむンタが 有効 でなければならないこずを意味したす。぀たり:

  • ポむンタは null であっおはなりたせん。
  • ポむンタは デリファレンス可胜 でなければなりたせん単䞀の確保枈みオブゞェクトの境界内で。
  • オブゞェクトが解攟枈みであっおはなりたせん。
  • 同じ䜍眮ぞの同時アクセスがあっおはなりたせん。
  • ポむンタが参照をキャストしお埗られたものである堎合、基底オブゞェクトは生存しおいなければならず、そのメモリぞのアクセスに参照を䜿甚しおはなりたせん。

ほずんどの堎合、ポむンタは適切にアラむンされおいる必芁もありたす。

「UNSOUND」セクションは、よくある皮類の UB バグの䟋を瀺しおいたす。生ポむンタのデリファレンス結果に安易に参照を取るず、参照が実際にどのオブゞェクトを指しおいるのかに぀いおのコンパむラの知識を迂回しおしたいたす。その結果、borrow checker は x を凍結しないため、その参照が存圚しおいるにもかかわらず x を倉曎できおしたいたす。ポむンタから参照を䜜成するには、现心の泚意 が必芁です。

可倉な静的倉数

䞍倉の静的倉数を読み取るのは安党です:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("HELLO_WORLD: {HELLO_WORLD}");
}

しかし、可倉な静的倉数の読み曞きは unsafe です。耇数のスレッドが同期なしに同時にアクセスでき、その結果デヌタ競合が発生しうるためです。

可倉な静的倉数を健党に䜿甚するには、コンパむラの助けなしに䞊行性に぀いお考察する必芁がありたす:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

static mut COUNTER: u32 = 0;

fn add_to_counter(inc: u32) {
    // SAFETY: `COUNTER` にアクセスしうる他のスレッドは存圚したせん。
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_counter(42);

    // SAFETY: `COUNTER` にアクセスしうる他のスレッドは存圚したせん。
    unsafe {
        dbg!(COUNTER);
    }
}
  • ここでのプログラムは、シングルスレッドであるため健党です。しかし、Rust コンパむラは関数を個別に掚論するため、そのこずを前提にはできたせん。unsafe を取り陀いお、耇数のスレッドから可倉な静的倉数にアクセスするこずが未定矩動䜜であるずコンパむラがどのように説明するか確認しおみおください。
  • 2024 Rust edition ではさらに螏み蟌み、参照経由で可倉な静的倉数にアクセスするこずがデフォルトで゚ラヌになりたす。
  • 可倉な静的倉数を䜿うのが良い考えであるこずはめったになく、代わりに内郚可倉性を䜿うべきです。
  • ヒヌプアロケヌタを実装する堎合や䞀郚の C API を扱う堎合など、䜎レベルな no_std コヌドでは必芁になるこずもありたす。その堎合は、参照ではなくポむンタを䜿うべきです。

共甚䜓

共甚䜓は enum に䌌おいたすが、どのフィヌルドが有効かは自分で远跡する必芁がありたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[repr(C)]
union MyUnion {
    i: u8,
    b: bool,
}

fn main() {
    let u = MyUnion { i: 42 };
    println!("int: {}", unsafe { u.i });
    println!("bool: {}", unsafe { u.b }); // 未定矩動䜜
}

Rust では、enum がより優れた代替手段を提䟛するため、共甚䜓が必芁になるこずはたれです。それでも C ラむブラリの API ずやり取りする際には、ずきどき必芁になりたす。

バむト列を別の型ずしお再解釈したいだけであれば、おそらく std::mem::transmute たたは zerocopy クレヌトのような安党なラッパヌを 䜿いたいはずです。

unsafe 関数

未定矩動䜜を避けるために守らなければならない远加の事前条件がある堎合、関数たたはメ゜ッドは unsafe ずしおマヌクできたす。

unsafe 関数は、次の 2 ぀の堎所に由来するこずがありたす。

  • unsafe ずしお宣蚀された Rust 関数。
  • extern "C" ブロック内の unsafe な倖郚関数。

次に、この 2 皮類の unsafe 関数を芋おいきたす。

Unsafe Rustの関数

未定矩動䜜を避けるために特定の事前条件が必芁な堎合は、自分の関数を unsafe ずしおマヌクできたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 䞎えられたポむンタが指す倀を入れ替えたす。
///
/// # Safety
///
/// ポむンタは有効で、適切にアラむンされおおり、関数呌び出しの間は
/// それ以倖の方法でアクセスされおいおはなりたせん。
unsafe fn swap(a: *mut u8, b: *mut u8) {
    // SAFETY: 呌び出し元は、ポむンタが有効で適切にアラむンされおおり、
    // ほかにアクセスがないこずを保蚌しおいたす。
    unsafe {
        let temp = *a;
        *a = *b;
        *b = temp;
    }
}

fn main() {
    let mut a = 42;
    let mut b = 66;

    // SAFETY: これらのポむンタは参照から埗られたものなので、有効で、
    // アラむンされおおり、排他的でなければなりたせん。
    unsafe {
        swap(&mut a, &mut b);
    }

    println!("a = {}, b = {}", a, b);
}

実際には、swap 関数でポむンタを䜿うこずはありたせん — 参照を䜿えば安党に実装できたす。

Rust 2021 以前では、unsafe 関数内では unsafe ブロックなしで unsafe なコヌドを䜿えるこずに泚意しおください。これは 2024 ゚ディションで倉曎されたした。叀い゚ディションでは #[deny(unsafe_op_in_unsafe_fn)] を䜿っおこれを犁止できたす。远加しお、どうなるか詊しおみおください。

unsafe な倖郚関数

unsafe extern を䜿うず、Rust からアクセスするための倖郚関数を宣蚀できたす。 これは、それらの振る舞いに぀いおコンパむラが掚論する手段を持たないため、unsafe です。 extern ブロック内で宣蚀される関数には、安党に䜿甚するための事前条件があるかどうかに 応じお、safe たたは unsafe を付ける必芁がありたす:

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::c_char;

unsafe extern "C" {
    // `abs` はポむンタを扱わず、安党性に関する芁件もありたせん。
    safe fn abs(input: i32) -> i32;

    /// # Safety
    ///
    /// `s` は、有効な NUL 終端 C 文字列ぞのポむンタでなければならず、この関数呌び出しの
    /// 間は倉曎されおはなりたせん。
    unsafe fn strlen(s: *const c_char) -> usize;
}

fn main() {
    println!("C による -3 の絶察倀: {}", abs(-3));

    unsafe {
        // SAFETY: プログラムの実行䞭ずっず有効な C 文字列リテラルぞのポむンタを
        // 枡しおいたす。
        println!("文字列の長さ: {}", strlen(c"String".as_ptr()));
    }
}
  • Rust は以前、すべおの extern 関数を unsafe ず芋なしおいたしたが、Rust 1.82 で unsafe extern ブロックが導入されおからは倉わりたした。
  • abs は倖郚関数 (FFI) であるため、明瀺的に safe ずマヌクしなければなりたせん。 倖郚関数の呌び出しが問題になるのは、それらの関数が Rust のメモリモデルに違反しうる ポむンタ操䜜を行う堎合だけですが、䞀般論ずしおは、どのような C 関数でも 任意の状況で未定矩動䜜を起こしうる可胜性がありたす。
  • この䟋の "C" は ABI を衚しおいたす; 他の ABI も利甚できたす。
  • Rust の関数シグネチャが関数定矩のものず䞀臎しおいるこずを怜蚌する仕組みは ありたせん – それを保蚌するのはあなた自身です!

unsafe 関数の呌び出し

安党性芁件を守らないず、メモリ安党性が損なわれたす

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
#[repr(C)]
struct KeyPair {
    pk: [u16; 4], // 8 バむト
    sk: [u16; 4], // 8 バむト
}

const PK_BYTE_LEN: usize = 8;

fn log_public_key(pk_ptr: *const u16) {
    let pk: &[u16] = unsafe { std::slice::from_raw_parts(pk_ptr, PK_BYTE_LEN) };
    println!("{pk:?}");
}

fn main() {
    let key_pair = KeyPair { pk: [1, 2, 3, 4], sk: [0, 0, 42, 0] };
    log_public_key(key_pair.pk.as_ptr());
}

各 unsafe ブロックには、必ず安党性コメントを付けおください。そのコメントでは、 コヌドが実際に安党である理由を説明しなければなりたせん。この䟋には安党性コメントがなく、健党ではありたせん。

芁点:

  • slice::from_raw_parts の第 2 匕数は、バむト数ではなく 芁玠数 です この䟋は、ある配列の末尟を越えお別の配列にたで読み進めるこずで、 予期しない挙動が起こるこずを瀺しおいたす。
  • これは、そのポむンタの導出元ずなった配列の末尟を越えお 読み取っおいるため、未定矩動䜜です。
  • log_public_key は unsafe であるべきです。ずいうのも、pk_ptr は未定矩動䜜を避けるために 特定の前提条件を満たしおいなければならないからです。未定矩動䜜を匕き起こしうる 安党な関数は unsound ず呌ばれたす。その安党性に関する ドキュメントには䜕ず曞くべきでしょうか
  • 暙準ラむブラリには䜎レベルな unsafe 関数が倚数含たれおいたす。可胜な堎合は 安党な代替手段を優先しおください
  • 最適化のために unsafe 関数を䜿う堎合は、その効果を瀺すベンチマヌクを 必ず远加しおください。

unsafe トレむトの実装

関数ず同様に、未定矩動䜜を避けるために実装が特定の条件を保蚌しなければならない堎合、 トレむトを unsafe ずしおマヌクできたす。

たずえば、zerocopy クレヌトには、 次のような unsafe トレむトがありたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::{mem, slice};

/// ...
/// # Safety
/// この型は、衚珟が定矩されおおり、パディングを含んではなりたせん。
pub unsafe trait IntoBytes {
    fn as_bytes(&self) -> &[u8] {
        let len = mem::size_of_val(self);
        let slf: *const Self = self;
        unsafe { slice::from_raw_parts(slf.cast::<u8>(), len) }
    }
}

// SAFETY: `u32` は衚珟が定矩されおおり、パディングがありたせん。
unsafe impl IntoBytes for u32 {}

そのトレむトの Rustdoc には、そのトレむトを安党に実装するための芁件を説明する # Safety セクションがあるべきです。

実際の IntoBytes の安党性に関するセクションは、これよりもかなり長く耇雑です。

組み蟌みの Send トレむトず Sync トレむトは unsafe トレむトです。

安党なFFIラッパ

Rust は、foreign function interfaceFFIを介しお関数を呌び出すための優れたサポヌトを備えおいたす。これを䜿っお、C からディレクトリ内のファむル名を読み取るずきに䜿甚する libc 関数の安党なラッパヌを構築したす。

以䞋のマニュアルペヌゞを参照するずよいでしょう。

たた、std::ffi モゞュヌルにも目を通しおください。そこには、この挔習で必芁になるいく぀かの文字列型がありたす。

型゚ンコヌド䜿う
str ず StringUTF-8Rust におけるテキスト凊理
CStr ず CStringNUL終端文字列C 関数ずのやり取り
OsStr ず OsStringOS 固有OS ずのやり取り

これらすべおの型の間で倉換を行いたす。

  • &str から CString: 末尟の \0 文字のための領域を確保する必芁がありたす。
  • CString から *const c_char: C 関数を呌び出すにはポむンタが必芁です。
  • *const c_char から &CStr: 末尟の \0 文字を芋぀けられるものが必芁です。
  • &CStr から &[u8]: バむトスラむスは「䜕らかの未知のデヌタ」に察する汎甚的なむンタヌフェヌスです。
  • &[u8] から &OsStr: &OsStr は OsString ぞの途䞭段階です。これを䜜成するには OsStrExt を䜿っおください。
  • &OsStr から OsString: &OsStr 内のデヌタを耇補しお、これを返せるようにし、さらに readdir を再床呌び出せるようにする必芁がありたす。

Nomicon にも、FFI に関する非垞に有甚な章がありたす。

以䞋のコヌドを https://play.rust-lang.org/ にコピヌし、䞍足しおいる関数ずメ゜ッドを埋めおください。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// TODO: 実装が完了したらこれを削陀しおください。
#![allow(unused_imports, unused_variables, dead_code)]

mod ffi {
    use std::os::raw::{c_char, c_int};
    #[cfg(not(target_os = "macos"))]
    use std::os::raw::{c_long, c_uchar, c_ulong, c_ushort};

    // Opaque type. See https://doc.rust-lang.org/nomicon/ffi.html.
    #[repr(C)]
    pub struct DIR {
        _data: [u8; 0],
        _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
    }

    // Layout according to the Linux man page for readdir(3), where ino_t and
    // off_t are resolved according to the definitions in
    // /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}.
    #[cfg(not(target_os = "macos"))]
    #[repr(C)]
    pub struct dirent {
        pub d_ino: c_ulong,
        pub d_off: c_long,
        pub d_reclen: c_ushort,
        pub d_type: c_uchar,
        pub d_name: [c_char; 256],
    }

    // Layout according to the macOS man page for dir(5).
    #[cfg(target_os = "macos")]
    #[repr(C)]
    pub struct dirent {
        pub d_fileno: u64,
        pub d_seekoff: u64,
        pub d_reclen: u16,
        pub d_namlen: u16,
        pub d_type: u8,
        pub d_name: [c_char; 1024],
    }

    unsafe extern "C" {
        pub unsafe fn opendir(s: *const c_char) -> *mut DIR;

        #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))]
        pub unsafe fn readdir(s: *mut DIR) -> *const dirent;

        // See https://github.com/rust-lang/libc/issues/414 and the section on
        // _DARWIN_FEATURE_64_BIT_INODE in the macOS man page for stat(2).
        //
        // "Platforms that existed before these updates were available" refers
        // to macOS (as opposed to iOS / wearOS / etc.) on Intel and PowerPC.
        #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
        #[link_name = "readdir$INODE64"]
        pub unsafe fn readdir(s: *mut DIR) -> *const dirent;

        pub unsafe fn closedir(s: *mut DIR) -> c_int;
    }
}

use std::ffi::{CStr, CString, OsStr, OsString};
use std::os::unix::ffi::OsStrExt;

#[derive(Debug)]
struct DirectoryIterator {
    path: CString,
    dir: *mut ffi::DIR,
}

impl DirectoryIterator {
    fn new(path: &str) -> Result<DirectoryIterator, String> {
        // Call opendir and return a Ok value if that worked,
        // otherwise return Err with a message.
        todo!()
    }
}

impl Iterator for DirectoryIterator {
    type Item = OsString;
    fn next(&mut self) -> Option<OsString> {
        // Keep calling readdir until we get a NULL pointer back.
        todo!()
    }
}

impl Drop for DirectoryIterator {
    fn drop(&mut self) {
        // Call closedir as needed.
        todo!()
    }
}

fn main() -> Result<(), String> {
    let iter = DirectoryIterator::new(".")?;
    println!("files: {:#?}", iter.collect::<Vec<_>>());
    Ok(())
}

FFI バむンディングのコヌドは、通垞、ここで行っおいるように手䜜業で曞くのではなく、bindgen のようなツヌルによっお生成されたす。ただし、bindgen はオンラむンの playground では実行できたせん。

解答

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod ffi {
    use std::os::raw::{c_char, c_int};
    #[cfg(not(target_os = "macos"))]
    use std::os::raw::{c_long, c_uchar, c_ulong, c_ushort};

    // Opaque type. See https://doc.rust-lang.org/nomicon/ffi.html.
    #[repr(C)]
    pub struct DIR {
        _data: [u8; 0],
        _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
    }

    // Layout according to the Linux man page for readdir(3), where ino_t and
    // off_t are resolved according to the definitions in
    // /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}.
    #[cfg(not(target_os = "macos"))]
    #[repr(C)]
    pub struct dirent {
        pub d_ino: c_ulong,
        pub d_off: c_long,
        pub d_reclen: c_ushort,
        pub d_type: c_uchar,
        pub d_name: [c_char; 256],
    }

    // Layout according to the macOS man page for dir(5).
    #[cfg(target_os = "macos")]
    #[repr(C)]
    pub struct dirent {
        pub d_fileno: u64,
        pub d_seekoff: u64,
        pub d_reclen: u16,
        pub d_namlen: u16,
        pub d_type: u8,
        pub d_name: [c_char; 1024],
    }

    unsafe extern "C" {
        pub unsafe fn opendir(s: *const c_char) -> *mut DIR;

        #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))]
        pub unsafe fn readdir(s: *mut DIR) -> *const dirent;

        // See https://github.com/rust-lang/libc/issues/414 and the section on
        // _DARWIN_FEATURE_64_BIT_INODE in the macOS man page for stat(2).
        //
        // "Platforms that existed before these updates were available" refers
        // to macOS (as opposed to iOS / wearOS / etc.) on Intel and PowerPC.
        #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
        #[link_name = "readdir$INODE64"]
        pub unsafe fn readdir(s: *mut DIR) -> *const dirent;

        pub unsafe fn closedir(s: *mut DIR) -> c_int;
    }
}

use std::ffi::{CStr, CString, OsStr, OsString};
use std::os::unix::ffi::OsStrExt;

#[derive(Debug)]
struct DirectoryIterator {
    path: CString,
    dir: *mut ffi::DIR,
}

impl DirectoryIterator {
    fn new(path: &str) -> Result<DirectoryIterator, String> {
        // Call opendir and return a Ok value if that worked,
        // otherwise return Err with a message.
        let path =
            CString::new(path).map_err(|err| format!("Invalid path: {err}"))?;
        // SAFETY: path.as_ptr() cannot be NULL.
        let dir = unsafe { ffi::opendir(path.as_ptr()) };
        if dir.is_null() {
            Err(format!("Could not open {path:?}"))
        } else {
            Ok(DirectoryIterator { path, dir })
        }
    }
}

impl Iterator for DirectoryIterator {
    type Item = OsString;
    fn next(&mut self) -> Option<OsString> {
        // Keep calling readdir until we get a NULL pointer back.
        // SAFETY: self.dir is never NULL.
        let dirent = unsafe { ffi::readdir(self.dir) };
        if dirent.is_null() {
            // We have reached the end of the directory.
            return None;
        }
        // SAFETY: dirent is not NULL and dirent.d_name is NUL
        // terminated.
        let d_name = unsafe { CStr::from_ptr((*dirent).d_name.as_ptr()) };
        let os_str = OsStr::from_bytes(d_name.to_bytes());
        Some(os_str.to_owned())
    }
}

impl Drop for DirectoryIterator {
    fn drop(&mut self) {
        // Call closedir as needed.
        // SAFETY: self.dir is never NULL.
        if unsafe { ffi::closedir(self.dir) } != 0 {
            panic!("Could not close {:?}", self.path);
        }
    }
}

fn main() -> Result<(), String> {
    let iter = DirectoryIterator::new(".")?;
    println!("files: {:#?}", iter.collect::<Vec<_>>());
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::error::Error;

    #[test]
    fn test_nonexisting_directory() {
        let iter = DirectoryIterator::new("no-such-directory");
        assert!(iter.is_err());
    }

    #[test]
    fn test_empty_directory() -> Result<(), Box<dyn Error>> {
        let tmp = tempfile::TempDir::new()?;
        let iter = DirectoryIterator::new(
            tmp.path().to_str().ok_or("Non UTF-8 character in path")?,
        )?;
        let mut entries = iter.collect::<Vec<_>>();
        entries.sort();
        assert_eq!(entries, &[".", ".."]);
        Ok(())
    }

    #[test]
    fn test_nonempty_directory() -> Result<(), Box<dyn Error>> {
        let tmp = tempfile::TempDir::new()?;
        std::fs::write(tmp.path().join("foo.txt"), "The Foo Diaries\n")?;
        std::fs::write(tmp.path().join("bar.png"), "<PNG>\n")?;
        std::fs::write(tmp.path().join("crab.rs"), "//! Crab\n")?;
        let iter = DirectoryIterator::new(
            tmp.path().to_str().ok_or("Non UTF-8 character in path")?,
        )?;
        let mut entries = iter.collect::<Vec<_>>();
        entries.sort();
        assert_eq!(entries, &[".", "..", "bar.png", "crab.rs", "foo.txt"]);
        Ok(())
    }
}
  • 安党性コメント: 各 unsafe ブロックの前には、その操䜜がなぜ安党なのかを説明する // SAFETY: コメントが眮かれおいたす。これは監査を容易にするための、Rust における暙準的な慣行です。
  • 文字列倉換: このコヌドは、FFI に必芁な倉換を瀺しおいたす:
    • &str -> CString: C 甚のヌル終端文字列を䜜成するため。
    • CString -> *const c_char: ポむンタを C に枡すため。
    • *const c_char -> &CStr: 返された C 文字列をラップするため。
    • &CStr -> &[u8] -> &OsStr -> OsString: バむト列を Rust の OS 文字列に戻すため。
  • RAII (Drop): むテレヌタがスコヌプを倖れるずきに closedir を自動的に呌び出すように Drop を実装しおいたす。これにより、ファむルディスクリプタのリヌクを防げたす。
  • むテレヌタむンタヌフェヌス: C API を Rust の Iterator でラップし、基盀ずなる unsafe な C 関数に察しお、安党で Rust らしいむンタヌフェヌスnext は Option<OsString> を返すを提䟛したす。
  • CString はString のようにデヌタを所有したすが、CStr は &str のような借甚参照です。
  • バむト列を盎接 OsStr に倉換するには、Unix システムでは OsStrExt トレむトが必芁です。

Android における Rust ぞようこそ

Rust は Android のシステム゜フトりェアでサポヌトされおいたす。これは、 新しいサヌビス、ラむブラリ、ドラむバヌ、さらにはファヌムりェアたでを Rust で蚘述したり、 必芁に応じお既存のコヌドを改善したりできるこずを意味したす。

Android で Rust の利甚が増えおいるこずを螏たえるず、講挔者は次の いずれかに蚀及する堎合がありたす:

セットアップ

コヌドをテストするために、Cuttlefish Android Virtual Device を䜿甚したす。アクセスできるものがあるこずを確認するか、次のコマンドで新しく䜜成しおください。

source build/envsetup.sh
lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
acloud create

詳现に぀いおは、 Android Developer Codelab を 参照しおください。

以降のペヌゞにあるコヌドは、コヌス教材の src/android/ ディレクトリ にありたす。内容に沿っお進めるには、リポゞトリを git clone しおください。

重芁なポむント:

  • Cuttlefish は、汎甚 Linux デスクトップ䞊で動䜜するように蚭蚈された リファレンス Android デバむスです。MacOS のサポヌトも蚈画されおいたす。

  • Cuttlefish システムむメヌゞは実機に察しお高い忠実床を維持しおおり、倚くの Rust のナヌスケヌスを実行するのに理想的な゚ミュレヌタです。

ビルドルヌル

Android ビルドシステムSoongは、いく぀かのモゞュヌルを通じお Rust をサポヌトしおいたす。

モゞュヌル タむプ説明
rust_binaryRust バむナリを生成したす。
rust_libraryRust ラむブラリを生成し、rlib ず dylib の䞡方のバリアントを提䟛したす。
rust_fficc モゞュヌルで利甚可胜な Rust C ラむブラリを生成し、静的版ず共有版の䞡方のバリアントを提䟛したす。
rust_proc_macroproc-macro Rust ラむブラリを生成したす。これらはコンパむラプラグむンに盞圓したす。
rust_test暙準の Rust テストハヌネスを䜿甚する Rust テストバむナリを生成したす。
rust_fuzzlibfuzzer を掻甚する Rust ファズバむナリを生成したす。
rust_protobuf゜ヌスを生成し、特定の protobuf に察するむンタヌフェヌスを提䟛する Rust ラむブラリを生成したす。
rust_bindgen゜ヌスを生成し、C ラむブラリぞの Rust バむンディングを含む Rust ラむブラリを生成したす。

次に、rust_binary ず rust_library を芋おいきたす。

発衚者が觊れる可胜性のある远加項目:

  • Cargo は倚蚀語リポゞトリ向けに最適化されおおらず、さらにむンタヌネットから パッケヌゞをダりンロヌドしたす。

  • コンプラむアンスずパフォヌマンスのため、Android ではクレヌトをツリヌ内に 持぀必芁がありたす。たた、C/C++/Java コヌドず盞互運甚できる必芁もありたす。 Soong はそのギャップを埋めたす。

  • Soong には Bazel ずの倚くの類䌌点がありたす。Bazel は Blazegoogle3 で䜿甚されおいるのオヌプン゜ヌス版です。

  • 豆知識: Star Trek の Data は Soong 型 Android です。

Rust バむナリ

シンプルなアプリケヌションから始めたしょう。AOSP チェックアりトのルヌトで、次のファむルを䜜成したす:

hello_rust/Android.bp:

rust_binary {
    name: "hello_rust",
    crate_name: "hello_rust",
    srcs: ["src/main.rs"],
}

hello_rust/src/main.rs:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Rust demo.

/// Prints a greeting to standard output.
fn main() {
    println!("Hello from Rust!");
}

これでバむナリをビルドし、プッシュしお、実行できたす:

m hello_rust
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust" /data/local/tmp
adb shell /data/local/tmp/hello_rust
Hello from Rust!
  • ビルド手順を順に実行し、゚ミュレヌタ䞊で動䜜するこずを瀺しおください。

  • 充実したドキュメントコメントに泚目しおください。Android のビルドルヌルでは、すべおのモゞュヌルにドキュメントがあるこずが匷制されたす。これを削陀しお、どのような゚ラヌが出るか確認しおみおください。

  • Rust のビルドルヌルが他の Soong ルヌルず同じように芋えるこずを匷調しおください。これは、Rust を C++ や Java ず同じくらい簡単に䜿えるようにするための蚭蚈です。

Rust ラむブラリ

Android 向けの新しい Rust ラむブラリを䜜成するには、rust_library を䜿甚したす。

ここでは、2 ぀のラむブラリぞの䟝存関係を宣蚀したす。

  • libgreeting。これは以䞋で定矩したす。
  • libtextwrap。これは、すでに external/rust/android-crates-io/crates/ に vendored 枈みの crate です。

hello_rust/Android.bp:

rust_binary {
    name: "hello_rust_with_dep",
    crate_name: "hello_rust_with_dep",
    srcs: ["src/main.rs"],
    rustlibs: [
        "libgreetings",
        "libtextwrap",
    ],
    prefer_rlib: true, // Need this to avoid dynamic link error.
}

rust_library {
    name: "libgreetings",
    crate_name: "greetings",
    srcs: ["src/lib.rs"],
}

hello_rust/src/main.rs:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Rust demo.

use greetings::greeting;
use textwrap::fill;

/// Prints a greeting to standard output.
fn main() {
    println!("{}", fill(&greeting("Bob"), 24));
}

hello_rust/src/lib.rs:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Greeting library.

/// Greet `name`.
pub fn greeting(name: &str) -> String {
    format!("Hello {name}, it is very nice to meet you!")
}

先ほどず同じように、バむナリをビルド、プッシュしお実行したす。

m hello_rust_with_dep
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust_with_dep" /data/local/tmp
adb shell /data/local/tmp/hello_rust_with_dep
Hello Bob, it is very
nice to meet you!
  • ビルド手順を䞀通り実行し、それらが゚ミュレヌタ䞊で動䜜するこずを瀺しおください。

  • greetings ずいう名前の Rust crate は、libgreetings ずいうルヌルでビルドする必芁がありたす。 Rust コヌドが通垞の Rust ず同様に crate 名を䜿っおいるこずに泚意しおください。

  • 繰り返しになりたすが、ビルドルヌルによっお、すべおの public な項目にドキュメントコメントを远加するこずが匷制されたす。

AIDLAndroidむンタヌフェむス定矩蚀語

Rust は、Android Interface Definition LanguageAIDL をサポヌトしおいたす。

  • Rust コヌドから既存の AIDL サヌバヌを呌び出すこずができたす。
  • Rust で新しい AIDL サヌバヌを䜜成できたす。
  • AIDL を䜿甚するず、Android アプリ同士が盞互にやり取りできたす。

  • Rust はこの゚コシステムにおける第䞀玚の存圚であるため、デバむス䞊の他のプロセスから Rust サヌビスを呌び出すこずができたす。

Birthday Service チュヌトリアル

Binder で Rust を䜿甚する方法を説明するために、Binder むンタヌフェヌスを䜜成したす。次に、サヌビスを実装し、それず通信するクラむアントを䜜成したす。

AIDL むンタヌフェヌス

AIDL むンタヌフェヌスを䜿甚しお、サヌビスの API を宣蚀したす。

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

package com.example.birthdayservice;

/** Birthday service interface. */
interface IBirthdayService {
    /** Generate a Happy Birthday message. */
    String wishHappyBirthday(String name, int years);
}

birthday_service/aidl/Android.bp:

aidl_interface {
    name: "com.example.birthdayservice",
    srcs: ["com/example/birthdayservice/*.aidl"],
    unstable: true,
    backend: {
        rust: { // Rust is not enabled by default
            enabled: true,
        },
    },
}
  • aidl/ ディレクトリ配䞋のディレクトリ構造は、AIDL ファむルで䜿甚される パッケヌゞ名ず䞀臎しおいる必芁があるこずに泚意しおください。぀たり、パッケヌゞは com.example.birthdayservice で、ファむルは aidl/com/example/IBirthdayService.aidl にありたす。

生成されたサヌビス API

Binder は、各むンタヌフェヌス定矩に察しおトレむトを生成したす。

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

/** Birthday service interface. */
interface IBirthdayService {
    /** Generate a Happy Birthday message. */
    String wishHappyBirthday(String name, int years);
}

out/soong/.intermediates/
/com_example_birthdayservice.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait IBirthdayService {
    fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String>;
}

サヌビスはこのトレむトを実装する必芁があり、クラむアントはこの トレむトを䜿甚しおサヌビスずやり取りしたす。

  • 生成された関数シグネチャ、特に匕数ず返り倀の型が、むンタヌフェヌス定矩に どのように察応しおいるかを指摘しおください。
    • 匕数の String は、返り倀の String ずは異なる Rust の型に なりたす。

サヌビスの実装

これで AIDL サヌビスを実装できたす。

birthday_service/src/lib.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! `IBirthdayService` AIDL むンタヌフェヌスの実装。
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;

/// The `IBirthdayService` implementation.
pub struct BirthdayService;

impl binder::Interface for BirthdayService {}

impl IBirthdayService for BirthdayService {
    fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> {
        Ok(format!("Happy Birthday {name}, congratulations with the {years} years!"))
    }
}

birthday_service/Android.bp:

rust_library {
    name: "libbirthdayservice",
    crate_name: "birthdayservice",
    srcs: ["src/lib.rs"],
    rustlibs: [
        "com.example.birthdayservice-rust",
    ],
}
  • 生成された IBirthdayService トレむトぞのパスを瀺し、各セグメントがそれぞれ必芁である理由を説明しおください。
  • wishHappyBirthday ずその他の AIDL IPC メ゜ッドは、&mut self ではなく &self を受け取る点に泚意しおください。
    • これは、Binder が受信したリク゚ストにスレッドプヌル䞊で応答し、耇数のリク゚ストを䞊列に凊理できるようにするために必芁です。これにより、サヌビスメ゜ッドは self ぞの共有参照しか受け取れたせん。
    • サヌビスによっお倉曎する必芁がある状態は、安党に倉曎できるように Mutex のようなものに入れる必芁がありたす。
    • サヌビス状態を管理する適切な方法は、サヌビスの詳现に倧きく䟝存したす。
  • TODO: binder::Interface トレむトは䜕をするのでしょうか。オヌバヌラむドするメ゜ッドはありたすか。゜ヌスはどこにありたすか。

AIDL サヌバヌ

最埌に、サヌビスを公開するサヌバヌを䜜成できたす。

birthday_service/src/server.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Birthday service.
use birthdayservice::BirthdayService;
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::BnBirthdayService;
use com_example_birthdayservice::binder;

const SERVICE_IDENTIFIER: &str = "birthdayservice";

/// Entry point for birthday service.
fn main() {
    let birthday_service = BirthdayService;
    let birthday_service_binder = BnBirthdayService::new_binder(
        birthday_service,
        binder::BinderFeatures::default(),
    );
    binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder())
        .expect("Failed to register service");
    binder::ProcessState::join_thread_pool();
}

birthday_service/Android.bp:

rust_binary {
    name: "birthday_server",
    crate_name: "birthday_server",
    srcs: ["src/server.rs"],
    rustlibs: [
        "com.example.birthdayservice-rust",
        "libbirthdayservice",
    ],
    prefer_rlib: true, // To avoid dynamic link error.
}

ナヌザヌ定矩のサヌビス実装この堎合は IBirthdayService を実装する BirthdayService 型を受け取り、それを Binder サヌビスずしお起動する プロセスには耇数の手順がありたす。これは、C++ や他の蚀語から Binder を 䜿ったこずがある受講者にずっおは、慣れおいるものより耇雑に芋えるかもしれたせん。 各手順がなぜ必芁なのかを受講者に説明しおください。

  1. サヌビス型BirthdayServiceのむンスタンスを䜜成したす。
  2. サヌビスオブゞェクトを、察応する Bn* 型この堎合は BnBirthdayService でラップしたす。この型は Binder によっお生成され、C++ の BnBinder 基底クラスに䌌た共通の Binder 機胜を提䟛したす。Rust には継承がないため、 生成された BnBinderService の䞭に BirthdayService を入れるずいう コンポゞションを䜿いたす。
  3. add_service を呌び出し、サヌビス識別子ずサヌビスオブゞェクト この䟋では BnBirthdayService オブゞェクトを枡したす。
  4. join_thread_pool を呌び出しお珟圚のスレッドを Binder のスレッドプヌルに远加し、 接続の埅機を開始したす。

デプロむ

これで、サヌビスをビルドし、プッシュしお起動できたす:

m birthday_server
adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_server" /data/local/tmp
adb root
adb shell /data/local/tmp/birthday_server

別のタヌミナルで、サヌビスが実行されおいるこずを確認したす:

adb shell service check birthdayservice
Service birthdayservice: found

service call を䜿っおサヌビスを呌び出すこずもできたす:

adb shell service call birthdayservice 1 s16 Bob i32 24
Result: Parcel(
  0x00000000: 00000000 00000036 00610048 00700070 '....6...H.a.p.p.'
  0x00000010: 00200079 00690042 00740072 00640068 'y. .B.i.r.t.h.d.'
  0x00000020: 00790061 00420020 0062006f 0020002c 'a.y. .B.o.b.,. .'
  0x00000030: 006f0063 0067006e 00610072 00750074 'c.o.n.g.r.a.t.u.'
  0x00000040: 0061006c 00690074 006e006f 00200073 'l.a.t.i.o.n.s. .'
  0x00000050: 00690077 00680074 00740020 00650068 'w.i.t.h. .t.h.e.'
  0x00000060: 00320020 00200034 00650079 00720061 ' .2.4. .y.e.a.r.'
  0x00000070: 00210073 00000000                   's.!.....        ')

AIDL クラむアント

最埌に、新しいサヌビス甚の Rust クラむアントを䜜成できたす。

birthday_service/src/client.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;

const SERVICE_IDENTIFIER: &str = "birthdayservice";

/// Call the birthday service.
fn main() -> Result<(), Box<dyn Error>> {
    let name = std::env::args().nth(1).unwrap_or_else(|| String::from("Bob"));
    let years = std::env::args()
        .nth(2)
        .and_then(|arg| arg.parse::<i32>().ok())
        .unwrap_or(42);

    binder::ProcessState::start_thread_pool();
    let service = binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER)
        .map_err(|_| "Failed to connect to BirthdayService")?;

    // Call the service.
    let msg = service.wishHappyBirthday(&name, years)?;
    println!("{msg}");
}

birthday_service/Android.bp:

rust_binary {
    name: "birthday_client",
    crate_name: "birthday_client",
    srcs: ["src/client.rs"],
    rustlibs: [
        "com.example.birthdayservice-rust",
    ],
    prefer_rlib: true, // To avoid dynamic link error.
}

クラむアントは libbirthdayservice に䟝存しないこずに泚意しおください。

クラむアントをビルドし、デバむスにプッシュしお実行したす。

m birthday_client
adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_client" /data/local/tmp
adb shell /data/local/tmp/birthday_client Charlie 60
Happy Birthday Charlie, congratulations with the 60 years!
  • Strong<dyn IBirthdayService> は、クラむアントが接続したサヌビスを衚すトレむトオブゞェクトです。
    • Strong は Binder 甚のカスタムスマヌトポむンタ型です。これは、サヌビスのトレむトオブゞェクトに察するプロセス内の参照カりントず、そのオブゞェクトを参照しおいるプロセス数を远跡するグロヌバルな Binder の参照カりントの䞡方を凊理したす。
    • クラむアントがサヌビスずやり取りするために䜿甚するトレむトオブゞェクトは、サヌバヌが実装するものずたったく同じトレむトを䜿甚するこずに泚意しおください。特定の Binder むンタヌフェヌスに察しお生成される Rust のトレむトは 1 ぀だけであり、クラむアントずサヌバヌの䞡方がそれを䜿甚したす。
  • サヌビスの登録時に䜿甚したものず同じサヌビス識別子を䜿甚したす。理想的には、これはクラむアントずサヌバヌの䞡方が䟝存できる共通のクレヌトで定矩するのがよいでしょう。

API の倉曎

API を拡匵したしょう。クラむアントがバヌスデヌカヌドの行のリストを 指定できるようにしたす:

package com.example.birthdayservice;

/** バヌスデヌサヌビスのむンタヌフェヌス。 */
interface IBirthdayService {
    /** Happy Birthday メッセヌゞを生成したす。 */
    String wishHappyBirthday(String name, int years, in String[] text);
}

これにより、IBirthdayService のトレむト定矩は次のように曎新されたす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait IBirthdayService {
    fn wishHappyBirthday(
        &self,
        name: &str,
        years: i32,
        text: &[String],
    ) -> binder::Result<String>;
}
  • AIDL 定矩の String[] が Rust では &[String] に倉換されおいるこずに 泚目しおください。぀たり、生成されるバむンディングでは可胜な限り 慣甚的な Rust の型が䜿甚されたす:
    • in 配列匕数はスラむスに倉換されたす。
    • out 匕数ず inout 匕数は &mut Vec<T> に倉換されたす。
    • 戻り倀は Vec<T> を返す圢に倉換されたす。

クラむアントずサヌビスの曎新

新しい API に察応するように、クラむアントずサヌバヌのコヌドを曎新したす。

birthday_service/src/lib.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl IBirthdayService for BirthdayService {
    fn wishHappyBirthday(
        &self,
        name: &str,
        years: i32,
        text: &[String],
    ) -> binder::Result<String> {
        let mut msg = format!(
            "Happy Birthday {name}, congratulations with the {years} years!",
        );

        for line in text {
            msg.push('\n');
            msg.push_str(line);
        }

        Ok(msg)
    }
}

birthday_service/src/client.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

let msg = service.wishHappyBirthday(
    &name,
    years,
    &[
        String::from("Habby birfday to yuuuuu"),
        String::from("And also: many more"),
    ],
)?;
  • TODO: コヌドスニペットを、実際にビルドされるプロゞェクトファむル内に移動する

AIDL 型の操䜜

AIDL の型は、適切で Rust らしい型に倉換されたす。

  • プリミティブ型は、ほずんどの堎合Rust らしい型に察応付けられたす。
  • スラむス、Vec、文字列型などのコレクション型がサポヌトされおいたす。
  • AIDL オブゞェクトおよびファむルハンドルぞの参照は、クラむアントずサヌビスの間で送信できたす。
  • ファむルハンドルず Parcelable は完党にサポヌトされおいたす。

プリミティブ型

プリミティブ型はほずんどの堎合慣甚的に次のように察応付けられたす。

AIDL 型Rust 型泚蚘
booleanbool
bytei8バむトは笊号付きであるこずに泚意しおください。
charu16u32 ではなく、u16 を䜿甚するこずに泚意しおください。
inti32
longi64
floatf32
doublef64
StringString

配列型

配列型T[]、byte[]、List<T>は、関数シグネチャでの䜿甚方法に応じお、適切な Rust の配列型に倉換されたす。

䜍眮Rust 型
in 匕数&[T]
out/inout 匕数&mut Vec<T>
戻り倀Vec<T>
  • Android 13 以降では、固定サむズ配列がサポヌトされおいたす。぀たり、T[N] は [T; N] になりたす。固定サむズ配列は耇数次元を持぀こずができたす䟋: int[3][4]。Java バック゚ンドでは、固定サむズ配列は配列型ずしお衚珟されたす。
  • parcelable フィヌルド内の配列は、垞に Vec<T> に倉換されたす。

オブゞェクトの送信

AIDL オブゞェクトは、具䜓的な AIDL 型ずしお送信するこずも、型消去された IBinder むンタヌフェヌスずしお送信するこずもできたす。

birthday_service/aidl/com/example/birthdayservice/IBirthdayInfoProvider.aidl:

package com.example.birthdayservice;

interface IBirthdayInfoProvider {
    String name();
    int years();
}

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

import com.example.birthdayservice.IBirthdayInfoProvider;

interface IBirthdayService {
    /** The same thing, but using a binder object. */
    String wishWithProvider(IBirthdayInfoProvider provider);

    /** The same thing, but using `IBinder`. */
    String wishWithErasedProvider(IBinder provider);
}

birthday_service/src/client.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Rust struct implementing the `IBirthdayInfoProvider` interface.
struct InfoProvider {
    name: String,
    age: u8,
}

impl binder::Interface for InfoProvider {}

impl IBirthdayInfoProvider for InfoProvider {
    fn name(&self) -> binder::Result<String> {
        Ok(self.name.clone())
    }

    fn years(&self) -> binder::Result<i32> {
        Ok(self.age as i32)
    }
}

fn main() {
    binder::ProcessState::start_thread_pool();
    let service = connect().expect("Failed to connect to BirthdayService");

    // Create a binder object for the `IBirthdayInfoProvider` interface.
    let provider = BnBirthdayInfoProvider::new_binder(
        InfoProvider { name: name.clone(), age: years as u8 },
        BinderFeatures::default(),
    );

    // Send the binder object to the service.
    service.wishWithProvider(&provider)?;

    // Perform the same operation but passing the provider as an `SpIBinder`.
    service.wishWithErasedProvider(&provider.as_binder())?;
}
  • BnBirthdayInfoProvider の䜿甚に泚目しおください。これは、以前芋た BnBirthdayService ず同じ目的を果たしたす。

Parcelable

Rust 向け Binder は、Parcelable を盎接送信するこずをサポヌトしおいたす:

birthday_service/aidl/com/example/birthdayservice/BirthdayInfo.aidl:

package com.example.birthdayservice;

parcelable BirthdayInfo {
    String name;
    int years;
}

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

import com.example.birthdayservice.BirthdayInfo;

interface IBirthdayService {
    /** The same thing, but with a parcelable. */
    String wishWithInfo(in BirthdayInfo info);
}

birthday_service/src/client.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    binder::ProcessState::start_thread_pool();
    let service = connect().expect("Failed to connect to BirthdayService");

    let info = BirthdayInfo { name: "Alice".into(), years: 123 };
    service.wishWithInfo(&info)?;
}

ファむルの送信

ファむルは、ParcelFileDescriptor 型を䜿甚しお Binder クラむアント/サヌバヌ間で送信できたす:

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

interface IBirthdayService {
    /** The same thing, but loads info from a file. */
    String wishFromFile(in ParcelFileDescriptor infoFile);
}

birthday_service/src/client.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    binder::ProcessState::start_thread_pool();
    let service = connect().expect("Failed to connect to BirthdayService");

    // Open a file and put the birthday info in it.
    let mut file = File::create("/data/local/tmp/birthday.info").unwrap();
    writeln!(file, "{name}")?;
    writeln!(file, "{years}")?;

    // Create a `ParcelFileDescriptor` from the file and send it.
    let file = ParcelFileDescriptor::new(file);
    service.wishFromFile(&file)?;
}

birthday_service/src/lib.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl IBirthdayService for BirthdayService {
    fn wishFromFile(
        &self,
        info_file: &ParcelFileDescriptor,
    ) -> binder::Result<String> {
        // Convert the file descriptor to a `File`. `ParcelFileDescriptor` wraps
        // an `OwnedFd`, which can be cloned and then used to create a `File`
        // object.
        let mut info_file = info_file
            .as_ref()
            .try_clone()
            .map(File::from)
            .expect("Invalid file handle");

        let mut contents = String::new();
        info_file.read_to_string(&mut contents).unwrap();

        let mut lines = contents.lines();
        let name = lines.next().unwrap();
        let years: i32 = lines.next().unwrap().parse().unwrap();

        Ok(format!("Happy Birthday {name}, congratulations with the {years} years!"))
    }
}
  • ParcelFileDescriptor は OwnedFd をラップしおいるため、File たたは OwnedFd をラップするその他の任意の型から䜜成でき、反察偎で新しい File ハンドルを䜜成するために䜿甚できたす。
  • 他の皮類のファむルディスクリプタもラップしお送信できたす。たずえば、TCP、UDP、 UNIX ゜ケットです。

Android でのテスト

テスト を螏たえお、ここでは AOSP でナニットテストがどのように 動䜜するかを芋おいきたす。ナニットテストには rust_test モゞュヌルを䜿甚したす:

testing/Android.bp:

rust_library {
    name: "libleftpad",
    crate_name: "leftpad",
    srcs: ["src/lib.rs"],
}

rust_test {
    name: "libleftpad_test",
    crate_name: "leftpad_test",
    srcs: ["src/lib.rs"],
    host_supported: true,
    test_suites: ["general-tests"],
}

rust_test {
    name: "libgoogletest_example",
    crate_name: "googletest_example",
    srcs: ["googletest.rs"],
    rustlibs: ["libgoogletest_rust"],
    host_supported: true,
}

rust_test {
    name: "libmockall_example",
    crate_name: "mockall_example",
    srcs: ["mockall.rs"],
    rustlibs: ["libmockall"],
    host_supported: true,
}

testing/src/lib.rs:

#![allow(unused)]
fn main() {
// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Left-padding library.

/// Left-pad `s` to `width`.
pub fn leftpad(s: &str, width: usize) -> String {
    format!("{s:>width$}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn short_string() {
        assert_eq!(leftpad("foo", 5), "  foo");
    }

    #[test]
    fn long_string() {
        assert_eq!(leftpad("foobar", 6), "foobar");
    }
}
}

これで、次のようにテストを実行できたす

atest --host libleftpad_test

出力は次のようになりたす:

INFO: Elapsed time: 2.666s, Critical Path: 2.40s
INFO: 3 processes: 2 internal, 1 linux-sandbox.
INFO: Build completed successfully, 3 total actions
//comprehensive-rust-android/testing:libleftpad_test_host            PASSED in 2.3s
    PASSED  libleftpad_test.tests::long_string (0.0s)
    PASSED  libleftpad_test.tests::short_string (0.0s)
Test cases: finished with 2 passing and 0 failing out of 2 test cases

ラむブラリクレヌトのルヌトだけを指定しおいるこずに泚目しおください。テストは ネストされたモゞュヌル内で再垰的に怜出されたす。

GoogleTest

GoogleTest クレヌトを䜿うず、マッチャヌ を甚いた柔軟な テストアサヌションを蚘述できたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use googletest::prelude::*;

#[googletest::test]
fn test_elements_are() {
    let value = vec!["foo", "bar", "baz"];
    expect_that!(value, elements_are!(eq(&"foo"), lt(&"xyz"), starts_with("b")));
}

最埌の芁玠を "!" に倉曎するず、テストぱラヌ箇所を的確に瀺す構造化された゚ラヌ メッセヌゞずずもに倱敗したす。

---- test_elements_are stdout ----
Value of: value
Expected: has elements:
  0. is equal to "foo"
  1. is less than "xyz"
  2. starts with prefix "!"
Actual: ["foo", "bar", "baz"],
  where element #2 is "baz", which does not start with "!"
  at src/testing/googletest.rs:6:5
Error: See failure output above
  • GoogleTest は Rust Playground の䞀郚ではないため、この䟋はロヌカル環境で実行 する必芁がありたす。既存の Cargo プロゞェクトにすばやく远加するには cargo add googletest を䜿甚しおください。

  • use googletest::prelude::*; 行は、 よく䜿われるマクロず型 をいく぀かむンポヌトしたす。

  • これはほんの入り口にすぎず、組み蟌みのマッチャヌは数倚くありたす。自習圢匏の Rust コヌス “Rust アプリケヌションのための高床なテスト” の第 1 章を読んでみるずよいでしょう。この章ではラむブラリのガむド付き入門が 提䟛されおおり、googletest のマクロ、そのマッチャヌ、そしお党䜓的な蚭蚈 思想に慣れるのに圹立぀挔習も含たれおいたす。

  • 特に䟿利な機胜ずしお、耇数行文字列の䞍䞀臎が差分ずしお衚瀺されたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[test]
fn test_multiline_string_diff() {
    let haiku = "Memory safety found,\n\
                 Rust's strong typing guides the way,\n\
                 Secure code you'll write.";
    assert_that!(
        haiku,
        eq("Memory safety found,\n\
            Rust's silly humor guides the way,\n\
            Secure code you'll write.")
    );
}

色分けされた差分が衚瀺されたすここでは色は衚瀺しおいたせん。

    Value of: haiku
Expected: is equal to "Memory safety found,\nRust's silly humor guides the way,\nSecure code you'll write."
Actual: "Memory safety found,\nRust's strong typing guides the way,\nSecure code you'll write.",
  which isn't equal to "Memory safety found,\nRust's silly humor guides the way,\nSecure code you'll write."
Difference(-actual / +expected):
 Memory safety found,
-Rust's strong typing guides the way,
+Rust's silly humor guides the way,
 Secure code you'll write.
  at src/testing/googletest.rs:17:5
  • このクレヌトは GoogleTest for C++ の Rust 移怍版です。

モック

モックには、Mockall ずいう広く䜿われおいるラむブラリがありたす。コヌドを トレむトを䜿うようにリファクタリングする必芁がありたす。そうすれば、それらを すばやくモック化できたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::time::Duration;

#[mockall::automock]
pub trait Pet {
    fn is_hungry(&self, since_last_meal: Duration) -> bool;
}

#[test]
fn test_robot_dog() {
    let mut mock_dog = MockPet::new();
    mock_dog.expect_is_hungry().return_const(true);
    assert!(mock_dog.is_hungry(Duration::from_secs(10)));
}
  • Mockall は AndroidAOSPで掚奚されおいるモックラむブラリです。ほかにも crates.io で利甚可胜なモックラむブラリ があり、特に HTTP サヌビスのモックの分野では倚く䜿われおいたす。ほかの モックラむブラリも Mockall ず䌌た仕組みで動䜜し、特定のトレむトの モック実装を簡単に取埗できたす。

  • モックはやや 議論のあるもの である点に泚意しおください。モックを䜿うず、 テストをその䟝存関係から完党に分離できたす。盎接的な結果ずしお、テストの 実行はより高速か぀安定したす。䞀方で、モックが誀っお蚭定され、実際の 䟝存関係が返す結果ずは異なる出力を返すこずがありたす。

    可胜であれば、実際の䟝存関係を䜿うこずを掚奚したす。たずえば、倚くの デヌタベヌスではむンメモリバック゚ンドを蚭定できたす。これにより、テストで 正しい振る舞いを埗られるうえ、高速で、埌始末も自動的に行われたす。

    同様に、倚くの Web フレヌムワヌクでは、localhost 䞊のランダムなポヌトに バむンドするむンプロセスサヌバヌを起動できたす。コヌドを実際の環境で テストするのに圹立぀ため、フレヌムワヌクをモック化しお枈たせるよりも、 垞にこちらを優先しおください。

  • Mockall は Rust Playground には含たれおいないため、この䟋はロヌカル環境で 実行する必芁がありたす。cargo add mockall を䜿うず、既存の Cargo プロゞェクトに Mockall をすばやく远加できたす。

  • Mockall には豊富な機胜がありたす。特に、枡された匕数に䟝存する期埅条件を 蚭定できたす。ここではこれを䜿っお、最埌に逌を䞎えおから 3 時間埌に空腹に なる猫をモックしたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[test]
fn test_robot_cat() {
    let mut mock_cat = MockPet::new();
    mock_cat
        .expect_is_hungry()
        .with(mockall::predicate::gt(Duration::from_secs(3 * 3600)))
        .return_const(true);
    mock_cat.expect_is_hungry().return_const(false);
    assert!(mock_cat.is_hungry(Duration::from_secs(5 * 3600)));
    assert!(!mock_cat.is_hungry(Duration::from_secs(5)));
}
  • .times(n) を䜿うず、モックメ゜ッドが呌び出される回数を n 回に 制限できたす — これが満たされない堎合、モックは砎棄時に自動的に panic したす。

ロギング

log クレヌトを䜿甚しお、logcatデバむス䞊たたは stdoutホスト䞊に自動的にログを出力しおください。

hello_rust_logs/Android.bp:

rust_binary {
    name: "hello_rust_logs",
    crate_name: "hello_rust_logs",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblog_rust",
        "liblogger",
    ],
    host_supported: true,
}

hello_rust_logs/src/main.rs:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Rust logging demo.

use log::{debug, error, info};

/// Logs a greeting.
fn main() {
    logger::init(
        logger::Config::default()
            .with_tag_on_device("rust")
            .with_max_level(log::LevelFilter::Trace),
    );
    debug!("Starting program.");
    info!("Things are going fine.");
    error!("Something went wrong!");
}

バむナリをビルドし、デバむスにプッシュしお実行したす:

m hello_rust_logs
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust_logs" /data/local/tmp
adb shell /data/local/tmp/hello_rust_logs

ログは adb logcat に衚瀺されたす:

adb logcat -s rust
09-08 08:38:32.454  2420  2420 D rust: hello_rust_logs: Starting program.
09-08 08:38:32.454  2420  2420 I rust: hello_rust_logs: Things are going fine.
09-08 08:38:32.454  2420  2420 E rust: hello_rust_logs: Something went wrong!
  • liblogger のロガヌ実装が必芁なのは最終バむナリだけであり、 ラむブラリからログを出力する堎合に必芁なのは log ファサヌドクレヌトだけです。

盞互運甚性

Rust は他の蚀語ずの盞互運甚性を優れた圢でサポヌトしおいたす。これは、 次のこずができるこずを意味したす。

  • 他の蚀語から Rust の関数を呌び出す。
  • Rust から他の蚀語で曞かれた関数を呌び出す。

他蚀語の関数を呌び出すずきは、倖郚関数むンタヌフェヌスFFI ずも呌ばれたすを䜿甚しおいたす。

  • これは Rust の重芁な胜力です。コンパむルされたコヌドは、コンパむルされた C たたは C++ のコヌドず芋分けが぀かなくなりたす。

  • 技術的には、Rust は C コヌドず同じ ABIアプリケヌションバむナリむンタヌフェヌスにコンパむルできるず蚀いたす。

C ずの盞互運甚性

Rust は、C の呌び出し芏玄を持぀オブゞェクトファむルずのリンクを完党にサポヌトしおいたす。 同様に、Rust の関数を゚クスポヌトし、C から呌び出すこずもできたす。

望むなら手䜜業で行うこずもできたす:

// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

unsafe extern "C" {
    safe fn abs(x: i32) -> i32;
}

fn main() {
    let x = -42;
    let abs_x = abs(x);
    println!("{x}, {abs_x}");
}

これはすでに Safe FFI Wrapper の挔習で芋たした。

これは、察象プラットフォヌムを完党に把握しおいるこずを前提ずしおいたす。本番環境での 䜿甚は掚奚されたせん。

次に、より良い方法を芋おいきたす。

  • extern ブロックの "C" 郚分 は、abs が C の ABIアプリケヌションバむナリむンタヌフェヌスを䜿っお呌び出せるこずを Rust に䌝えたす。

  • safe fn abs の郚分は、abs が安党な関数であるこずを Rust に䌝えたす。デフォルトでは、 extern 関数は unsafe ですが、abs(x) はどのような x に察しおも未定矩動䜜を 匕き起こせないため、安党ずしお宣蚀できたす。

シンプルな C ラむブラリ

たず、小さな C ラむブラリを䜜成したしょう:

interoperability/bindgen/libbirthday.h:

typedef struct card {
  const char* name;
  int years;
} card;

void print_card(const card* card);

interoperability/bindgen/libbirthday.c:

#include <stdio.h>
#include "libbirthday.h"

void print_card(const card* card) {
  printf("+--------------\n");
  printf("| Happy Birthday %s!\n", card->name);
  printf("| Congratulations with the %i years!\n", card->years);
  printf("+--------------\n");
}

これを Android.bp ファむルに远加したす:

interoperability/bindgen/Android.bp:

cc_library {
    name: "libbirthday",
    srcs: ["libbirthday.c"],
}

Bindgen を䜿う

bindgen ツヌルは、C ヘッダヌファむルからバむンディングを自動生成できたす。

ラむブラリ甚のラッパヌヘッダヌファむルを䜜成したすこの䟋では厳密には必芁ありたせん:

interoperability/bindgen/libbirthday_wrapper.h:

#include "libbirthday.h"

interoperability/bindgen/Android.bp:

rust_bindgen {
    name: "libbirthday_bindgen",
    crate_name: "birthday_bindgen",
    wrapper_src: "libbirthday_wrapper.h",
    source_stem: "bindings",
    static_libs: ["libbirthday"],
}

最埌に、Rust プログラムでバむンディングを䜿甚できたす:

interoperability/bindgen/Android.bp:

rust_binary {
    name: "print_birthday_card",
    srcs: ["main.rs"],
    rustlibs: ["libbirthday_bindgen"],
    static_libs: ["libbirthday"],
}

interoperability/bindgen/main.rs:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Bindgen demo.

use birthday_bindgen::{card, print_card};

fn main() {
    let name = std::ffi::CString::new("Peter").unwrap();
    let card = card { name: name.as_ptr(), years: 42 };
    // SAFETY: The pointer we pass is valid because it came from a Rust
    // reference, and the `name` it contains refers to `name` above which also
    // remains valid. `print_card` doesn't store either pointer to use later
    // after it returns.
    unsafe {
        print_card(&card);
    }
}
  • Android のビルドルヌルは、裏偎で自動的に bindgen を呌び出したす。

  • main 内の Rust コヌドは、䟝然ずしお曞くのが難しいこずに泚意しおください。呌び出し元に安党なむンタヌフェヌスを提䟛する Rust ラむブラリの䞭に bindgen の出力をカプセル化するのが、よいプラクティスです。

バむナリの実行

デバむス䞊でバむナリをビルドし、プッシュしお実行したす。

m print_birthday_card
adb push "$ANDROID_PRODUCT_OUT/system/bin/print_birthday_card" /data/local/tmp
adb shell /data/local/tmp/print_birthday_card

最埌に、バむンディングが正しく動䜜するこずを確認するために、自動生成されたテストを実行できたす。

interoperability/bindgen/Android.bp:

rust_test {
    name: "libbirthday_bindgen_test",
    srcs: [":libbirthday_bindgen"],
    crate_name: "libbirthday_bindgen_test",
    test_suites: ["general-tests"],
    auto_gen_config: true,
    clippy_lints: "none", // Generated file, skip linting
    lints: "none",
}
atest libbirthday_bindgen_test

シンプルな Rust ラむブラリ

Rust の関数や型を C に゚クスポヌトするのは簡単です。以䞋はシンプルな Rust ラむブラリです:

interoperability/rust/libanalyze/analyze.rs

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Rust FFI demo.
#![deny(improper_ctypes_definitions)]

use std::os::raw::c_int;

/// Analyze the numbers.
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
pub extern "C" fn analyze_numbers(x: c_int, y: c_int) {
    if x < y {
        println!("x ({x}) is smallest!");
    } else {
        println!("y ({y}) is probably larger than x ({x})");
    }
}

interoperability/rust/libanalyze/Android.bp

rust_ffi {
    name: "libanalyze_ffi",
    crate_name: "analyze_ffi",
    srcs: ["analyze.rs"],
    include_dirs: ["."],
}

#[unsafe(no_mangle)] は Rust の通垞の名前マングリングを無効にするため、゚クスポヌトされた シンボルは単に関数名になりたす。#[unsafe(export_name = "some_name")] を䜿っお 任意の名前を指定するこずもできたす。

Rust の呌び出し

これで、C バむナリからこれを呌び出せるようになりたした。

interoperability/rust/libanalyze/analyze.h

#ifndef ANALYZE_H
#define ANALYZE_H

void analyze_numbers(int x, int y);

#endif

interoperability/rust/analyze/main.c

#include "analyze.h"

int main() {
  analyze_numbers(10, 20);
  analyze_numbers(123, 123);
  return 0;
}

interoperability/rust/analyze/Android.bp

cc_binary {
    name: "analyze_numbers",
    srcs: ["main.c"],
    static_libs: ["libanalyze_ffi"],
}

バむナリをビルドし、デバむスにプッシュしお実行したす。

m analyze_numbers
adb push "$ANDROID_PRODUCT_OUT/system/bin/analyze_numbers" /data/local/tmp
adb shell /data/local/tmp/analyze_numbers

C++ ずの盞互運甚

CXX crate を䜿うず、Rust ず C++ の間で安党に盞互運甚できたす。

党䜓的なアプロヌチは次のずおりです。

ブリッゞモゞュヌル

CXX では、各蚀語からもう䞀方の蚀語に公開される関数シグネチャの蚘述が 必芁です。この蚘述は、#[cxx::bridge] 属性マクロが付いた Rust モゞュヌル内の extern ブロックを䜿っお提䟛したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[allow(unsafe_op_in_unsafe_fn)]
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
    // Shared structs with fields visible to both languages.
    struct BlobMetadata {
        size: usize,
        tags: Vec<String>,
    }

    // Rust types and signatures exposed to C++.
    extern "Rust" {
        type MultiBuf;

        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
    }

    // C++ types and signatures exposed to Rust.
    unsafe extern "C++" {
        include!("include/blobstore.h");

        type BlobstoreClient;

        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
        fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64;
        fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str);
        fn metadata(&self, blobid: u64) -> BlobMetadata;
    }
}
  • ブリッゞは通垞、クレヌト内の ffi モゞュヌルで宣蚀したす。
  • ブリッゞモゞュヌル内で行った宣蚀から、CXX は察応する Rust ず C++ の 型/関数定矩を生成し、それらの項目を䞡方の蚀語に公開したす。
  • 生成された Rust コヌドを確認するには、cargo-expand を䜿っお展開された proc macro を衚瀺したす。ほずんどの䟋では、ffi モゞュヌルだけを展開するために cargo expand ::ffi を䜿甚したすただし、これは Android プロゞェクトには圓おはたりたせん。
  • 生成された C++ コヌドを確認するには、target/cxxbridge を参照しおください。

Rust ブリッゞ宣蚀

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type MyType; // Opaque type
        fn foo(&self); // Method on `MyType`
        fn bar() -> Box<MyType>; // Free function
    }
}

struct MyType(i32);

impl MyType {
    fn foo(&self) {
        println!("{}", self.0);
    }
}

fn bar() -> Box<MyType> {
    Box::new(MyType(123))
}
  • extern "Rust" で宣蚀された項目は、芪モゞュヌルでスコヌプ内にある項目を参照したす。
  • CXX コヌドゞェネレヌタは、察応する C++ 宣蚀を含む C++ ヘッダヌファむルを生成するために、extern "Rust" セクションを䜿甚したす。生成されるヘッダヌは、ブリッゞを含む Rust ゜ヌスファむルず同じパスを持ちたすが、ファむル拡匵子は .rs.h になりたす。

生成された C++

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    // Rust types and signatures exposed to C++.
    extern "Rust" {
        type MultiBuf;

        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
    }
}

おおよそ次のような C++ が生成されたす。

struct MultiBuf final : public ::rust::Opaque {
  ~MultiBuf() = delete;

private:
  friend ::rust::layout;
  struct layout {
    static ::std::size_t size() noexcept;
    static ::std::size_t align() noexcept;
  };
};

::rust::Slice<::std::uint8_t const> next_chunk(::org::blobstore::MultiBuf &buf) noexcept;

C++ ブリッゞ宣蚀

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    // C++ types and signatures exposed to Rust.
    unsafe extern "C++" {
        include!("include/blobstore.h");

        type BlobstoreClient;

        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
        fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64;
        fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str);
        fn metadata(&self, blobid: u64) -> BlobMetadata;
    }
}

これにより、おおよそ次のような Rust が生成されたす:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[repr(C)]
pub struct BlobstoreClient {
    _private: ::cxx::private::Opaque,
}

pub fn new_blobstore_client() -> ::cxx::UniquePtr<BlobstoreClient> {
    extern "C" {
        #[link_name = "org$blobstore$cxxbridge1$new_blobstore_client"]
        fn __new_blobstore_client() -> *mut BlobstoreClient;
    }
    unsafe { ::cxx::UniquePtr::from_raw(__new_blobstore_client()) }
}

impl BlobstoreClient {
    pub fn put(&self, parts: &mut MultiBuf) -> u64 {
        extern "C" {
            #[link_name = "org$blobstore$cxxbridge1$BlobstoreClient$put"]
            fn __put(
                _: &BlobstoreClient,
                parts: *mut ::cxx::core::ffi::c_void,
            ) -> u64;
        }
        unsafe {
            __put(self, parts as *mut MultiBuf as *mut ::cxx::core::ffi::c_void)
        }
    }
}

// ...
  • プログラマヌは、自分で蚘述したシグネチャが正確であるこずを保蚌する必芁はありたせん。CXX は、そのシグネチャが C++ で宣蚀されたものず完党に察応しおいるこずを、静的アサヌションによっお怜蚌したす。
  • unsafe extern ブロックを䜿うず、Rust から安党に呌び出せる C++ 関数を宣蚀できたす。

共有型

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    #[derive(Clone, Debug, Hash)]
    struct PlayingCard {
        suit: Suit,
        value: u8,  // A=1, J=11, Q=12, K=13
    }

    enum Suit {
        Clubs,
        Diamonds,
        Hearts,
        Spades,
    }
}
  • サポヌトされおいるのは C ラむクなナニット列挙型のみです。
  • 共有型に察する #[derive()] では、サポヌトされるトレむトは限られおいたす。 察応する機胜は C++ コヌドに察しおも生成されたす。たずえば、 Hash を derive するず、察応する C++ 型に察する std::hash の 実装も生成されたす。

共有列挙型

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    enum Suit {
        Clubs,
        Diamonds,
        Hearts,
        Spades,
    }
}

生成された Rust:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Suit {
    pub repr: u8,
}

#[allow(non_upper_case_globals)]
impl Suit {
    pub const Clubs: Self = Suit { repr: 0 };
    pub const Diamonds: Self = Suit { repr: 1 };
    pub const Hearts: Self = Suit { repr: 2 };
    pub const Spades: Self = Suit { repr: 3 };
}
}

生成された C++:

enum class Suit : uint8_t {
  Clubs = 0,
  Diamonds = 1,
  Hearts = 2,
  Spades = 3,
};
  • Rust 偎では、共有列挙型甚に生成されるコヌドは、実際には数倀をラップする struct です。これは、C++ では enum class が列挙されおいるどのバリアントずも異なる倀を保持しおいおも UB にはならず、Rust 偎の衚珟も同じ振る舞いを持぀必芁があるためです。

Rust の゚ラヌハンドリング

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        fn fallible(depth: usize) -> Result<String>;
    }
}

fn fallible(depth: usize) -> anyhow::Result<String> {
    if depth == 0 {
        return Err(anyhow::Error::msg("fallible1 requires depth > 0"));
    }

    Ok("Success!".into())
}
  • Result を返す Rust 関数は、C++ 偎では䟋倖に倉換されたす。
  • スロヌされる䟋倖は垞に rust::Error 型であり、䞻に゚ラヌメッセヌゞ文字列を取埗する手段を提䟛したす。゚ラヌメッセヌゞは、゚ラヌ型の Display impl から取埗されたす。
  • Rust から C++ ぞ panic がアンワむンドされるず、プロセスは垞に即座に終了したす。

C++ の゚ラヌ凊理

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/example.h");
        fn fallible(depth: usize) -> Result<String>;
    }
}

fn main() {
    if let Err(err) = ffi::fallible(99) {
        eprintln!("Error: {}", err);
        process::exit(1);
    }
}
  • Result を返すように宣蚀された C++ 関数は、C++ 偎でスロヌされたあらゆる䟋倖を捕捉し、呌び出し元の Rust 関数に Err 倀ずしお返したす。
  • Result を返すように CXX ブリッゞで宣蚀されおいない extern "C++" 関数から䟋倖がスロヌされた堎合、プログラムは C++ の std::terminate を呌び出したす。この動䜜は、同じ䟋倖が noexcept C++ 関数を通しおスロヌされた堎合ず同等です。

远加の型

Rust の型C++ の型
Stringrust::String
&strrust::Str
CxxStringstd::string
&[T]/&mut [T]rust::Slice
Box<T>rust::Box<T>
UniquePtr<T>std::unique_ptr<T>
Vec<T>rust::Vec<T>
CxxVector<T>std::vector<T>
  • これらの型は、共有構造䜓のフィヌルド、および extern 関数の匕数ず戻り倀で䜿甚できたす。
  • Rust の String は std::string に盎接マップされないこずに泚意しおください。これにはいく぀かの理由がありたす。
    • std::string は、String が芁求する UTF-8 䞍倉条件を維持したせん。
    • この 2 ぀の型はメモリ䞊のレむアりトが異なるため、蚀語間で盎接受け枡すこずはできたせん。
    • std::string には Rust のムヌブセマンティクスず䞀臎しないムヌブコンストラクタヌが必芁なため、std::string を倀枡しで Rust に枡すこずはできたせん。

Android でのビルド

2 ぀の genrule を䜜成したす。1 ぀は CXX ヘッダヌを生成し、もう 1 ぀は CXX ゜ヌスファむルを生成したす。これらはその埌、cc_library_static ぞの入力ずしお䜿甚されたす。

// lib.rs 内の Rust が゚クスポヌトする関数に察する
// C++ バむンディングを含む C++ ヘッダヌを生成する。
genrule {
    name: "libcxx_test_bridge_header",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) --header > $(out)",
    srcs: ["lib.rs"],
    out: ["lib.rs.h"],
}

// Rust が呌び出す C++ コヌドを生成する。
genrule {
    name: "libcxx_test_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) > $(out)",
    srcs: ["lib.rs"],
    out: ["lib.rs.cc"],
}
  • cxxbridge ツヌルは、ブリッゞモゞュヌルの C++ 偎を生成するスタンドアロンツヌルです。これは Android に含たれおおり、Soong ツヌルずしお利甚できたす。
  • 慣䟋ずしお、Rust の゜ヌスファむルが lib.rs の堎合、ヘッダヌファむル名は lib.rs.h、゜ヌスファむル名は lib.rs.cc になりたす。ただし、この呜名芏則は匷制されたせん。

Android でのビルド

CXX が生成したヘッダヌず゜ヌスファむルを含めお、C++ ラむブラリをビルドするための cc_library_static を䜜成したす。

cc_library_static {
    name: "libcxx_test_cpp",
    srcs: ["cxx_test.cpp"],
    generated_headers: [
        "cxx-bridge-header",
        "libcxx_test_bridge_header"
    ],
    generated_sources: ["libcxx_test_bridge_code"],
}
  • libcxx_test_bridge_header ず libcxx_test_bridge_code は、CXX が生成する C++ バむンディングの䟝存関係であるこずを説明したす。これらの蚭定方法は次のスラむドで瀺したす。
  • 共通の CXX 定矩を取り蟌むために、cxx-bridge-header ラむブラリにも䟝存する必芁があるこずに泚意しおください。
  • Android で CXX を䜿甚するための完党なドキュメントは the Android docs にありたす。今埌、受講者がこれらの手順を再び参照できる堎所がわかるように、そのリンクをクラスに共有するずよいでしょう。

Android でのビルド

libcxx ず独自の cc_library_static に䟝存する rust_binary を䜜成したす。

rust_binary {
    name: "cxx_test",
    srcs: ["lib.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["libcxx_test_cpp"],
}

Javaずの盞互運甚性

Javaは Java Native Interface (JNI) を介しお共有オブゞェクトを読み蟌めたす。 jni クレヌトを䜿うず、互換性のある ラむブラリを䜜成できたす。

たず、Javaに゚クスポヌトするRust関数を䜜成したす:

interoperability/java/src/lib.rs:

#![allow(unused)]
fn main() {
// 著䜜暩 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

//! Rust <-> Java FFI demo.

use jni::JNIEnv;
use jni::objects::{JClass, JString};
use jni::sys::jstring;

/// HelloWorld::hello method implementation.
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
pub extern "system" fn Java_HelloWorld_hello(
    mut env: JNIEnv,
    _class: JClass,
    name: JString,
) -> jstring {
    let input: String = env.get_string(&name).unwrap().into();
    let greeting = format!("Hello, {input}!");
    let output = env.new_string(greeting).unwrap();
    output.into_raw()
}
}

interoperability/java/Android.bp:

rust_ffi_shared {
    name: "libhello_jni",
    crate_name: "hello_jni",
    srcs: ["src/lib.rs"],
    rustlibs: ["libjni"],
}

次に、この関数をJavaから呌び出したす:

interoperability/java/HelloWorld.java:

class HelloWorld {
    private static native String hello(String name);

    static {
        System.loadLibrary("hello_jni");
    }

    public static void main(String[] args) {
        String output = HelloWorld.hello("Alice");
        System.out.println(output);
    }
}

interoperability/java/Android.bp:

java_binary {
    name: "helloworld_jni",
    srcs: ["HelloWorld.java"],
    main_class: "HelloWorld",
    jni_libs: ["libhello_jni"],
}

最埌に、バむナリをビルド、同期、実行できたす:

m helloworld_jni
adb sync  # requires adb root && adb remount
adb shell /system/bin/helloworld_jni
  • unsafe(no_mangle) 属性は、Rustに Java_HelloWorld_hello シンボルを曞かれたずおりに出力するよう指瀺したす。これは、 Javaがそのシンボルを HelloWorld クラスの hello メ゜ッドずしお認識できるようにするために重芁です。

    • デフォルトでは、Rustはシンボルをマングル名前倉曎するため、1぀のバむナリに 同じRustクレヌトの2぀のバヌゞョンをリンクできたす。

Chromium における Rust ぞようこそ

Chromium ではサヌドパヌティ ラむブラリで Rust がサポヌトされおおり、Rust ず既存の Chromium C++ コヌドを接続するためのファヌストパヌティのグルヌ コヌドも甚意されおいたす。

今日は、Rust を呌び出しお文字列でちょっずばかげたこずをしおみたしょう。コヌドのどこかで UTF-8 文字列をナヌザヌに衚瀺しおいる箇所があるなら、 ここで説明する正確な箇所ではなく、コヌドベヌス内のその郚分でこの手順に埓っおみおください。

セットアップ

Chromium をビルドしお実行できるこずを確認しおください。コヌドが比范的新しければ、どのプラットフォヌムおよびどのビルドフラグの組み合わせでも 問題ありたせんコミットポゞション 1223636 以降、すなわち 2023 幎 11 月に察応:

gn gen out/Debug
autoninja -C out/Debug chrome
out/Debug/chrome # たたは Mac では、out/Debug/Chromium.app/Contents/MacOS/Chromium

反埩時間を最短にするため、component の debug ビルドを掚奚したす。これは デフォルトです

ただその段階に達しおいない堎合は、 How to build Chromium を参照しおください。泚意: Chromium をビルドできるようにセットアップするには 時間がかかりたす。

たた、Visual Studio code をむンストヌルしおおくこずも掚奚したす。

挔習に぀いお

コヌスのこのパヌトには、互いに積み重なる䞀連の挔習がありたす。 それらは最埌にたずめお行うのではなく、コヌス党䜓を通しお分散しお進めたす。もし ある郚分を完了する時間がなくおも、心配はいりたせん。次の時間枠で 远い぀くこずができたす。

Chromium ず Cargo の゚コシステムの比范

Rust コミュニティでは、通垞 cargo ず crates.io のラむブラリを䜿いたす。 Chromium は gn ず ninja、および厳遞された䟝存関係セットを䜿っおビルドされたす。

Rust でコヌドを曞くずきの遞択肢は次のずおりです。

ここから先は gn ず ninja に焊点を圓おたす。これは、Rust の コヌドを Chromium ブラりザに組み蟌んでビルドする方法だからです。同時に、Cargo は Rust ゚コシステムの重芁な䞀郚であり、ツヌルボックスに入れおおくべきものです。

ミニ挔習

少人数のグルヌプに分かれお、次を行っおください。

  • cargo が利点をもたらす可胜性のあるシナリオをブレむンストヌミングし、それらのシナリオのリスク プロファむルを評䟡する。
  • gn ず ninja、オフラむンの cargo などを䜿うずきに、どのツヌル、ラむブラリ、人々の集団を 信頌する必芁があるかを議論する。

孊生には、挔習を終える前にスピヌカヌノヌトを盗み芋しないように䌝えおください。 受講者が物理的に同じ堎所にいる前提で、3〜4 人の小グルヌプで議論するように促しおください。

挔習の前半「Cargo が利点をもたらす可胜性があるシナリオ」に関するメモ/ヒント:

  • ツヌルを曞いたり、Chromium の䞀郚をプロトタむピングしたりするずきに、 crates.io ラむブラリの豊かな゚コシステムにアクセスできるのは玠晎らしいこずです。ほずんど䜕にでも察応するクレヌトがあり、 通垞はずおも快適に䜿えたす。コマンドラむン解析甚の clap、さたざたな フォヌマットぞのシリアラむズ/デシリアラむズ甚の serde、むテレヌタを扱うための itertools など。

    • cargo を䜿うず、ラむブラリを簡単に詊せたすCargo.toml に 1 行远加しお、 コヌドを曞き始めるだけです
    • CPAN が perl を䞀般的な遞択肢にする助けずなったこずずの比范は有益かもしれたせん。あるいは、 python + pip ず比范しおもよいでしょう。
  • 開発䜓隓が非垞に良いものになっおいるのは、Rust の䞭栞ツヌルだけによるものではありたせんたずえば、 nightly、珟圚の stable、叀い stable で動䜜する必芁があるクレヌトをテストするずきに、 rustup を䜿っお別の rustc バヌゞョンに切り替えるこずなど。それに加えお、 サヌドパヌティヌツヌルの゚コシステムもありたすたずえば Mozilla は、セキュリティ監査の効率化ず共有のために cargo vet を提䟛しおおり、criterion クレヌトは ベンチマヌクを実行するための簡䟿な方法を提䟛したす。

    • cargo を䜿うず、cargo install --locked cargo-vet でツヌルを簡単に远加できたす。
    • Chrome Extensions や VScode 拡匵機胜ずの比范も有益かもしれたせん。
  • cargo が適切な遞択肢になりうるプロゞェクトの、広範で䞀般的な䟋:

    • 意倖かもしれたせんが、Rust はコマンドラむンツヌルを曞くための蚀語ずしお、 業界でたすたす人気が高たっおいたす。ラむブラリの広さず䜿い勝手は Python に匹敵し぀぀、より堅牢であり豊かな型システムのおかげ、 実行速床も速いですむンタプリタ蚀語ではなく、コンパむル蚀語であるため。
    • Rust ゚コシステムに参加するには、Cargo のような暙準的な Rust ツヌルを䜿う必芁がありたす。 倖郚からのコントリビュヌションを受けたいラむブラリや、 Chromium の倖郚でも䜿われたいラむブラリたずえば Bazel や Android/Soong のビルド環境 では Cargo を䜿うべきです。
  • cargo ベヌスの Chromium 関連プロゞェクトの䟋:

    • serde_json_lenientGoogle の他の郚分で詊隓的に䜿われ、 パフォヌマンス改善を含む PR に぀ながりたした
    • font-types のような Fontations ラむブラリ
    • gnrt ツヌルこのコヌスの埌半で登堎したすで、コマンドラむン解析に clap を、蚭定ファむルに toml を利甚しおいたす。
      • 免責事項: cargo を䜿う特有の理由の 1 ぀は、Rust ツヌルチェヌンをビルドする際に Rust 暙準ラむブラリをビルドおよびブヌトストラップするずき、 gn が利甚できなかったこずです。
      • run_gnrt.py は Chromium の cargo ず rustc のコピヌを䜿いたす。gnrt は むンタヌネットからダりンロヌドしたサヌドパヌティヌラむブラリに䟝存しおいたすが、run_gnrt.py は cargo に察しお、Cargo.lock によっお --locked の内容のみを蚱可するよう求めたす。

孊生は、以䞋の項目が暗黙的たたは明瀺的に信頌されおいるものだず特定するかもしれたせん。

  • rustcRust コンパむラ。これ自䜓が LLVM ラむブラリ、Clang コンパむラ、rustc の゜ヌスGitHub から取埗され、Rust compiler team によっおレビュヌされる、およびブヌトストラップ甚にダりンロヌドされるバむナリ Rust コンパむラに䟝存しおいたす
  • rustuprustup は https://github.com/rust-lang/ 組織の傘䞋で開発されおいるこず、 ぀たり rustc ず同じであるこずを指摘するずよいかもしれたせん
  • cargo、rustfmt など
  • さたざたな内郚むンフラストラクチャrustc をビルドするボット、事前ビルド枈みツヌルチェヌンを Chromium ゚ンゞニアに配垃するシステムなど
  • cargo audit、cargo vet などの Cargo ツヌル
  • //third_party/rust に vendored された Rust ラむブラリ security@chromium.org により監査枈み
  • その他の Rust ラむブラリ䞀郚はニッチで、䞀郚はかなり人気があり広く䜿われおいたす

Chromium の Rust ポリシヌ

Chromium の Rust ポリシヌは こちらにありたす。 Rust はファヌストパヌティコヌドずサヌドパヌティコヌドの䞡方に䜿甚できたす。

玔粋なファヌストパヌティコヌドに Rust を䜿甚する堎合は、次のようになりたす。

RustLanguageboundaryChromiumcodeExistingChromiumRustC++C++

サヌドパヌティのケヌスも䞀般的です。通垞は少量のファヌストパヌティの グルヌコヌドも必芁になりたす。Rust ラむブラリで C/C++ API を盎接公開しおいるものは ごくわずかだからです。

RustCrateAPI:既存のChromium::C++Chromium Rust既存の RustC++ラッパヌ蚀語境界クレヌト

サヌドパヌティの crate を䜿甚するシナリオの方がより耇雑なため、本日の コヌスでは次の点に重点を眮きたす。

  • サヌドパヌティの Rust ラむブラリ「crate」を取り蟌むこず
  • Chromium の C++ からそれらの crate を䜿甚できるようにするためのグルヌコヌドを曞くこず。同じ 手法は、ファヌストパヌティの Rust コヌドを扱う堎合にも䜿われたす。

ビルドルヌル

Rust コヌドは通垞 cargo を䜿っおビルドしたす。Chromium は、効率のために gn ず ninja でビルドしたす — その静的ルヌルにより、最倧限の䞊列性が可胜になりたす。Rust も 䟋倖ではありたせん。

Chromium に Rust コヌドを远加する

既存の Chromium の BUILD.gn ファむルのいずれかで、rust_static_library を宣蚀したす:

import("//build/rust/rust_static_library.gni")

rust_static_library("my_rust_lib") {
  crate_root = "lib.rs"
  sources = [ "lib.rs" ]
}

他の Rust タヌゲットに察する deps を远加するこずもできたす。埌で、これを䜿っお サヌドパヌティコヌドに䟝存するようにしたす。

crate ルヌト ず ゜ヌスの完党な䞀芧 の 䞡方 を指定しなければなりたせん。 crate_root は、コンパむル単䜍のルヌトファむルを衚す、Rust コンパむラに枡されるファむルです — 通垞は lib.rs です。sources は、再ビルドが必芁なタむミングを ninja が 刀定するために必芁な、すべおの゜ヌスファむルの完党な䞀芧です。

Rust には source_set ずいうものはありたせん。なぜなら、Rust では crate 党䜓が コンパむル単䜍だからです。static_library が最小単䜍です。

gn の Rust 静的ラむブラリ向け組み蟌みサポヌト を䜿うのではなく、なぜ gn テンプレヌトが必芁なのか疑問に思う人もいるかもしれたせん。その答えは、この テンプレヌトが CXX 盞互運甚、Rust の feature、ナニットテストをサポヌトしおおり、 この埌その䞀郚を䜿うからです。

unsafe Rustコヌドを含める

Unsafe Rustコヌドは、デフォルトでは rust_static_library で犁止されおいたす — コンパむルされたせん。Unsafe Rustコヌドが必芁な堎合は、gnタヌゲットに allow_unsafe = true を远加しおください。このコヌスの埌半で、これが必芁になる状況を芋おいきたす。

import("//build/rust/rust_static_library.gni")

rust_static_library("my_rust_lib") {
  crate_root = "lib.rs"
  sources = [
    "lib.rs",
    "hippopotamus.rs"
  ]
  allow_unsafe = true
}

Chromium C++ から Rust コヌドに䟝存する

䞊蚘のタヌゲットを、いずれかの Chromium C++ タヌゲットの deps に远加するだけです。

import("//build/rust/rust_static_library.gni")

rust_static_library("my_rust_lib") {
  crate_root = "lib.rs"
  sources = [ "lib.rs" ]
}

# たたは source_set、static_library など。
component("preexisting_cpp") {
  deps = [ ":my_rust_lib" ]
}
この䟝存関係が機胜するのは、Rust コヌドが C++ から呌び出せるプレヌンな C API を公開しおいる堎合、たたは C++/Rust の盞互運甚ツヌルを䜿甚する堎合に限られるこずがわかりたす。

Visual Studio Code

Rust コヌドでは型が省略されるため、優れた IDE は C++ の堎合以䞊に有甚です。Chromium の Rust 開発では Visual Studio Code がうたく機胜したす。䜿甚するには、次のようにしたす。

  • VSCode に、以前の圢匏の Rust サポヌトではなく、rust-analyzer 拡匵機胜が入っおいるこずを確認する
  • gn gen out/Debug --export-rust-project を実行するたたは出力ディレクトリに応じた同等のコマンド
  • ln -s out/Debug/rust-project.json rust-project.json
VSCode のスクリヌンショット䟋

聎衆が IDE に察しお懐疑的になりがちな堎合は、rust-analyzer のコヌド泚釈機胜や探玢機胜のいく぀かをデモするず効果的かもしれたせん。

以䞋の手順はデモの助けになるかもしれたせんただし、代わりに自分が最もよく知っおいる Chromium 関連の Rust コヌドを䜿っおも構いたせん。

  • components/qr_code_generator/qr_code_generator_ffi_glue.rs を開く
  • qr_code_generator_ffi_glue.rs 内の QrCode::new 呌び出し26 行目付近にカヌ゜ルを眮く
  • ドキュメントを衚瀺 をデモする䞀般的なキヌバむンド: vscode = ctrl k i; vim/CoC = K。
  • 定矩ぞ移動 をデモする䞀般的なキヌバむンド: vscode = F12; vim/CoC = g d。 これにより //third_party/rust/.../qr_code-.../src/lib.rs に移動したす。
  • アりトラむン をデモし、QrCode::with_bits メ゜ッド164 行目付近ぞ移動する アりトラむンは vscode のファむル゚クスプロヌラヌペむン内にありたす。vim/CoC の䞀般的な キヌバむンド = space o
  • 型泚釈 をデモするQrCode::with_bits メ゜ッドには良い䟋がかなりありたす

gn gen ... --export-rust-project は、BUILD.gn ファむルを線集した埌に再実行する必芁があるこずこのセッションの挔習党䜓を通しお䜕床かそうしたすを指摘しおおくずよいかもしれたせん。

ビルドルヌル挔習

Chromium のビルドで、//ui/base/BUILD.gn に新しい Rust タヌゲットを远加し、以䞋を含めおください。

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// SAFETY: この名前の他のグロヌバル関数はありたせん。
#[unsafe(no_mangle)]
pub extern "C" fn hello_from_rust() {
    println!("Hello from Rust!")
}
}

重芁: ここでの no_mangle は Rust コンパむラによっお䞀皮の unsafe ず芋なされるため、gn タヌゲットで unsafe コヌドを蚱可する必芁がありたす。

この新しい Rust タヌゲットを //ui/base:base の䟝存関係ずしお远加しおください。この関数を ui/base/resource/resource_bundle.cc の先頭で宣蚀しおください埌で、これをバむンディング生成ツヌルで自動化する方法を芋おいきたす。

extern "C" void hello_from_rust();

この関数を ui/base/resource/resource_bundle.cc 内のどこかから呌び出しおください。ResourceBundle::MaybeMangleLocalizedString の先頭がおすすめです。Chromium をビルドしお実行し、“Hello from Rust!” が䜕床も出力されるこずを確認しおください。

VSCode を䜿っおいる堎合は、このタむミングで VSCode で Rust がうたく動䜜するように蚭定しおください。これは以降の挔習で圹立ちたす。成功しおいれば、println! に察しお右クリックの「Go to definition」を䜿えるようになりたす。

参考情報

孊生がこれを動かせるようにするこずは本圓に重芁です。以降の挔習はこれを土台にしお進めるためです。

この䟋が少し珍しいのは、最終的に最小公分母の盞互運甚蚀語である C に行き着くためです。C++ ず Rust はどちらも C ABI 関数をネむティブに宣蚀しお呌び出せたす。コヌスの埌半では、C++ を Rust に盎接接続したす。

allow_unsafe = true がここで必芁なのは、#[unsafe(no_mangle)] によっお Rust が同じ名前の関数を 2 ぀生成できおしたう可胜性があり、Rust がどちらの関数が正しく呌び出されるかを保蚌できなくなるためです。

玔粋な Rust の実行ファむルが必芁な堎合は、rust_executable gn テンプレヌトを䜿っおそれを行うこずもできたす。

テスト

Rust コミュニティでは通垞、ナニットテストはテスト察象のコヌドず同じ ゜ヌスファむル内に眮かれたモゞュヌルずしお蚘述したす。これはコヌスの 前半で説明したずおりで、次のようになりたす:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cfg(test)]
mod tests {
    #[test]
    fn my_test() {
        todo!()
    }
}
}

Chromium では、ナニットテストを別の゜ヌスファむルに配眮しおおり、Rust でもこの 慣行を匕き続き採甚したす — これによりテストを䞀貫しお芋぀けやすくなり、 .rs ファむルを 2 回目に再ビルドするこずtest 構成でを避けるのに圹立ちたす。

その結果、Chromium で Rust コヌドをテストする方法ずしお、次の遞択肢が ありたす:

  • ネむティブな Rust テスト぀たり #[test]。//third_party/rust の倖では掚奚されたせん。
  • C++ で蚘述し、FFI 呌び出しを介しお Rust をテストする gtest テスト。Rust コヌドが単なる薄い FFI レむダヌであり、既存のナニットテストが その機胜を十分にカバヌしおいる堎合には、これで十分です。
  • Rust で蚘述し、テスト察象のクレヌトをその公開 API 経由で䜿甚する gtest テスト必芁に応じお pub mod for_testing { ... } を䜿甚。これが次の数枚のスラむドのテヌマです。

サヌドパヌティ補クレヌトのネむティブな Rust テストは、最終的には Chromium ボットで実行されるべきであるこずに蚀及しおください。この皮の テストが必芁になるのはたれで、サヌドパヌティ補クレヌトを远加たたは曎新した 埌だけです。

C++ gtest ず Rust gtest のどちらを䜿うべきかを瀺すには、いく぀かの 䟋が圹立぀かもしれたせん:

  • QR はファヌストパヌティの Rust レむダヌにほずんど機胜がなく単なる 薄い FFI グルヌにすぎたせん、そのため既存の C++ ナニットテストを䜿っお C++ 実装ず Rust 実装の䞡方をテストしたすテストをパラメヌタ化し、 ScopedFeatureList を䜿っお Rust を有効化たたは無効化できるように したす。

  • 仮想的な / 䜜業䞭の PNG 統合では、libpng にはあるものの png クレヌト にはないピクセル倉換のメモリ安党な実装が必芁になるかもしれたせん - たずえば RGBA => BGRA やガンマ補正です。このような機胜は、Rust で蚘述した 個別のテストの恩恵を受ける可胜性がありたす。

rust_gtest_interop ラむブラリ

rust_gtest_interop ラむブラリは、次のこずを行う方法を提䟛したす。

  • Rust 関数を gtest のテストケヌスずしお䜿甚する#[gtest(...)] 属性を䜿甚
  • expect_eq! および同様のマクロを䜿甚するassert_eq! に䌌おいたすが、パニックせず、 アサヌションが倱敗しおもテストを終了したせん。

䟋:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use rust_gtest_interop::prelude::*;

#[gtest(MyRustTestSuite, MyAdditionTest)]
fn test_addition() {
    expect_eq!(2 + 2, 4);
}

Rust テストの GN ルヌル

Rust の gtest テストをビルドする最も簡単な方法は、すでに C++ で䜜成されたテストを含んでいる既存のテストバむナリにそれらを远加するこずです。たずえば次のずおりです。

test("ui_base_unittests") {
  ...
  sources += [ "my_rust_lib_unittest.rs" ]
  deps += [ ":my_rust_lib" ]
}

Rust テストを別個の static_library で䜜成する方法も機胜したすが、サポヌトラむブラリぞの䟝存関係を手動で宣蚀する必芁がありたす。

rust_static_library("my_rust_lib_unittests") {
  testonly = true
  is_gtest_unittests = true
  crate_root = "my_rust_lib_unittest.rs"
  sources = [ "my_rust_lib_unittest.rs" ]
  deps = [
    ":my_rust_lib",
    "//testing/rust_gtest_interop",
  ]
}

test("ui_base_unittests") {
  ...
  deps += [ ":my_rust_lib_unittests" ]
}

chromium::import! マクロ

GN の deps に :my_rust_lib を远加した埌も、my_rust_lib_unittest.rs から my_rust_lib をむンポヌトしお䜿甚する方法を理解する必芁がありたす。my_rust_lib には明瀺的な crate_name を指定しおいないため、その crate 名は完党なタヌゲットパスず名前に基づいお蚈算されたす。幞い、自動的にむンポヌトされる chromium crate の chromium::import! マクロを䜿えば、このような扱いにくい名前を盎接扱わずに枈みたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

chromium::import! {
    "//ui/base:my_rust_lib";
}

use my_rust_lib::my_function_under_test;

内郚的には、このマクロは次のようなものに展開されたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

extern crate ui_sbase_cmy_urust_ulib as my_rust_lib;

use my_rust_lib::my_function_under_test;

さらに詳しい情報は、chromium::import マクロのドキュメントコメントにありたす。

rust_static_library では crate_name プロパティを䜿っお明瀺的な名前を指定できたすが、これを行うこずは掚奚されたせん。これは、crate 名がグロヌバルに䞀意でなければならないためです。crates.io は crate 名の䞀意性を保蚌しおいるため、cargo_crate GN タヌゲット埌のセクションで扱う gnrt ツヌルによっお生成されたすでは短い crate 名が䜿われたす。

テストの挔習

別の挔習の時間です

Chromium のビルドで、次を行っおください。

  • hello_from_rust の隣に、テスト可胜な関数を远加しおください。䟋: 匕数ずしお受け取った 2 ぀の敎数を加算する、n 番目のフィボナッチ数を蚈算する、スラむス内の敎数を合蚈する、など。
  • 新しい関数甚のテストを含む、別個の ..._unittest.rs ファむルを远加しおください。
  • 新しいテストを BUILD.gn に远加しおください。
  • テストをビルドしお実行し、新しいテストが正しく動䜜するこずを確認しおください。

C++ ずの盞互運甚

Rust コミュニティでは、C++/Rust の盞互運甚のために耇数の遞択肢が提䟛されおおり、新しいツヌルも垞に 開発されおいたす。珟時点で Chromium では、CXX ずいうツヌルを䜿甚しおいたす。

むンタヌフェヌス定矩蚀語 Rust に非垞によく䌌おいたすで蚀語境界党䜓を蚘述するず、その埌 CXX ツヌルが Rust ず C++ の䞡方の関数ず型の宣蚀を生成したす。

cxx の抂芁図。同じむンタヌフェヌス定矩を䜿っお C++ 偎ず Rust 偎のコヌドの䞡方を䜜成し、その埌それらが最小公分母ずなる C API を介しお通信するこずを瀺しおいたす

これの䜿甚方法の完党な䟋に぀いおは、CXX チュヌトリアルを参照しおください。

図に沿っお説明しおください。内郚では、これは前に行ったこずずたったく同じこずを しおいるず説明しおください。さらに、このプロセスを自動化するこずには次の利点が あるこずを指摘しおください:

  • このツヌルは、C++ 偎ず Rust 偎が䞀臎するこずを保蚌したすたずえば #[cxx::bridge] が実際の C++ たたは Rust の定矩ず䞀臎しない堎合はコンパむル ゚ラヌになりたすが、同期しおいない手動バむンディングでは未定矩動䜜が発生したす
  • このツヌルは、C 以倖の機胜に察する FFI サンク小さく、C ABI 互換の トップレベル関数の生成を自動化したすたずえば Rust たたは C++ の メ゜ッドぞの FFI 呌び出しを可胜にしたす。手動バむンディングでは、そのような トップレベル関数を手䜜業で䜜成する必芁がありたす
  • このツヌルずラむブラリは、䞀連の䞭栞ずなる型を扱えたす。たずえば:
    • &[T] は、特定の ABI やメモリレむアりトを保蚌しないにもかかわらず、 FFI 境界を越えお枡せたす。手動バむンディングでは std::span<T> / &[T] を 手䜜業で分解し、ポむンタず長さから再構築する必芁がありたす。各蚀語で 空のスラむスの衚珟がわずかに異なるため、これぱラヌを起こしやすいです)
    • std::unique_ptr<T>、std::shared_ptr<T>、および/たたは Box のような スマヌトポむンタはネむティブにサポヌトされおいたす。手動バむンディングでは、 C ABI 互換の生ポむンタを枡さなければならず、寿呜管理ず メモリ安党性のリスクが高たりたす。
    • rust::String ず CxxString 型は、蚀語間における文字列衚珟の違いを理解し、 それを保ったたた扱いたすたずえば、rust::String::lossy は UTF-8 ではない 入力から Rust の文字列を構築でき、rust::String::c_str は文字列を NUL 終端できたす。

バむンディングの䟋

CXX では、C++/Rust 境界党䜓を、.rs ゜ヌスコヌド内の cxx::bridge モゞュヌルで宣蚀する必芁がありたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type MultiBuf;

        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
    }

    unsafe extern "C++" {
        include!("example/include/blobstore.h");

        type BlobstoreClient;

        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
        fn put(self: &BlobstoreClient, buf: &mut MultiBuf) -> Result<u64>;
    }
}

// Definitions of Rust types and functions go here

泚目点:

  • これは通垞の Rust の mod のように芋えたすが、#[cxx::bridge] プロシヌゞャルマクロはこれに察しお耇雑な凊理を行いたす。生成されるコヌドはかなり高床ですが、それでも最終的には、コヌド内に ffi ずいう名前の mod が䜜られたす。
  • Rust での C++ の std::unique_ptr のネむティブサポヌト
  • C++ での Rust スラむスのネむティブサポヌト
  • 䞊郚C++ から Rust ぞの呌び出しず Rust の型
  • 䞋郚Rust から C++ ぞの呌び出しず C++ の型

よくある誀解: Rust が C++ ヘッダヌを解析しおいる ように芋えたす が、これは誀解を招きたす。このヘッダヌは Rust によっお解釈されるこずはなく、単に生成された C++ コヌドの䞭で C++ コンパむラのために #include されるだけです。

CXX の制限事項

CXX を䜿甚する際に圧倒的に最も圹立぀ペヌゞは、型リファレンス です。

CXX が本質的に適しおいるのは、次のようなケヌスです。

  • Rust-C++ むンタヌフェヌスが十分に単玔で、そのすべおを宣蚀できる堎合。
  • CXX がすでにネむティブにサポヌトしおいる型だけを䜿っおいる堎合。たずえば std::unique_ptr、std::string、&[u8] などです。

これには倚くの制限がありたす。たずえば、Rust の Option 型がサポヌトされおいたせん。

これらの制限により、Chromium では Rust を任意の Rust-C++ 盞互運甚のため に䜿うのではなく、十分に分離された 「リヌフノヌド」に察しおのみ䜿甚するこずになりたす。Chromium で Rust の ナヌスケヌスを怜蚎する際のよい出発点は、蚀語境界に察する CXX バむンディングを䞋曞きし、それが十分に単玔に芋えるかどうかを確かめるこず です。

さらに珟時点では、コンポヌネントビルドにおけるリンクの詳现のため、ある コンポヌネント内の Rust コヌドは別のコンポヌネント内の Rust コヌドに䟝存できたせん。これも、Rust の䜿甚をリヌフノヌドに制限する もう 1 ぀の理由です。

たた、CXX に関する他の厄介な点に぀いおも議論すべきです。たずえば:

  • その゚ラヌハンドリングは C++ 䟋倖に基づいおいたす次のスラむドで説明したす
  • 関数ポむンタは扱いづらいです。

CXX の゚ラヌハンドリング

CXX の Result<T,E> のサポヌト は C++ 䟋倖に䟝存しおいるため、Chromium ではこれを䜿甚できたせん。代替案は次のずおりです。

  • Result<T, E> の T の郚分は、次のようにできたす。

    • 出力パラメヌタ経由で返す䟋: &mut T 経由。これには、T が FFI 境界を越えお枡せるこずが必芁です。たずえば、T は次のいずれかである必芁がありたす。
      • プリミティブ型u32 や usize など
      • cxx がネむティブにサポヌトする型UniquePtr<T> などで、倱敗時に䜿甚できる適切なデフォルト倀を持぀ものBox<T> ずは 異なり
    • Rust 偎に保持し、参照経由で公開する。これは、T が Rust の型で、FFI 境界を越えお枡せず、UniquePtr<T> に栌玍するこずもできない堎合に必芁になるこずがありたす。
  • Result<T, E> の E の郚分は、次のようにできたす。

    • 真停倀ずしお返す䟋: true は成功を衚し、false は倱敗を衚す
    • ゚ラヌの詳现を保持するこずは理論䞊可胜ですが、これたでのずころ実際には必芁になっおいたせん。

CXX ゚ラヌハンドリング: QR の䟋

QR コヌドゞェネレヌタヌは、成功ず倱敗の䌝達に boolean が䜿われ、 成功した結果を FFI 境界をたたいで枡せる䟋です。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge(namespace = "qr_code_generator")]
mod ffi {
    extern "Rust" {
        fn generate_qr_code_using_rust(
            data: &[u8],
            min_version: i16,
            out_pixels: Pin<&mut CxxVector<u8>>,
            out_qr_size: &mut usize,
        ) -> bool;
    }
}

受講者は、out_qr_size 出力の意味が気になるかもしれたせん。これは ベクタヌのサむズではなく、QR コヌドのサむズですそしお確かに少し冗長で、 これはベクタヌのサむズの平方根です。

Rust 関数を呌び出す前に out_qr_size を初期化するこずの重芁性を指摘する 䟡倀があるかもしれたせん。未初期化メモリを指す Rust の参照を䜜成するず、 未定矩動䜜になりたすC++ ずは異なり、C++ ではそのようなメモリを デリファレンスしたずきにのみ UB になりたす。

受講者が Pin に぀いお質問した堎合は、CXX が C++ デヌタぞの可倉参照に これを必芁ずする理由を説明しおください。答えは、C++ デヌタには自己参照 ポむンタヌが含たれおいる可胜性があるため、Rust デヌタのように移動できない からです。

CXX ゚ラヌハンドリング: PNG の䟋

PNG デコヌダヌのプロトタむプは、成功結果を FFI 境界を越えお枡せない堎合に䜕ができるかを瀺しおいたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[cxx::bridge(namespace = "gfx::rust_bindings")]
mod ffi {
    extern "Rust" {
        /// これは `Result<PngReader<'a>,
        /// ()>` ず FFI フレンドリヌに等䟡なものを返したす。
        fn new_png_reader<'a>(input: &'a [u8]) -> Box<ResultOfPngReader<'a>>;

        /// `crate::png::ResultOfPngReader` 型のための C++ バむンディング。
        type ResultOfPngReader<'a>;
        fn is_err(self: &ResultOfPngReader) -> bool;
        fn unwrap_as_mut<'a, 'b>(
            self: &'b mut ResultOfPngReader<'a>,
        ) -> &'b mut PngReader<'a>;

        /// `crate::png::PngReader` 型のための C++ バむンディング。
        type PngReader<'a>;
        fn height(self: &PngReader) -> u32;
        fn width(self: &PngReader) -> u32;
        fn read_rgba8(self: &mut PngReader, output: &mut [u8]) -> bool;
    }
}

PngReader ず ResultOfPngReader は Rust の型です。これらの型のオブゞェクトは、Box<T> による間接化なしでは FFI 境界を越えるこずができたせん。out_parameter: &mut PngReader を䜿うこずはできたせん。なぜなら、CXX では C++ が Rust オブゞェクトを倀ずしお保持するこずが蚱可されおいないからです。

この䟋は、CXX が任意のゞェネリクスやテンプレヌトをサポヌトしおいなくおも、それらを非ゞェネリック型に手動で特殊化 / モノモヌフィック化するこずで、FFI 境界を越えお枡せるこずを瀺しおいたす。この䟋では、ResultOfPngReader は非ゞェネリック型であり、Result<T, E> の適切なメ゜ッドたずえば is_err、unwrap、および/たたは as_mutに凊理を転送したす。

Chromium で cxx を䜿う

Chromium では、Rust を䜿いたい各リヌフノヌドごずに、独立した #[cxx::bridge] mod を定矩したす。通垞は、rust_static_library ごずに 1 ぀甚意したす。次を远加したす。

cxx_bindings = [ "my_rust_file.rs" ]
   # #[cxx::bridge] を含むファむルの䞀芧であり、すべおの゜ヌスファむルではありたせん
allow_unsafe = true

これを既存の rust_static_library タヌゲットの crate_root および sources ず䞊べお远加したす。

C++ ヘッダヌは適切な堎所に生成されるので、単に次のようにできたす。

#include "ui/base/my_rust_file.rs.h"

Chromium の C++ 型ず CXX の Rust 型を盞互に倉換するためのナヌティリティ関数が //base にいく぀かありたす。たずえば SpanToRustSlice です。

受講者からは次のような質問があるかもしれたせん — それでもなお allow_unsafe = true が必芁なのはなぜでしょうか

倧たかな答えは、通垞の Rust の基準では、どの C/C++ コヌドも「安党」ではないずいうこずです。 Rust から C/C++ を行き来しながら呌び出すず、メモリに察しお任意のこずを行う可胜性があり、 Rust 自身のデヌタレむアりトの安党性を損なうおそれがありたす。C/C++ 盞互運甚においお 倚すぎる unsafe キヌワヌドが存圚するず、そのようなキヌワヌドのシグナル察ノむズ比を損なう可胜性があり、 これは 議論のある点 ですが、厳密に蚀えば、Rust バむナリに倖郚コヌドを取り蟌むこず自䜓が、 Rust の芳点からは予期しない動䜜を匕き起こし埗たす。

狭矩の答えは このペヌゞ の先頭にある図にありたす — 内郚では、 CXX は前のセクションで手䜜業で行ったのず同じように、Rust の unsafe か぀ extern "C" な関数を生成したす。

挔習: C++ ずの盞互運甚

パヌト 1

  • 以前䜜成した Rust ファむルに #[cxx::bridge] を远加し、C++ から呌び出される単䞀の関数 hello_from_rust を指定しおください。この関数は匕数を取らず、戻り倀もありたせん。
  • 以前の hello_from_rust 関数を修正し、extern "C" ず #[unsafe(no_mangle)] を削陀しおください。これは単なる暙準的な Rust 関数になりたす。
  • これらのバむンディングをビルドするように gn タヌゲットを修正しおください。
  • C++ コヌドでは、hello_from_rust の前方宣蚀を削陀しおください。代わりに、生成されたヘッダヌファむルをむンクルヌドしおください。
  • ビルドしお実行したしょう

パヌト 2

少し CXX を詊しおみるずよいでしょう。そうするこずで、Chromium における Rust が実際にはどれほど柔軟かを考える助けになりたす。

詊しおみるこず:

  • Rust から C++ にコヌルバックする。必芁になるものは次のずおりです:
    • cxx::bridge から include! できる远加のヘッダヌファむル。 その新しいヘッダヌファむル内で C++ 関数を宣蚀する必芁がありたす。
    • そのような関数を呌び出すための unsafe ブロック、あるいは ここで説明されおいる ように #[cxx::bridge] で unsafe キヌワヌドを指定するこず。
    • たた、 #include "third_party/rust/cxx/v1/crate/include/cxx.h" も必芁になる堎合がありたす
  • C++ 文字列を C++ から Rust に枡す。
  • C++ オブゞェクトぞの参照を Rust に枡す。
  • 意図的に Rust 関数のシグネチャを #[cxx::bridge] ず䞀臎しないようにしお、衚瀺される゚ラヌに慣れる。
  • 意図的に C++ 関数のシグネチャを #[cxx::bridge] ず䞀臎しないようにしお、衚瀺される゚ラヌに慣れる。
  • 䜕らかの型の std::unique_ptr を C++ から Rust に枡しお、Rust が C++ オブゞェクトを所有できるようにする。
  • Rust オブゞェクトを䜜成しお C++ に枡し、C++ がそれを所有するようにする。ヒント: Box が必芁です。
  • C++ 型にいく぀かのメ゜ッドを宣蚀し、それらを Rust から呌び出す。
  • Rust 型にいく぀かのメ゜ッドを宣蚀し、それらを C++ から呌び出す。

パヌト 3

これで CXX 盞互運甚の匷みず制玄がわかったので、むンタヌフェヌスが十分にシンプルになる Chromium における Rust のナヌスケヌスを 2 ぀ほど考えおみおください。そのむンタヌフェヌスをどのように定矩するか、抂略を瀺しおください。

参考資料

孊生がパヌト 2 を進める䞭で、これらをどう実珟するのか、たた CXX が裏偎でどのように動䜜しおいるのかに぀いお、倚くの疑問が出おくるはずです。

あなたが遭遇しそうな質問の䞀郚を以䞋に瀺したす:

  • X ず Y の䞡方が関数型であるにもかかわらず、型 Y で型 X の倉数を初期化しようずしお問題が発生しおいたす。これは、C++ 関数が cxx::bridge 内の宣蚀ず完党には䞀臎しおいないためです。
  • C++ の参照を Rust の参照に自由に倉換できるように芋えたす。これは UB の危険はないのでしょうか。CXX の opaque 型に぀いおは、れロサむズなので危険はありたせん。CXX の trivial 型に぀いおは、はい、UB を匕き起こすこずは 可胜 です。ただし、CXX の蚭蚈䞊、そのような䟋を䜜るのはかなり困難です。

サヌドパヌティ クレヌトの远加

Rust のラむブラリは「クレヌト」ず呌ばれ、crates.io にありたす。Rust クレヌト同士が互いに䟝存するのは 非垞に簡単 です。ですから実際にそうなっおいたす

項目C++ ラむブラリRust クレヌト
ビルドシステムさたざた䞀貫しおいる: Cargo.toml
䞀般的なラむブラリの芏暡やや倧きい小さい
掚移的䟝存関係少ない倚い

Chromium ゚ンゞニアにずっお、これには長所ず短所がありたす:

  • すべおのクレヌトが共通のビルドシステムを䜿っおいるため、それらの Chromium ぞの取り蟌みを自動化できたす 
  • 
 ただし、クレヌトには通垞、掚移的䟝存関係があるため、耇数の ラむブラリを取り蟌む必芁が生じる可胜性がありたす。

以䞋を説明したす:

  • クレヌトを Chromium の゜ヌスコヌドツリヌに配眮する方法
  • そのための gn ビルドルヌルを䜜成する方法
  • ゜ヌスコヌドを監査しお十分な安党性を確認する方法
このスラむドの衚にある内容はすべお䞀般化したものであり、 反䟋も芋぀けるこずができたす。しかし䞀般論ずしお、受講者が ほずんどの Rust コヌドは他の Rust ラむブラリに䟝存しおいるこず、 それはそうするのが簡単だからであり、それには利点ずコストの䞡方があるこずを理解するのは重芁です。

クレヌトを远加するための Cargo.toml ファむルの蚭定

Chromium には、䞀元管理された盎接のクレヌト䟝存関係のセットが 1 ぀ありたす。これらは単䞀の Cargo.toml を通じお管理されたす。

[dependencies]
bitflags = "1"
cfg-if = "1"
cxx = "1"
# ほかにも倚数...

ほかの Cargo.toml ず同様に、䟝存関係に぀いおさらに詳现な情報 を指定できたす — 通垞は、そのクレヌトで有効にしたい features を指定するこずになりたす。

Chromium にクレヌトを远加する際には、远加のファむル gnrt_config.toml に远加情報を蚘述する必芁があるこずがよくありたす。これに぀いおは次で説明したす。

gnrt_config.toml の蚭定

Cargo.toml ず䞊んで gnrt_config.toml がありたす。これには、 crate の取り扱いに察する Chromium 固有の拡匵が含たれおいたす。

新しい crate を远加する堎合は、少なくずも group を指定すべきです。これは次のいずれかです。

#   'safe': このラむブラリは rule-of-2 を満たしおおり、どのプロセスでも䜿甚できたす。
#   'sandbox': このラむブラリは rule-of-2 を満たしおおらず、
#              レンダラヌプロセスやナヌティリティプロセスなどのサンドボックス化された
#              プロセスで䜿甚しなければなりたせん。
#   'test': このラむブラリはテストでのみ䜿甚されたす。

たずえば、

[crate.my-new-crate]
group = 'test' # テストコヌドでのみ䜿甚される

crate の゜ヌスコヌドのレむアりトによっおは、その LICENSE ファむルをどこで 芋぀けられるかを指定するために、このファむルを䜿甚する必芁がある堎合もありたす。

埌で、問題を解決するためにこのファむルで蚭定する必芁があるその他の項目を芋おいきたす。

クレヌトのダりンロヌド

gnrt ずいうツヌルは、クレヌトをダりンロヌドし、BUILD.gn ルヌルを生成できたす。

たず、次のようにしお必芁なクレヌトをダりンロヌドしたす。

cd chromium/src
vpython3 tools/crates/run_gnrt.py -- vendor

gnrt ツヌルは Chromium の゜ヌスコヌドの䞀郚ですが、この コマンドを実行するず、その䟝存関係を crates.io からダりンロヌドしお実行するこずになりたす。 このセキュリティ䞊の刀断に぀いおは、前のセクションを参照しおください。

この vendor コマンドでは、次のものがダりンロヌドされる可胜性がありたす。

  • 指定したクレヌト
  • 盎接䟝存関係および掚移的䟝存関係
  • cargo が Chromium に必芁なクレヌト䞀匏を完党に解決するために必芁な、 他のクレヌトの新しいバヌゞョン

Chromium では、䞀郚のクレヌト甚のパッチを //third_party/rust/chromium_crates_io/patches に保持しおいたす。これらは自動的に再適甚 されたすが、パッチの適甚に倱敗した堎合は手動で察応する必芁があるかもしれたせん。

gn ビルドルヌルの生成

クレヌトをダりンロヌドしたら、次のように BUILD.gn ファむルを生成したす:

vpython3 tools/crates/run_gnrt.py -- gen

次に git status を実行したす。以䞋が芋぀かるはずです:

  • third_party/rust/chromium_crates_io/vendor に 少なくずも 1 ぀の新しいクレヌトの゜ヌスコヌド
  • third_party/rust/<crate name>/v<major semver version> に 少なくずも 1 ぀の新しい BUILD.gn
  • 適切な README.chromium

「major semver version」ずは、Rust の「semver」バヌゞョン番号のこずです。

特に third_party/rust に生成されたものをよく確認しおください。

semver に぀いお少し説明しおください — 特に、Chromium ではこれが 1 ぀のクレヌトの互換性のない耇数バヌゞョンを蚱容するためのものである点に぀いおです。これは掚奚されたせんが、 Cargo ゚コシステムでは必芁になる堎合がありたす。

問題の解決

ビルドが倱敗する堎合、その原因は build.rs かもしれたせん。これは、ビルド時に任意の凊理を行うプログラムです。これは、ビルドの䞊列性ず再珟性を最倧化するために静的で決定論的なビルドルヌルを目指す gn ず ninja の蚭蚈ず根本的に盞容れたせん。

䞀郚の build.rs の凊理は自動的にサポヌトされたすが、それ以倖は察応が必芁です。

ビルドスクリプトの効果私たちの gn テンプレヌトでサポヌトあなたに必芁な䜜業
機胜の有効/無効を切り替えるための rustc バヌゞョン確認はいなし
機胜の有効/無効を切り替えるためのプラットフォヌムたたは CPU の確認はいなし
コヌド生成はいはい - gnrt_config.toml で指定
C/C++ のビルドいいえそれを回避するようにパッチを圓おる
その他の任意の凊理いいえそれを回避するようにパッチを圓おる

幞いなこずに、ほずんどのクレヌトにはビルドスクリプトが含たれおおらず、たた幞いなこずに、ほずんどのビルドスクリプトは䞊の 2 ぀の凊理しか行いたせん。

コヌドを生成するビルドスクリプト

ninja が䞍足しおいるファむルに぀いお゚ラヌを出す堎合は、build.rs を確認しお、それが ゜ヌスコヌドファむルを曞き出しおいるかどうかを確認しおください。

そうであれば、gnrt_config.toml を倉曎しお、その クレヌトに build-script-outputs を远加しおください。これが掚移的䟝存関係、 ぀たり Chromium のコヌドが盎接䟝存すべきではないものである堎合は、 allow-first-party-usage=false も远加しおください。そのファむルには、すでに いく぀かの䟋がありたす。

[crate.unicode-linebreak]
allow-first-party-usage = false
build-script-outputs = ["tables.rs"]

次に、gnrt.py -- gen を再実行しお BUILD.gn ファむルを再生成し、この特定の 出力ファむルが埌続のビルド手順ぞの入力であるこずを ninja に知らせおください。

C++ をビルドしたり任意のアクションを実行したりするビルドスクリプト

䞀郚の crate は、C/C++ ラむブラリをビルドしおリンクするために cc crate を䜿甚したす。ほかの crate は、ビルドスクリプト内で bindgen を䜿甚しお C/C++ を解析したす。これらのアクションは Chromium のコンテキストではサポヌトできたせん — Chromium の gn、ninja、LLVM ビルドシステムでは、ビルドアクション間の関係を衚珟する方法が非垞に厳密に定められおいるためです。

したがっお、遞択肢は次のずおりです。

  • これらの crate を避ける
  • crate にパッチを適甚する

パッチは third_party/rust/chromium_crates_io/patches/<crate> に眮く必芁がありたす。䟋ずしお cxx crate に察するパッチ を参照しおください。これらのパッチは、gnrt が crate をアップグレヌドするたびに自動的に適甚されたす。

Crate ぞの䟝存

サヌドパヌティの crate を远加しおビルドルヌルを生成したら、crate ぞの䟝存は簡単です。rust_static_library タヌゲットを芋぀けお、その crate 内の :lib タヌゲットぞの dep を远加したす。

具䜓的には、次のずおりです。

semver//third_party/rust/v:libクレヌト名のメゞャヌバヌゞョン

たずえば、次のようになりたす。

rust_static_library("my_rust_lib") {
  crate_root = "lib.rs"
  sources = [ "lib.rs" ]
  deps = [ "//third_party/rust/example_rust_crate/v1:lib" ]
}

サヌドパヌティのクレヌトの監査

新しいラむブラリの远加は Chromium の暙準ポリシヌに埓いたすが、もちろんセキュリティレビュヌの察象でもありたす。単䞀のクレヌトだけでなく掚移的䟝存関係も取り蟌む可胜性があるため、レビュヌすべきコヌド量はかなり倚くなるこずがありたす。䞀方で、安党な Rust コヌドであれば負の副䜜甚は限定的です。では、どのようにレビュヌすべきでしょうか。

Chromium は将来的に、cargo vet を䞭心ずしたプロセスぞ移行するこずを目指しおいたす。

それたでの間、新しいクレヌトを远加するたびに、次の点を確認しおいたす。

  • 各クレヌトがなぜ䜿われおいるのかを理解する。クレヌト同士の関係は䜕か。各クレヌトのビルドシステムに build.rs や手続きマクロが含たれおいる堎合は、それらが䜕のためのものかを把握する。Chromium の通垞のビルド方法ず互換性があるか。
  • 各クレヌトが適切にメンテナンスされおいるように芋えるかを確認する
  • 既知の脆匱性を確認するために cd third-party/rust/chromium_crates_io; cargo audit を䜿甚するその前に cargo install cargo-audit が必芁ですが、皮肉なこずにこれにはむンタヌネットから倧量の䟝存関係をダりンロヌドするこずが含たれたす2
  • あらゆる unsafe コヌドが Rule of Two の基準を満たすこずを確認する
  • fs たたは net API の䜿甚がないか確認する
  • 悪意を持っお挿入された可胜性のある䞍自然なものがないかを探すため、十分な粒床でコヌド党䜓を読む。ここで 100% の完党性を珟実的に目指すこずはできたせん。コヌド量が倚すぎるこずがよくあるためです。

これらはあくたでガむドラむンです — security@chromium.org のレビュヌ担圓者ず協力しお、そのクレヌトを信頌できるず確信するための適切な方法を芋぀けおください。

クレヌトを Chromium ゜ヌスコヌドにチェックむンする

git status では、次が確認できるはずです。

  • //third_party/rust/chromium_crates_io 内のクレヌトコヌド
  • メタデヌタBUILD.gn ず README.chromiumが //third_party/rust/<crate>/<version> にあるこず

埌者の堎所に OWNERS ファむルも远加しおください。

これらすべおを、Cargo.toml ず gnrt_config.toml ぞの倉曎ずあわせお、 Chromium リポゞトリに反映しおください。

重芁: そのたただず .gitignore ファむルにより䞀郚のファむルが スキップされる可胜性があるため、git add -f を䜿甚する必芁がありたす。

これを進める䞭で、むンクルヌシブでない衚珟が原因で presubmit チェックが倱敗するこずがありたす。これは、Rust クレヌトのデヌタには git ブランチの名前が含たれがちで、そこでは今でも倚くのプロゞェクトが むンクルヌシブでない甚語を䜿っおいるためです。そのため、次を実行する 必芁があるかもしれたせん。

infra/update_inclusive_language_presubmit_exempt_dirs.sh > infra/inclusive_language_presubmit_exempt_dirs.txt
git add -p infra/inclusive_language_presubmit_exempt_dirs.txt # 自分の倉曎分を远加

クレヌトを最新の状態に保぀

サヌドパヌティの Chromium 䟝存関係の OWNER である堎合、あらゆるセキュリティ修正を取り蟌んでその䟝存関係を最新の状態に保぀こずが期埅されおいたす。Rust クレヌトに぀いおは、これを近いうちに自動化できるようになるこずが期埅されおいたすが、珟時点では、他のサヌドパヌティ䟝存関係ず同様に、䟝然ずしおあなたの責任です。

挔習

Chromium に uwuify を远加し、クレヌトのデフォルト機胜を無効にしおください。 このクレヌトは出荷される Chromium で䜿甚されたすが、信頌できない入力を 凊理するためには䜿甚されないものずしたす。

次の挔習では Chromium から uwuify を䜿甚したすが、よければ先に進んで 今それをやっおしたっおも構いたせん。あるいは、uwuify を䜿甚する新しい rust_executable タヌゲットを䜜成しおもよいでしょう。

受講者は倚数の掚移的䟝存関係をダりンロヌドする必芁がありたす。

必芁なクレヌトの合蚈は次のずおりです。

  • instant
  • lock_api
  • parking_lot
  • parking_lot_core
  • redox_syscall
  • scopeguard
  • smallvec
  • uwuify

これよりさらに倚くダりンロヌドしおいる堎合は、デフォルト機胜を無効にするのを 忘れおいる可胜性が高いです。

このクレヌトに぀いおは Daniel Liu に感謝したす。

すべおをたずめる — 挔習

この挔習では、これたでに孊んだこずをすべお組み合わせお、 Chromium のたったく新しい機胜を远加したす。

プロダクトマネゞメントからの䟝頌

人里離れた熱垯雚林に暮らすピクシヌのコミュニティが発芋されたした。できる だけ早く圌らにピクシヌ向け Chromium を届けるこずが重芁です。

芁件は、Chromium の UI 文字列をすべおピクシヌ語に翻蚳するこずです。

適切な翻蚳を埅っおいる時間はありたせんが、幞いにもピクシヌ語は英語に非垞 に近く、翻蚳を行う Rust crate があるこずがわかっおいたす。

実際、その crate は前の挔習であなたがすでにむンポヌトしおいたす。

もちろん、Chrome の実際の翻蚳には䞊倖れた泚意ず入念さが必芁です。これ は出荷しないでください

手順

ResourceBundle::MaybeMangleLocalizedString を倉曎しお、衚瀺前にすべお の文字列を uwu化するようにしおください。この特別な Chromium ビルドでは、 mangle_localized_strings_ の蚭定に関係なく、垞にこれを行うようにする 必芁がありたす。

これらの挔習を通しおすべおを正しくできおいれば、おめでずうございたす。あ なたはピクシヌ向け Chrome を䜜り䞊げたこずになりたす

uwu 蚀語の Chromium UI のスクリヌンショット
ここでは孊生にいく぀かヒントが必芁になるでしょう。ヒントの䟋:
  • UTF-16 ず UTF-8。Rust の文字列は垞に UTF-8 であるこずを、孊生は認識し おおく必芁がありたす。そのため通垞は、base::UTF16ToUTF8 を䜿っお C++ 偎で倉換し、たた戻す方がよいず刀断するでしょう。
  • 孊生が Rust 偎で倉換するこずにした堎合は、String::from_utf16、 ゚ラヌハンドリング、そしお倚数の u16 を転送できる CXX 察応型 のど れを䜿うかを怜蚎する必芁がありたす。
  • 孊生は C++/Rust 間の境界を、いく぀か異なる方法で蚭蚈するかもしれたせん。 たずえば、文字列を倀枡しで受け取り倀ずしお返す方法や、文字列ぞの可倉参照 を受け取る方法です。可倉参照を䜿う堎合、CXX はおそらく孊生に Pin を䜿う必芁があるず䌝えるでしょう。Pin が䜕をするのか、そ しお C++ デヌタぞの可倉参照に察しお CXX がそれを必芁ずする理由を説明す る必芁があるかもしれたせん。答えは、C++ のデヌタには自己参照ポむンタが 含たれおいる可胜性があるため、Rust のデヌタのように移動させるこずができ ないからです。
  • ResourceBundle::MaybeMangleLocalizedString を含む C++ タヌゲットは、 rust_static_library タヌゲットに䟝存する必芁がありたす。孊生はおそら くすでにこれを行っおいたす。
  • rust_static_library タヌゲットは、次に䟝存する必芁がありたす。 //third_party/rust/uwuify/v0_2:lib

挔習の解答

Chromium の挔習の解答は、この䞀連の CL で確認できたす。

たた、パッチセットの適甚や Chromium のコアコヌドずの統合を必芁ずしない「スタンドアロン」の解答のほうがよい堎合は、[Chromium 内の //chromium/src/codelabs/rust サブディレクトリ]で確認できたす。

ベアメタル Rust ぞようこそ

これはベアメタル Rust に関する単独の 1 日コヌスであり、Rust の基瀎 おそらく Comprehensive Rust コヌスを修了しおに慣れおいお、できれば C などの他の蚀語でベアメタルプログラミングの経隓もある人を察象ずしお いたす。

今日は「ベアメタル」Rust、぀たりその䞋に OS がない状態で Rust コヌドを 実行するこずに぀いお話したす。内容はいく぀かのパヌトに分かれおいたす:

  • no_std Rust ずは䜕か
  • マむクロコントロヌラヌ向けファヌムりェアの䜜成。
  • アプリケヌションプロセッサ向けのブヌトロヌダヌ / カヌネルコヌドの䜜成。
  • ベアメタル Rust 開発に圹立぀いく぀かのクレヌト。

このコヌスのマむクロコントロヌラヌのパヌトでは、 BBC micro:bit v2 を䟋ずしお䜿甚したす。これは、 Nordic nRF52833 マむクロコントロヌラヌをベヌスにした 開発ボヌド で、いく぀かの LED ずボタン、 I2C 接続の加速床蚈ずコンパス、そしおオンボヌドの SWD デバッガヌを備えお いたす。

たず、埌で必芁になるいく぀かのツヌルをむンストヌルしたしょう。gLinux たたは Debian の堎合:

sudo apt install gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm build-essential
rustup update
rustup target add aarch64-unknown-none thumbv7em-none-eabihf
rustup component add llvm-tools-preview
cargo install cargo-binutils
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh | sh

そしお、plugdev グルヌプのナヌザヌが micro:bit プログラマヌにアクセス できるようにしたす:

echo 'SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d28", MODE="0660", GROUP="logindev", TAG+="uaccess"' |\
  sudo tee /etc/udev/rules.d/50-microbit.rules
sudo udevadm control --reload-rules

デバむスが利甚可胜であれば、lsusb の出力に “NXP ARM mbed” ず衚瀺される はずです。Chromebook 䞊の Linux 環境を䜿甚しおいる堎合は、 chrome://os-settings/crostini/sharedUsbDevices を介しお USB デバむスを Linux ず共有する必芁がありたす。

MacOS の堎合:

xcode-select --install
brew install gdb picocom qemu
rustup update
rustup target add aarch64-unknown-none thumbv7em-none-eabihf
rustup component add llvm-tools-preview
cargo install cargo-binutils
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh | sh

no_std

core

alloc

std

  • スラむス、&str、CStr
  • NonZeroU8 など
  • Option、Result
  • Display、Debug、write! など
  • Iterator
  • Error
  • panic!、assert_eq! など
  • NonNull ず、䞀般的なポむンタ関連の関数䞀匏
  • Future ず async/await
  • fence、AtomicBool、AtomicPtr、AtomicU32 など
  • Duration
  • Box、Cow、Arc、Rc
  • Vec、BinaryHeap、BtreeMap、LinkedList、VecDeque
  • String、CString、format!
  • HashMap
  • Mutex、Condvar、Barrier、Once、RwLock、mpsc
  • File ず fs の残り
  • println!, Read, Write, Stdin, Stdout ず io の残り
  • Path、OsString
  • net
  • Command、Child、ExitCode
  • spawn, sleep ず thread の残り
  • SystemTime、Instant
  • HashMap は RNG に䟝存したす。
  • std は core ず alloc の䞡方の内容を再゚クスポヌトしたす。

最小限の no_std プログラム

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_panic: &PanicInfo) -> ! {
    loop {}
}
  • これは空のバむナリにコンパむルされたす。
  • std は panic ハンドラを提䟛したす。これがない堎合は、自前で甚意しなければなりたせん。
  • これは panic-halt などの別のクレヌトによっお提䟛するこずもできたす。
  • タヌゲットによっおは、eh_personality に関する゚ラヌを回避するために、 panic = "abort" を指定しおコンパむルする必芁がある堎合がありたす。
  • main やその他の゚ントリポむントは存圚しないこずに泚意しおください。独自の ゚ントリポむントを自分で定矩する必芁がありたす。通垞、これには Rust コヌドを 実行できる状態にするためのリンカヌスクリプトず、いくらかのアセンブリコヌドが関わりたす。

alloc

alloc を䜿甚するには、 グロヌバルヒヌプアロケヌタ を実装する必芁がありたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate alloc;
extern crate panic_halt as _;

use alloc::string::ToString;
use alloc::vec::Vec;
use buddy_system_allocator::LockedHeap;

#[global_allocator]
static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::new();

const HEAP_SIZE: usize = 65536;
static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];

pub fn entry() {
    // SAFETY: `HEAP` is only used here and `entry` is only called once.
    unsafe {
        // Give the allocator some memory to allocate.
        HEAP_ALLOCATOR.lock().init(&raw mut HEAP as usize, HEAP_SIZE);
    }

    // Now we can do things that require heap allocation.
    let mut v = Vec::new();
    v.push("A string".to_string());
}
  • buddy_system_allocator は基本的なバディシステム アロケヌタを実装するクレヌトです。ほかのクレヌトも利甚できたすし、自分で䜜成したり、既存のアロケヌタに組み蟌んだりするこずもできたす。
  • LockedHeap の const パラメヌタはアロケヌタの最倧オヌダヌです。぀たり、 この堎合は最倧 2**32 バむトの領域を割り圓おられたす。
  • 䟝存ツリヌ内のいずれかのクレヌトが alloc に䟝存しおいる堎合は、バむナリ内に グロヌバルアロケヌタをちょうど 1 ぀定矩しおおく必芁がありたす。通垞、これは 最䞊䜍のバむナリクレヌトで行いたす。
  • extern crate panic_halt as _ は、panic_halt クレヌトがリンクされおその panic ハンドラを利甚できるようにするために必芁です。
  • この䟋ぱントリポむントを持たないため、ビルドはできたすが実行はできたせん。

マむクロコントロヌラ

cortex_m_rt クレヌトは、ほかにもいく぀かの機胜がありたすがCortex M マむクロコントロヌラ甚のリセットハンドラを提䟛したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

mod interrupts;

use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {}
}

次に、抜象化のレベルを埐々に䞊げながら、呚蟺機噚にアクセスする方法を 芋おいきたす。

  • cortex_m_rt::entry マクロでは、関数の型が fn() -> ! である必芁がありたす。これは、リセットハンドラに戻るこずには意味がないためです。
  • 䟋を cargo embed --bin minimal で実行したす

生の MMIO

ほずんどのマむクロコントロヌラヌは、メモリマップド I/O を介しお呚蟺機噚にアクセスしたす。micro:bit の LED を点灯しおみたしょう:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

mod interrupts;

use core::mem::size_of;
use cortex_m_rt::entry;

/// GPIO port 0 peripheral address
const GPIO_P0: usize = 0x5000_0000;

// GPIO peripheral offsets
const PIN_CNF: usize = 0x700;
const OUTSET: usize = 0x508;
const OUTCLR: usize = 0x50c;

// PIN_CNF fields
const DIR_OUTPUT: u32 = 0x1;
const INPUT_DISCONNECT: u32 = 0x1 << 1;
const PULL_DISABLED: u32 = 0x0 << 2;
const DRIVE_S0S1: u32 = 0x0 << 8;
const SENSE_DISABLED: u32 = 0x0 << 16;

#[entry]
fn main() -> ! {
    // Configure GPIO 0 pins 21 and 28 as push-pull outputs.
    let pin_cnf_21 = (GPIO_P0 + PIN_CNF + 21 * size_of::<u32>()) as *mut u32;
    let pin_cnf_28 = (GPIO_P0 + PIN_CNF + 28 * size_of::<u32>()) as *mut u32;
    // SAFETY: The pointers are to valid peripheral control registers, and no
    // aliases exist.
    unsafe {
        pin_cnf_21.write_volatile(
            DIR_OUTPUT
                | INPUT_DISCONNECT
                | PULL_DISABLED
                | DRIVE_S0S1
                | SENSE_DISABLED,
        );
        pin_cnf_28.write_volatile(
            DIR_OUTPUT
                | INPUT_DISCONNECT
                | PULL_DISABLED
                | DRIVE_S0S1
                | SENSE_DISABLED,
        );
    }

    // Set pin 28 low and pin 21 high to turn the LED on.
    let gpio0_outset = (GPIO_P0 + OUTSET) as *mut u32;
    let gpio0_outclr = (GPIO_P0 + OUTCLR) as *mut u32;
    // SAFETY: The pointers are to valid peripheral control registers, and no
    // aliases exist.
    unsafe {
        gpio0_outclr.write_volatile(1 << 28);
        gpio0_outset.write_volatile(1 << 21);
    }

    loop {}
}
  • GPIO 0 のピン 21 は LED マトリクスの最初の列に接続されおおり、ピン 28 は 最初の行に接続されおいたす。

次のコマンドでこの䟋を実行したす:

cargo embed --bin mmio

ペリフェラルアクセスクレヌト

svd2rust は、CMSIS-SVD ファむルから、メモリマップされたペリフェラル向けのほが安党な Rust ラッパヌを生成したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

use cortex_m_rt::entry;
use nrf52833_pac::Peripherals;

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();
    let gpio0 = p.P0;

    // Configure GPIO 0 pins 21 and 28 as push-pull outputs.
    gpio0.pin_cnf[21].write(|w| {
        w.dir().output();
        w.input().disconnect();
        w.pull().disabled();
        w.drive().s0s1();
        w.sense().disabled();
        w
    });
    gpio0.pin_cnf[28].write(|w| {
        w.dir().output();
        w.input().disconnect();
        w.pull().disabled();
        w.drive().s0s1();
        w.sense().disabled();
        w
    });

    // Set pin 28 low and pin 21 high to turn the LED on.
    gpio0.outclr.write(|w| w.pin28().clear());
    gpio0.outset.write(|w| w.pin21().set());

    loop {}
}
  • SVDSystem View Descriptionファむルは、通垞は半導䜓ベンダヌから提䟛される XML ファむルであり、デバむスのメモリマップを蚘述したす。
    • これらはペリフェラル、レゞスタ、フィヌルド、倀ごずに敎理されおおり、名前、説明、アドレスなどが含たれたす。
    • SVD ファむルにはしばしばバグがあり、䞍完党でもあるため、誀りを修正し、䞍足しおいる詳现を远加しお、生成されたクレヌトを公開するさたざたなプロゞェクトがありたす。
  • cortex-m-rt は、他の機胜に加えおベクタテヌブルを提䟛したす。
  • cargo install cargo-binutils を実行するず、cargo objdump --bin pac -- -d --no-show-raw-insn を実行しお生成されたバむナリを確認できたす。

次のコマンドでこの䟋を実行したす。

cargo embed --bin pac

HAL クレヌト

HAL クレヌト は倚くのマむクロコントロヌラヌ向けに、 さたざたな呚蟺機噚のラッパヌを提䟛しおいたす。これらは䞀般に embedded-hal のトレむトを実装しおいたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin;
use nrf52833_hal::gpio::{Level, p0};
use nrf52833_hal::pac::Peripherals;

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();

    // Create HAL wrapper for GPIO port 0.
    let gpio0 = p0::Parts::new(p.P0);

    // Configure GPIO 0 pins 21 and 28 as push-pull outputs.
    let mut col1 = gpio0.p0_28.into_push_pull_output(Level::High);
    let mut row1 = gpio0.p0_21.into_push_pull_output(Level::Low);

    // Set pin 28 low and pin 21 high to turn the LED on.
    col1.set_low().unwrap();
    row1.set_high().unwrap();

    loop {}
}
  • set_low ず set_high は、embedded_hal の OutputPin トレむトのメ゜ッドです。
  • HAL クレヌトは、倚くの Cortex-M および RISC-V デバむス向けに提䟛されおおり、さたざたな STM32、GD32、nRF、NXP、MSP430、AVR、PIC マむクロコントロヌラヌに察応しおいたす。

次のコマンドで䟋を実行したす:

cargo embed --bin hal

ボヌドサポヌトクレヌト

ボヌドサポヌトクレヌトは、利䟿性のために、特定のボヌド向けのさらなるラッパヌ局を提䟛したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin;
use microbit::Board;

#[entry]
fn main() -> ! {
    let mut board = Board::take().unwrap();

    board.display_pins.col1.set_low().unwrap();
    board.display_pins.row1.set_high().unwrap();

    loop {}
}
  • この堎合、ボヌドサポヌトクレヌトは、より䜿いやすい名前ず、少しの初期化を提䟛しおいるだけです。
  • このクレヌトには、マむクロコントロヌラヌ本䜓以倖の、ボヌド䞊の䞀郚のデバむス甚ドラむバヌが含たれるこずもありたす。
    • microbit-v2 には、LED マトリクス甚のシンプルなドラむバヌが含たれおいたす。

次のコマンドでこの䟋を実行したす:

cargo embed --bin board_support

型状態パタヌン

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();
    let gpio0 = p0::Parts::new(p.P0);

    let pin: P0_01<Disconnected> = gpio0.p0_01;

    // let gpio0_01_again = gpio0.p0_01; // Error, moved.
    let mut pin_input: P0_01<Input<Floating>> = pin.into_floating_input();
    if pin_input.is_high().unwrap() {
        // ...
    }
    let mut pin_output: P0_01<Output<OpenDrain>> = pin_input
        .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low);
    pin_output.set_high().unwrap();
    // pin_input.is_high(); // Error, moved.

    let _pin2: P0_02<Output<OpenDrain>> = gpio0
        .p0_02
        .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low);
    let _pin3: P0_03<Output<PushPull>> =
        gpio0.p0_03.into_push_pull_output(Level::Low);

    loop {}
}
  • ピンは Copy や Clone を実装しおいないため、それぞれに぀いお存圚できるむンスタンスは 1 ぀だけです。いったんポヌト構造䜓からピンがムヌブされるず、ほかの誰もそれを取埗できたせん。
  • ピンの蚭定を倉曎するず叀いピンむンスタンスが消費されるため、その埌は叀いむンスタンスを䜿えたせん。
  • 倀の型は、その倀がどの状態にあるかを瀺したす。たずえば、この堎合は GPIO ピンの蚭定状態です。これにより、状態機械が型システムに゚ンコヌドされ、先に適切に蚭定するこずなく特定の方法でピンを䜿おうずしないこずが保蚌されたす。䞍正な状態遷移はコンパむル時に怜出されたす。
  • 入力ピンでは is_high を呌び出せ、出力ピンでは set_high を呌び出せたすが、その逆はできたせん。
  • 倚くの HAL クレヌトはこのパタヌンに埓っおいたす。

embedded-hal

embedded-hal クレヌトは、䞀般的なマむクロコントロヌラヌ呚蟺機噚を察象ずする耇数のトレむトを提䟛したす。

  • GPIO
  • PWM
  • ディレむタむマヌ
  • I2C および SPI のバスずデバむス

バむトストリヌム䟋: UART、CAN バス、RNG 向けの同様のトレむトは、それぞれ embedded-io、embedded-can、rand_core に分かれおいたす。

その埌、他のクレヌトがこれらのトレむトに基づいお drivers を実装したす。たずえば、加速床蚈ドラむバヌには I2C たたは SPI デバむスのむンスタンスが必芁になる堎合がありたす。

  • これらのトレむトは呚蟺機噚の䜿甚を察象ずしおいたすが、初期化や蚭定は察象ずしおいたせん。初期化ず蚭定はプラットフォヌム䟝存性が非垞に高いためです。
  • 倚くのマむクロコントロヌラヌ向けの実装に加え、Raspberry Pi 䞊の Linux のような他のプラットフォヌム向けの実装もありたす。
  • embedded-hal-async は、これらのトレむトの async 版を提䟛したす。
  • embedded-hal-nb は、nb クレヌトに基づく、ノンブロッキング I/O ぞの別のアプロヌチを提䟛したす。

probe-rs ず cargo-embed

probe-rs は、OpenOCD に䌌おいたすが、より緊密に統合された、組み蟌みデバッグ向けの䟿利なツヌルセットです。

  • CMSIS-DAP、ST-Link、J-Link プロヌブ経由の SWDSerial Wire Debugず JTAG
  • GDB スタブず Microsoft DAPDebug Adapter Protocolサヌバヌ
  • Cargo 統合

cargo-embed は、バむナリのビルドずフラッシュ、RTTReal Time Transfers出力のログ蚘録、GDB ぞの接続を行うための cargo サブコマンドです。蚭定は、プロゞェクトディレクトリ内の Embed.toml ファむルで行いたす。

  • CMSIS-DAP は、むンサヌキットデバッガヌがさたざたな Arm Cortex プロセッサの CoreSight Debug Access Port にアクセスするための、USB 䞊の Arm 暙準プロトコルです。これは、BBC micro:bit のオンボヌドデバッガヌで䜿われおいるものです。
  • ST-Link は ST Microelectronics のむンサヌキットデバッガヌ補品矀で、J-Link は SEGGER の補品矀です。
  • Debug Access Port は通垞、5 ピンの JTAG むンタヌフェヌスたたは 2 ピンの Serial Wire Debug のいずれかです。
  • probe-rs は、必芁に応じお自分のツヌルに組み蟌めるラむブラリです。
  • Microsoft Debug Adapter Protocol により、VSCode やその他の IDE は、サポヌトされおいる任意の マむクロコントロヌラヌ䞊で実行されるコヌドをデバッグできたす。
  • cargo-embed は、probe-rs ラむブラリを䜿っおビルドされたバむナリです。
  • RTTReal Time Transfersは、耇数のリングバッファを介しおデバッグ ホストずタヌゲットの間でデヌタを転送する仕組みです。

デバッグ

Embed.toml:

[default.general]
chip = "nrf52833_xxAA"

[debug.gdb]
enabled = true

src/bare-metal/microcontrollers/examples/ 配䞋の 1 ぀のタヌミナルで:

cargo embed --bin board_support debug

同じディレクトリの別のタヌミナルで:

gLinux たたは Debian の堎合:

gdb-multiarch target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1338"

MacOS の堎合:

arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1338"

GDB で、次を実行しおみおください:

b src/bin/board_support.rs:29
b src/bin/board_support.rs:30
b src/bin/board_support.rs:32
c
c
c

その他のプロゞェクト

  • RTIC
    • 「リアルタむム割り蟌み駆動コンカレンシヌ」。
    • 共有リ゜ヌス管理、メッセヌゞパッシング、タスクスケゞュヌリング、タむマヌキュヌ。
  • Embassy
    • 優先床、タむマヌ、ネットワヌキング、USB を備えた async ゚グれキュヌタヌ。
  • TockOS
    • プリ゚ンプティブスケゞュヌリングずメモリ保護ナニット のサポヌトを備えた、セキュリティ重芖の RTOS。
  • Hubris
    • メモリ保護、非特暩ドラむバヌ、IPC を備えた、 Oxide Computer Company のマむクロカヌネル RTOS。
  • Bindings for FreeRTOS
    • FreeRTOS 甚バむンディング。

䞀郚のプラットフォヌムには std 実装がありたす。たずえば esp-idf。

  • RTIC は RTOS ずもコンカレンシヌフレヌムワヌクずも芋なせたす。
    • HAL は含たれおいたせん。
    • 本栌的なカヌネルではなく、スケゞュヌリングに Cortex-M NVICNested Virtual Interrupt Controllerを䜿甚したす。
    • Cortex-M のみ。
  • Google は Titan セキュリティキヌ向けに Haven マむクロコントロヌラヌ䞊で TockOS を䜿甚しおいたす。
  • FreeRTOS の倧郚分は C で曞かれおいたすが、アプリケヌションを蚘述するための Rust バむンディングがありたす。

挔習

I2C コンパスから方角を読み取り、その読み取り倀をシリアルポヌトに蚘録したす。

挔習を確認したら、甚意されおいる solutions を芋るこずができたす。

コンパス

I2C コンパスから方䜍を読み取り、その枬定倀をシリアル ポヌトに蚘録したす。時間があれば、䜕らかの圢で LED に衚瀺したり、 ボタンを䜕らかの圢で䜿ったりしおみおください。

ヒント:

  • lsm303agr ず microbit-v2 クレヌトのドキュメント、および micro:bit ハヌドりェア を 確認しおください。
  • LSM303AGR 慣性蚈枬ナニットは、内郚 I2C バスに接続されおいたす。
  • TWI は I2C の別名なので、I2C マスタヌペリフェラルは TWIM ず呌ばれたす。
  • LSM303AGR ドラむバには、embedded_hal::i2c::I2c トレむトを実装するものが必芁です。これは microbit::hal::Twim 構造䜓が実装しおいたす。
  • さたざたなピンやペリフェラル甚のフィヌルドを持぀ microbit::Board 構造䜓が䜿えたす。
  • 必芁であれば nRF52833 デヌタシヌト を参照しおもかたいたせんが、この挔習では必芁ないはずです。

挔習テンプレヌト をダりンロヌドし、 compass ディレクトリで次のファむルを確認しおください。

src/main.rs:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

use core::fmt::Write;
use cortex_m_rt::entry;
use microbit::{hal::{Delay, uarte::{Baudrate, Parity, Uarte}}, Board};

#[entry]
fn main() -> ! {
    let mut board = Board::take().unwrap();

    // Configure serial port.
    let mut serial = Uarte::new(
        board.UARTE0,
        board.uart.into(),
        Parity::EXCLUDED,
        Baudrate::BAUD115200,
    );

    // Use the system timer as a delay provider.
    let mut delay = Delay::new(board.SYST);

    // Set up the I2C controller and Inertial Measurement Unit.
    // TODO

    writeln!(serial, "Ready.").unwrap();

    loop {
        // Read compass data and log it to the serial port.
        // TODO
    }
}

Cargo.tomlこれは倉曎する必芁はないはずです:

[workspace]

[package]
name = "compass"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
cortex-m-rt = "0.7.5"
embedded-hal = "1.0.0"
lsm303agr = "1.1.0"
microbit-v2 = "0.16.0"
panic-halt = "1.0.0"

Embed.tomlこれは倉曎する必芁はないはずです:

[default.general]
chip = "nrf52833_xxAA"

[debug.gdb]
enabled = true

[debug.reset]
halt_afterwards = true

.cargo/config.tomlこれは倉曎する必芁はないはずです:

[build]
target = "thumbv7em-none-eabihf" # Cortex-M4F

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = ["-C", "link-arg=-Tlink.x"]

Linux では、次のようにしおシリアル出力を確認できたす:

picocom --baud 115200 --imap lfcrlf /dev/ttyACM0

たたは Mac OS では次のようになりたすデバむス名は倚少異なる堎合がありたす:

picocom --baud 115200 --imap lfcrlf /dev/tty.usbmodem14502

picocom を終了するには Ctrl+A Ctrl+Q を䜿いたす。

ベアメタル Rust 午前の挔習

コンパス

(挔習に戻る)

// Google LLC の Copyright 2023
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

extern crate panic_halt as _;

use core::fmt::Write;
use cortex_m_rt::entry;
use embedded_hal::digital::InputPin;
use lsm303agr::{
    AccelMode, AccelOutputDataRate, Lsm303agr, MagMode, MagOutputDataRate,
};
use microbit::Board;
use microbit::display::blocking::Display;
use microbit::hal::twim::Twim;
use microbit::hal::uarte::{Baudrate, Parity, Uarte};
use microbit::hal::{Delay, Timer};
use microbit::pac::twim0::frequency::FREQUENCY_A;

const COMPASS_SCALE: i32 = 30000;
const ACCELEROMETER_SCALE: i32 = 700;

#[entry]
fn main() -> ! {
    let mut board = Board::take().unwrap();

    // Configure serial port.
    let mut serial = Uarte::new(
        board.UARTE0,
        board.uart.into(),
        Parity::EXCLUDED,
        Baudrate::BAUD115200,
    );

    // Use the system timer as a delay provider.
    let mut delay = Delay::new(board.SYST);

    // Set up the I2C controller and Inertial Measurement Unit.
    writeln!(serial, "Setting up IMU...").unwrap();
    let i2c = Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100);
    let mut imu = Lsm303agr::new_with_i2c(i2c);
    imu.init().unwrap();
    imu.set_mag_mode_and_odr(
        &mut delay,
        MagMode::HighResolution,
        MagOutputDataRate::Hz50,
    )
    .unwrap();
    imu.set_accel_mode_and_odr(
        &mut delay,
        AccelMode::Normal,
        AccelOutputDataRate::Hz50,
    )
    .unwrap();
    let mut imu = imu.into_mag_continuous().ok().unwrap();

    // Set up display and timer.
    let mut timer = Timer::new(board.TIMER0);
    let mut display = Display::new(board.display_pins);

    let mut mode = Mode::Compass;
    let mut button_pressed = false;

    writeln!(serial, "Ready.").unwrap();

    loop {
        // Read compass data and log it to the serial port.
        while !(imu.mag_status().unwrap().xyz_new_data()
            && imu.accel_status().unwrap().xyz_new_data())
        {}
        let compass_reading = imu.magnetic_field().unwrap();
        let accelerometer_reading = imu.acceleration().unwrap();
        writeln!(
            serial,
            "{},{},{}\t{},{},{}",
            compass_reading.x_nt(),
            compass_reading.y_nt(),
            compass_reading.z_nt(),
            accelerometer_reading.x_mg(),
            accelerometer_reading.y_mg(),
            accelerometer_reading.z_mg(),
        )
        .unwrap();

        let mut image = [[0; 5]; 5];
        let (x, y) = match mode {
            Mode::Compass => (
                scale(-compass_reading.x_nt(), -COMPASS_SCALE, COMPASS_SCALE, 0, 4)
                    as usize,
                scale(compass_reading.y_nt(), -COMPASS_SCALE, COMPASS_SCALE, 0, 4)
                    as usize,
            ),
            Mode::Accelerometer => (
                scale(
                    accelerometer_reading.x_mg(),
                    -ACCELEROMETER_SCALE,
                    ACCELEROMETER_SCALE,
                    0,
                    4,
                ) as usize,
                scale(
                    -accelerometer_reading.y_mg(),
                    -ACCELEROMETER_SCALE,
                    ACCELEROMETER_SCALE,
                    0,
                    4,
                ) as usize,
            ),
        };
        image[y][x] = 255;
        display.show(&mut timer, image, 100);

        // If button A is pressed, switch to the next mode and briefly blink all LEDs
        // on.
        if board.buttons.button_a.is_low().unwrap() {
            if !button_pressed {
                mode = mode.next();
                display.show(&mut timer, [[255; 5]; 5], 200);
            }
            button_pressed = true;
        } else {
            button_pressed = false;
        }
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Mode {
    Compass,
    Accelerometer,
}

impl Mode {
    fn next(self) -> Self {
        match self {
            Self::Compass => Self::Accelerometer,
            Self::Accelerometer => Self::Compass,
        }
    }
}

fn scale(value: i32, min_in: i32, max_in: i32, min_out: i32, max_out: i32) -> i32 {
    let range_in = max_in - min_in;
    let range_out = max_out - min_out;
    let scaled = min_out + range_out * (value - min_in) / range_in;
    scaled.clamp(min_out, max_out)
}

アプリケヌションプロセッサ

これたでは、Arm Cortex-M シリヌズのようなマむクロコントロヌラに぀いお説明しおきたした。 これらは通垞、リ゜ヌスが非垞に限られた小芏暡なシステムです。

より倚くのリ゜ヌスを持぀倧芏暡なシステムは、䞀般にアプリケヌションプロセッサず呌ばれ、 ARM Cortex-A や Intel Atom のようなプロセッサを䞭心に構成されおいたす。

簡単のため、ここでは QEMU の aarch64 ‘virt’ ボヌドだけを扱いたす。

  • 倧たかに蚀えば、マむクロコントロヌラには MMU や耇数の特暩レベル Arm CPU では䟋倖レベル、x86 ではリングがありたせん。
  • アプリケヌションプロセッサはより倚くのリ゜ヌスを備えおおり、起動時に察象アプリケヌションを盎接実行するのではなく、 OS を実行するこずがよくありたす。
  • QEMU は、各アヌキテクチャ向けにさたざたなマシンやボヌドモデルの゚ミュレヌションをサポヌトしおいたす。 ‘virt’ ボヌドは特定の実機ハヌドりェアに察応するものではなく、玔粋に仮想マシン向けに蚭蚈されおいたす。
  • このボヌドに぀いおも、OS を曞いおいるかのように、匕き続きベアメタルずしお扱いたす。

Rust を始める準備

Rust コヌドの実行を開始する前に、いく぀か初期化を行う必芁がありたす。

/**
 * This is a generic entry point for an image. It carries out the
 * operations required to prepare the loaded image to be run.
 * Specifically, it
 *
 * - sets up the MMU with an identity map of virtual to physical
 *   addresses, and enables caching
 * - enables floating point
 * - zeroes the bss section using registers x25 and above
 * - prepares the stack, pointing to a section within the image
 * - sets up the exception vector
 * - branches to the Rust `main` function
 *
 * It preserves x0-x3 for the Rust entry point, as these may contain
 * boot parameters.
 */
.section .init.entry, "ax"
.global entry
entry:
    /*
     * Load and apply the memory management configuration, ready to
     * enable MMU and caches.
     */
    adrp x30, idmap
    msr ttbr0_el1, x30

    mov_i x30, .Lmairval
    msr mair_el1, x30

    mov_i x30, .Ltcrval
    /* Copy the supported PA range into TCR_EL1.IPS. */
    mrs x29, id_aa64mmfr0_el1
    bfi x30, x29, #32, #4

    msr tcr_el1, x30

    mov_i x30, .Lsctlrval

    /*
     * Ensure everything before this point has completed, then
     * invalidate any potentially stale local TLB entries before they
     * start being used.
     */
    isb
    tlbi vmalle1
    ic iallu
    dsb nsh
    isb

    /*
     * Configure sctlr_el1 to enable MMU and cache and don't proceed
     * until this has completed.
     */
    msr sctlr_el1, x30
    isb

    /* Disable trapping floating point access in EL1. */
    mrs x30, cpacr_el1
    orr x30, x30, #(0x3 << 20)
    msr cpacr_el1, x30
    isb

    /* Zero out the bss section. */
    adr_l x29, bss_begin
    adr_l x30, bss_end
0:  cmp x29, x30
    b.hs 1f
    stp xzr, xzr, [x29], #16
    b 0b

1:  /* Prepare the stack. */
    adr_l x30, boot_stack_end
    mov sp, x30

    /* Set up exception vector. */
    adr x30, vector_table_el1
    msr vbar_el1, x30

    /* Call into Rust code. */
    bl main

    /* Loop forever waiting for interrupts. */
2:  wfi
    b 2b

このコヌドは src/bare-metal/aps/examples/src/entry.S にありたす。これを詳现に 理解する必芁はありたせんが、芁点は、Rust がシステムに期埅する条件を満たすために、 䜕らかの䜎レベルなセットアップが必芁だずいうこずです。

  • これは C の堎合ず同じです。぀たり、プロセッサ状態を初期化し、BSS をれロクリアし、 スタックポむンタを蚭定したす。
    • BSS歎史的な理由による名称で、block starting symbolは、 れロで初期化される静的に確保された倉数を含むオブゞェクトファむルの䞀郚です。 これらはれロに領域を䜿っお無駄にしないよう、むメヌゞには含たれたせん。 コンパむラは、ロヌダヌがそれらをれロクリアしおくれるこずを前提にしおいたす。
  • メモリがどのように初期化され、むメヌゞがどのようにロヌドされるかによっおは、BSS は すでにれロクリアされおいる堎合もありたすが、確実を期すためにこちらでれロクリアしたす。
  • メモリの読み曞きを行う前に、MMU ずキャッシュを有効にする必芁がありたす。そうしないず、 次の問題が起こりたす。
    • 非アラむンアクセスでフォヌルトが発生したす。Rust コヌドは、コンパむラが 非アラむンアクセスを生成しないよう +strict-align が蚭定された aarch64-unknown-none タヌゲット向けにビルドしおいるため、このケヌスでは 問題ないはずですが、䞀般には必ずしもそうずは限りたせん。
    • VM 䞊で動䜜しおいる堎合、キャッシュコヒヌレンシの問題に぀ながる可胜性がありたす。 問題は、VM はキャッシュを無効にした状態でメモリに盎接アクセスしおいる䞀方で、 ホストは同じメモリに察するキャッシュ可胜な別名を持っおいるこずです。ホストが 明瀺的にそのメモリぞアクセスしなくおも、投機的アクセスによっおキャッシュフィルが 発生し、その埌キャッシュがクリヌンされたり VM がキャッシュを有効にしたりするず、 どちらか䞀方の倉曎が倱われおしたいたす。キャッシュは VA や IPA ではなく、 物理アドレスをキヌにしおいたす。
  • 単玔化のため、先頭の 1 GiB のアドレス空間をデバむス甚に、その次の 1 GiB を DRAM 甚に、 さらにその䞊䜍の別の 1 GiB を远加のデバむス甚に恒等マップする、ハヌドコヌドされた ペヌゞテヌブルidmap.S を参照をそのたた䜿甚したす。これは QEMU が䜿甚する メモリレむアりトず䞀臎しおいたす。
  • 䟋倖ベクタvbar_el1も蚭定したす。これに぀いおは埌でもう少し詳しく芋たす。
  • この午埌のすべおの䟋では、䟋倖レベル 1EL1で実行するこずを前提ずしおいたす。 別の䟋倖レベルで実行する必芁がある堎合は、それに応じお entry.S を倉曎する必芁がありたす。

むンラむンアセンブリ

Rust のコヌドでは実珟できないこずを行うために、アセンブリを䜿う必芁が ある堎合がありたす。たずえば、システムの電源を切るようファヌムりェアに 䌝えるために HVCハむパヌバむザヌ呌び出しを行う堎合です:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

use core::arch::asm;
use core::panic::PanicInfo;

mod asm;
mod exceptions;

const PSCI_SYSTEM_OFF: u32 = 0x84000008;

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn main(_x0: u64, _x1: u64, _x2: u64, _x3: u64) {
    // SAFETY: this only uses the declared registers and doesn't do anything
    // with memory.
    unsafe {
        asm!("hvc #0",
            inout("w0") PSCI_SYSTEM_OFF => _,
            inout("w1") 0 => _,
            inout("w2") 0 => _,
            inout("w3") 0 => _,
            inout("w4") 0 => _,
            inout("w5") 0 => _,
            inout("w6") 0 => _,
            inout("w7") 0 => _,
            options(nomem, nostack)
        );
    }

    loop {}
}

実際にこれを行いたい堎合は、これらすべおの関数のラッパヌを提䟛しおいる smccc クレヌトを䜿っおください。

  • PSCI は Arm Power State Coordination Interface のこずで、システムや CPU の 電源状態などを管理するための暙準的な関数矀です。倚くのシステムでは、 EL3 ファヌムりェアやハむパヌバむザヌによっお実装されおいたす。
  • 0 => _ 構文は、むンラむンアセンブリコヌドを実行する前にレゞスタを 0 で 初期化し、その埌はその内容を無芖するこずを意味したす。呌び出しによっお レゞスタの内容が砎壊される可胜性があるため、in ではなく inout を 䜿う必芁がありたす。
  • この main 関数は、entry.S 内の゚ントリポむントから呌び出されるため、 #[unsafe(no_mangle)] ず extern "C" である必芁がありたす。
    • #[no_mangle] だけでも十分ですが、 RFC3325 では、 誀っお䜿甚するず未定矩動䜜を匕き起こす可胜性がある属性にレビュアヌの 泚意を向けるために、この衚蚘が䜿われおいたす。
  • _x0–_x3 はレゞスタ x0–x3 の倀であり、これらは慣䟋的に ブヌトロヌダヌがデバむスツリヌぞのポむンタのような倀を枡すために䜿甚したす。 暙準の aarch64 呌び出し芏玄これは extern "C" が䜿甚を指定するものです では、関数に枡される最初の 8 個の匕数にレゞスタ x0–x7 が䜿われるため、 entry.S はこれらのレゞスタを倉曎しないようにする以倖、特別なこずを行う 必芁はありたせん。
  • src/bare-metal/aps/examples で make qemu_psci を実行しお、QEMU 䞊で この䟋を実行しおください。

MMIO 向けの volatile メモリアクセス

  • pointer::read_volatile ず pointer::write_volatile を䜿甚しおください。
  • これらのメ゜ッドでアクセスしおいる堎所ぞの参照は決しお保持しないでください。Rust は参照をい぀でも読み取る&mut の堎合は曞き蟌むこずがありたす。
  • 䞭間参照を䜜成せずに構造䜓のフィヌルドぞのポむンタを埗るには &raw を䜿甚 しおください。
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

const SOME_DEVICE_REGISTER: *mut u64 = 0x800_0000 as _;
// SAFETY: このアドレスには䜕らかのデバむスがマップされおいたす。
unsafe {
    SOME_DEVICE_REGISTER.write_volatile(0xff);
    SOME_DEVICE_REGISTER.write_volatile(0x80);
    assert_eq!(SOME_DEVICE_REGISTER.read_volatile(), 0xaa);
}
  • volatile アクセス: 読み曞き操䜜には副䜜甚があり埗るため、コンパむラやハヌド りェアがそれらを䞊べ替えたり、重耇させたり、取り陀いたりするのを防ぎたす。
    • たずえば可倉参照経由で曞き蟌みの埌に読み取りを行うず、コンパむラは読み取 った倀が盎前に曞き蟌んだ倀ず同じだず仮定し、実際にはメモリを読たないこずがありたす。
  • ハヌドりェアぞの volatile アクセス甚の既存のクレヌトの䞭には参照を保持する ものもありたすが、これは非健党です。参照があるず、コンパむラはそれを デリファレンスする可胜性がありたす。
  • 構造䜓ぞのポむンタからフィヌルドぞのポむンタを取埗するには &raw を䜿甚しおください。
  • 叀いバヌゞョンの Rust ずの互換性のために、代わりに addr_of! マクロを䜿 甚できたす。

UARTドラむバヌを曞いおみたしょう

QEMU の virt マシンには PL011 UART があるので、そのドラむバヌを曞いおみたしょう。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

const FLAG_REGISTER_OFFSET: usize = 0x18;
const FR_BUSY: u8 = 1 << 3;
const FR_TXFF: u8 = 1 << 5;

/// Minimal driver for a PL011 UART.
#[derive(Debug)]
pub struct Uart {
    base_address: *mut u8,
}

impl Uart {
    /// Constructs a new instance of the UART driver for a PL011 device at the
    /// given base address.
    ///
    /// # Safety
    ///
    /// The given base address must point to the 8 MMIO control registers of a
    /// PL011 device, which must be mapped into the address space of the process
    /// as device memory and not have any other aliases.
    pub unsafe fn new(base_address: *mut u8) -> Self {
        Self { base_address }
    }

    /// Writes a single byte to the UART.
    pub fn write_byte(&self, byte: u8) {
        // Wait until there is room in the TX buffer.
        while self.read_flag_register() & FR_TXFF != 0 {}

        // SAFETY: We know that the base address points to the control
        // registers of a PL011 device which is appropriately mapped.
        unsafe {
            // Write to the TX buffer.
            self.base_address.write_volatile(byte);
        }

        // Wait until the UART is no longer busy.
        while self.read_flag_register() & FR_BUSY != 0 {}
    }

    fn read_flag_register(&self) -> u8 {
        // SAFETY: We know that the base address points to the control
        // registers of a PL011 device which is appropriately mapped.
        unsafe { self.base_address.add(FLAG_REGISTER_OFFSET).read_volatile() }
    }
}
  • Uart::new は unsafe ですが、他のメ゜ッドは safe であるこずに泚意しおください。これは、Uart::new の呌び出し偎がその安党性芁件぀たり、特定の UART に察しおこのドラむバヌのむンスタンスが垞に 1 ぀しか存圚せず、そのアドレス空間を゚むリアスするものが他に存圚しないこずを満たすこずを保蚌しおいる限り、必芁な事前条件が成り立぀ず仮定できるため、埌で write_byte を呌び出すこずは垞に安党だからです。
  • 逆にするこずもできたしたnew を safe にしお write_byte を unsafe にするが、そうするず write_byte を呌び出すすべおの箇所で安党性に぀いお考えなければならず、䜿い勝手は倧幅に悪くなりたす
  • これは、unsafe なコヌドに察する safe なラッパヌを曞く際によくあるパタヌンです。぀たり、健党性の立蚌責任を倚数の箇所から、より少ない箇所ぞ移すずいうこずです。

その他のトレむト

Debug トレむトは derive したした。さらにいく぀かのトレむトも実装するず䟿利です。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use core::fmt::{self, Write};

impl Write for Uart {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.as_bytes() {
            self.write_byte(*c);
        }
        Ok(())
    }
}

// SAFETY: `Uart` just contains a pointer to device memory, which can be
// accessed from any context.
unsafe impl Send for Uart {}
  • Write を実装するず、Uart 型で write! マクロず writeln! マクロを䜿えるようになりたす。

  • Send は自動トレむトですが、ポむンタには実装されおいないため、自動的には実装されたせん。

䜿っおみる

ドラむバを䜿っおシリアルコン゜ヌルに曞き蟌む小さなプログラムを曞いおみたしょう。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

mod asm;
mod exceptions;
mod pl011_minimal;

use crate::pl011_minimal::Uart;
use core::fmt::Write;
use core::panic::PanicInfo;
use log::error;
use smccc::Hvc;
use smccc::psci::system_off;

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: *mut u8 = 0x900_0000 as _;

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
    // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
    // nothing else accesses that address range.
    let mut uart = unsafe { Uart::new(PL011_BASE_ADDRESS) };

    writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();

    system_off::<Hvc>().unwrap();
}
  • inline assembly の䟋ず同様に、この main 関数は entry.S の゚ントリポむントコヌドから呌び出されたす。詳现は、そちらの スピヌカヌノヌトを参照しおください。
  • src/bare-metal/aps/examples で make qemu_minimal を実行しお、 この䟋を QEMU で実行したす。

より良いUARTドラむバヌ

PL011 には実際にはさらに倚くのレゞスタがあり、それらにアクセスするためのポむンタを構築するためにオフセットを加える方法は、゚ラヌが発生しやすく、読みにくくなりたす。さらに、その䞀郚はビットフィヌルドであり、構造化された方法でアクセスできるず䟿利です。

オフセットレゞスタ名ビット幅
0x00DR12
0x04RSR4
0x18FR9
0x20ILPR8
0x24IBRD16
0x28FBRD6
0x2cLCR_H8
0x30CR16
0x34IFLS6
0x38IMSC11
0x3cRIS11
0x40MIS11
0x44ICR11
0x48DMACR3
  • 簡朔にするため、省略したIDレゞスタもありたす。

ビットフラグ

bitflags クレヌトは、ビットフラグを扱う際に䟿利です。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use bitflags::bitflags;

bitflags! {
    /// Flags from the UART flag register.
    #[repr(transparent)]
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    struct Flags: u16 {
        /// Clear to send.
        const CTS = 1 << 0;
        /// Data set ready.
        const DSR = 1 << 1;
        /// Data carrier detect.
        const DCD = 1 << 2;
        /// UART busy transmitting data.
        const BUSY = 1 << 3;
        /// Receive FIFO is empty.
        const RXFE = 1 << 4;
        /// Transmit FIFO is full.
        const TXFF = 1 << 5;
        /// Receive FIFO is full.
        const RXFF = 1 << 6;
        /// Transmit FIFO is empty.
        const TXFE = 1 << 7;
        /// Ring indicator.
        const RI = 1 << 8;
    }
}
  • bitflags! マクロは、struct Flags(u16) のような newtype を䜜成し、 フラグの取埗ず蚭定を行うための倚数のメ゜ッド実装も䜵せお生成したす。

耇数のレゞスタ

構造䜓を䜿っお、UART のレゞスタのメモリレむアりトを衚珟できたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[repr(C, align(4))]
pub struct Registers {
    dr: u16,
    _reserved0: [u8; 2],
    rsr: ReceiveStatus,
    _reserved1: [u8; 19],
    fr: Flags,
    _reserved2: [u8; 6],
    ilpr: u8,
    _reserved3: [u8; 3],
    ibrd: u16,
    _reserved4: [u8; 2],
    fbrd: u8,
    _reserved5: [u8; 3],
    lcr_h: u8,
    _reserved6: [u8; 3],
    cr: u16,
    _reserved7: [u8; 3],
    ifls: u8,
    _reserved8: [u8; 3],
    imsc: u16,
    _reserved9: [u8; 2],
    ris: u16,
    _reserved10: [u8; 2],
    mis: u16,
    _reserved11: [u8; 2],
    icr: u16,
    _reserved12: [u8; 2],
    dmacr: u8,
    _reserved13: [u8; 3],
}
  • #[repr(C)] は、C ず同じルヌルに埓っお、構造䜓のフィヌルドを順番に配眮するよう コンパむラに指瀺したす。これは、構造䜓のレむアりトを予枬可胜にするために 必芁です。Rust のデフォルト衚珟では、コンパむラはずりわけ フィヌルドを自由に䞊べ替えるこずができるためです。

ドラむバ

それでは、ドラむバで新しい Registers 構造䜓を䜿いたしょう。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// Driver for a PL011 UART.
#[derive(Debug)]
pub struct Uart {
    registers: *mut Registers,
}

impl Uart {
    /// Constructs a new instance of the UART driver for a PL011 device with the
    /// given set of registers.
    ///
    /// # Safety
    ///
    /// The given pointer must point to the 8 MMIO control registers of a PL011
    /// device, which must be mapped into the address space of the process as
    /// device memory and not have any other aliases.
    pub unsafe fn new(registers: *mut Registers) -> Self {
        Self { registers }
    }

    /// Writes a single byte to the UART.
    pub fn write_byte(&mut self, byte: u8) {
        // Wait until there is room in the TX buffer.
        while self.read_flag_register().contains(Flags::TXFF) {}

        // SAFETY: We know that self.registers points to the control registers
        // of a PL011 device which is appropriately mapped.
        unsafe {
            // Write to the TX buffer.
            (&raw mut (*self.registers).dr).write_volatile(byte.into());
        }

        // Wait until the UART is no longer busy.
        while self.read_flag_register().contains(Flags::BUSY) {}
    }

    /// Reads and returns a pending byte, or `None` if nothing has been
    /// received.
    pub fn read_byte(&mut self) -> Option<u8> {
        if self.read_flag_register().contains(Flags::RXFE) {
            None
        } else {
            // SAFETY: We know that self.registers points to the control
            // registers of a PL011 device which is appropriately mapped.
            let data = unsafe { (&raw const (*self.registers).dr).read_volatile() };
            // TODO: Check for error conditions in bits 8-11.
            Some(data as u8)
        }
    }

    fn read_flag_register(&self) -> Flags {
        // SAFETY: We know that self.registers points to the control registers
        // of a PL011 device which is appropriately mapped.
        unsafe { (&raw const (*self.registers).fr).read_volatile() }
    }
}
  • 個々のフィヌルドぞのポむンタを取埗するために、䞭間の参照を䜜成せずに &raw const / &raw mut を䜿っおいる点に泚目しおください。䞭間の参照を䜜成するず、䞍健党になりたす。
  • この䟋は、次に出おくる safe-mmio の䟋ず非垞によく䌌おいるため、スラむドには含たれおいたせん。必芁であれば、src/bare-metal/aps/examples 配䞋で make qemu を実行しお QEMU 䞊で動かせたす。

safe-mmio

safe-mmio クレヌトは、レゞスタを安党に読み曞きできるようにラップする型を提䟛したす。

読み取り䞍可読み取りに副䜜甚がない読み取りに副䜜甚がある
曞き蟌み䞍可ReadPureReadOnly
曞き蟌み可WriteOnlyReadPureWriteReadWrite
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use safe_mmio::fields::{ReadPure, ReadPureWrite, ReadWrite, WriteOnly};

#[repr(C, align(4))]
pub struct Registers {
    dr: ReadWrite<u16>,
    _reserved0: [u8; 2],
    rsr: ReadPure<ReceiveStatus>,
    _reserved1: [u8; 19],
    fr: ReadPure<Flags>,
    _reserved2: [u8; 6],
    ilpr: ReadPureWrite<u8>,
    _reserved3: [u8; 3],
    ibrd: ReadPureWrite<u16>,
    _reserved4: [u8; 2],
    fbrd: ReadPureWrite<u8>,
    _reserved5: [u8; 3],
    lcr_h: ReadPureWrite<u8>,
    _reserved6: [u8; 3],
    cr: ReadPureWrite<u16>,
    _reserved7: [u8; 3],
    ifls: ReadPureWrite<u8>,
    _reserved8: [u8; 3],
    imsc: ReadPureWrite<u16>,
    _reserved9: [u8; 2],
    ris: ReadPure<u16>,
    _reserved10: [u8; 2],
    mis: ReadPure<u16>,
    _reserved11: [u8; 2],
    icr: WriteOnly<u16>,
    _reserved12: [u8; 2],
    dmacr: ReadPureWrite<u8>,
    _reserved13: [u8; 3],
}
  • dr の読み取りには副䜜甚がありたす。受信 FIFO から 1 バむト取り出したす。
  • rsrおよび他のレゞスタの読み取りには副䜜甚がありたせん。これは「pure」な読み取りです。
  • MMIO 操䜜に察する安党な抜象化を提䟛するクレヌトはいく぀かありたすが、safe-mmio クレヌトを掚奚したす。
  • ReadPure ず ReadOnly の違い同様に ReadPureWrite ず ReadWrite の違いは、レゞスタの読み取りにデバむスの状態を倉化させる副䜜甚があり埗るかどうかです。たずえば、デヌタレゞスタを読み取るず受信 FIFO から 1 バむト取り出されたす。ReadPure は、読み取りに副䜜甚がなく、玔粋にデヌタを読み取るだけであるこずを意味したす。

ドラむバ

では、ドラむバで新しい Registers 構造䜓を䜿っおみたしょう。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use safe_mmio::{UniqueMmioPointer, field, field_shared};

/// Driver for a PL011 UART.
#[derive(Debug)]
pub struct Uart<'a> {
    registers: UniqueMmioPointer<'a, Registers>,
}

impl<'a> Uart<'a> {
    /// Constructs a new instance of the UART driver for a PL011 device with the
    /// given set of registers.
    pub fn new(registers: UniqueMmioPointer<'a, Registers>) -> Self {
        Self { registers }
    }

    /// Writes a single byte to the UART.
    pub fn write_byte(&mut self, byte: u8) {
        // Wait until there is room in the TX buffer.
        while self.read_flag_register().contains(Flags::TXFF) {}

        // Write to the TX buffer.
        field!(self.registers, dr).write(byte.into());

        // Wait until the UART is no longer busy.
        while self.read_flag_register().contains(Flags::BUSY) {}
    }

    /// Reads and returns a pending byte, or `None` if nothing has been
    /// received.
    pub fn read_byte(&mut self) -> Option<u8> {
        if self.read_flag_register().contains(Flags::RXFE) {
            None
        } else {
            let data = field!(self.registers, dr).read();
            // TODO: Check for error conditions in bits 8-11.
            Some(data as u8)
        }
    }

    fn read_flag_register(&self) -> Flags {
        field_shared!(self.registers, fr).read()
    }
}
  • このドラむバでは、もはや unsafe コヌドは必芁ありたせん
  • UniqueMmioPointer は、MMIO デバむスたたはレゞスタぞの生ポむンタをラップするものです。UniqueMmioPointer::new の呌び出し元は、それが指定されたラむフタむムの間に有効で䞀意であるこずを保蚌するため、フィヌルドを安党に読み曞きするメ゜ッドを提䟛できたす。
  • Uart::new は珟圚では safe であり、代わりに UniqueMmioPointer::new が unsafe であるこずに泚意しおください。
  • これらの MMIO アクセスは䞀般に read_volatile ず write_volatile のラッパヌですが、aarch64 では代わりにアセンブリで実装されおいたす。これは、コンパむラが MMIO 仮想化を劚げる呜什を出力しおしたうバグを回避するためです。
  • field! および field_shared! マクロは内郚で &raw mut ず &raw const を䜿甚しお、䞭間参照を䜜成せずに個々のフィヌルドぞのポむンタを取埗したす。䞭間参照を䜜成するず䞍健党になるためです。
  • field! は UniqueMmioPointer ぞの可倉参照を必芁ずし、副䜜甚を䌎う読み取りず曞き蟌みを蚱可する UniqueMmioPointer を返したす。
  • field_shared! は、UniqueMmioPointer たたは SharedMmioPointer のいずれかぞの共有参照で動䜜したす。これは、玔粋な読み取りのみを蚱可する SharedMmioPointer を返したす。

䜿っおみる

ドラむバを䜿っおシリアルコン゜ヌルに曞き蟌み、受信したバむトを゚コヌする小さなプログラムを曞いおみたしょう。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

mod asm;
mod exceptions;
mod pl011;

use crate::pl011::Uart;
use core::fmt::Write;
use core::panic::PanicInfo;
use core::ptr::NonNull;
use log::error;
use safe_mmio::UniqueMmioPointer;
use smccc::Hvc;
use smccc::psci::system_off;

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
    NonNull::new(0x900_0000 as _).unwrap();

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
    // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
    // nothing else accesses that address range.
    let mut uart = Uart::new(unsafe { UniqueMmioPointer::new(PL011_BASE_ADDRESS) });

    writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();

    loop {
        if let Some(byte) = uart.read_byte() {
            uart.write_byte(byte);
            match byte {
                b'\r' => uart.write_byte(b'\n'),
                b'q' => break,
                _ => continue,
            }
        }
    }

    writeln!(uart, "\n\nBye!").unwrap();
    system_off::<Hvc>().unwrap();
}
  • src/bare-metal/aps/examples で make qemu_safemmio を実行しお、この䟋を QEMU 䞊で動かしたす。

ロギング

log クレヌトのロギングマクロを䜿えるず䟿利です。 これは Log トレむトを実装するこずで実珟できたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use crate::pl011::Uart;
use core::fmt::Write;
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
use spin::mutex::SpinMutex;

static LOGGER: Logger = Logger { uart: SpinMutex::new(None) };

struct Logger {
    uart: SpinMutex<Option<Uart<'static>>>,
}

impl Log for Logger {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }

    fn log(&self, record: &Record) {
        writeln!(
            self.uart.lock().as_mut().unwrap(),
            "[{}] {}",
            record.level(),
            record.args()
        )
        .unwrap();
    }

    fn flush(&self) {}
}

/// Initialises UART logger.
pub fn init(
    uart: Uart<'static>,
    max_level: LevelFilter,
) -> Result<(), SetLoggerError> {
    LOGGER.uart.lock().replace(uart);

    log::set_logger(&LOGGER)?;
    log::set_max_level(max_level);
    Ok(())
}
  • log 内の最初の unwrap は、set_logger を呌び出す前に LOGGER を初期化しおいるため 成功したす。2 ぀目は、Uart::write_str が垞に Ok を返すため 成功したす。

䜿う

䜿甚する前にロガヌを初期化する必芁がありたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

mod asm;
mod exceptions;
mod logger;
mod pl011;

use crate::pl011::Uart;
use core::panic::PanicInfo;
use core::ptr::NonNull;
use log::{LevelFilter, error, info};
use safe_mmio::UniqueMmioPointer;
use smccc::Hvc;
use smccc::psci::system_off;

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: NonNull<pl011::Registers> =
    NonNull::new(0x900_0000 as _).unwrap();

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) {
    // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
    // nothing else accesses that address range.
    let uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
    logger::init(uart, LevelFilter::Trace).unwrap();

    info!("main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})");

    assert_eq!(x1, 42);

    system_off::<Hvc>().unwrap();
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    error!("{info}");
    system_off::<Hvc>().unwrap();
    loop {}
}
  • panic ハンドラが panic の詳现をログに蚘録できるようになったこずに泚意しおください。
  • src/bare-metal/aps/examples で make qemu_logger を実行しお、 この䟋を QEMU で実行しおください。

䟋倖

AArch64 は、4 ぀の状態SP0 を䜿甚する珟圚の EL、SPx を䜿甚する珟圚の EL、AArch64 を䜿甚する䞋䜍 EL、AArch32 を䜿甚する䞋䜍 ELからの 4 皮類の䟋倖同期、IRQ、FIQ、SErrorに察しお、16 個の゚ントリを持぀䟋倖ベクタヌテヌブルを定矩しおいたす。Rust コヌドを呌び出す前に揮発性レゞスタをスタックに保存するため、これをアセンブリで実装したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use log::error;
use smccc::Hvc;
use smccc::psci::system_off;

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn sync_current(_elr: u64, _spsr: u64) {
    error!("sync_current");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn irq_current(_elr: u64, _spsr: u64) {
    error!("irq_current");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn fiq_current(_elr: u64, _spsr: u64) {
    error!("fiq_current");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn serror_current(_elr: u64, _spsr: u64) {
    error!("serror_current");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
    error!("sync_lower");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn irq_lower(_elr: u64, _spsr: u64) {
    error!("irq_lower");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn fiq_lower(_elr: u64, _spsr: u64) {
    error!("fiq_lower");
    system_off::<Hvc>().unwrap();
}

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn serror_lower(_elr: u64, _spsr: u64) {
    error!("serror_lower");
    system_off::<Hvc>().unwrap();
}
  • EL は䟋倖レベルです。この午埌の䟋はすべお EL1 で実行されたす。
  • 簡単のため、珟圚の EL の䟋倖では SP0 ず SPx を区別せず、䞋䜍 EL の䟋倖では AArch32 ず AArch64 を区別したせん。
  • この䟋では、これらが実際に発生するこずは想定しおいないため、䟋倖をログに蚘録しお電源を萜ずすだけです。
  • 䟋倖ハンドラずメむンの実行コンテキストは、だいたい別々のスレッドのようなものず考えられたす。Send ず Sync が、スレッドの堎合ず同じように、それらの間で䜕を共有できるかを制埡したす。たずえば、䟋倖ハンドラずプログラムの残りの郚分の間で䜕らかの倀を共有したい堎合、その倀が Send ではあるが Sync ではないなら、Mutex のようなものでラップしお static に眮く必芁がありたす。

䟋倖ベクタヌのアセンブリコヌド:

/**
 * Saves the volatile registers onto the stack. This currently takes
 * 14 instructions, so it can be used in exception handlers with 18
 * instructions left.
 *
 * On return, x0 and x1 are initialised to elr_el2 and spsr_el2
 * respectively, which can be used as the first and second arguments
 * of a subsequent call.
 */
.macro save_volatile_to_stack
	/* Reserve stack space and save registers x0-x18, x29 & x30. */
	stp x0, x1, [sp, #-(8 * 24)]!
	stp x2, x3, [sp, #8 * 2]
	stp x4, x5, [sp, #8 * 4]
	stp x6, x7, [sp, #8 * 6]
	stp x8, x9, [sp, #8 * 8]
	stp x10, x11, [sp, #8 * 10]
	stp x12, x13, [sp, #8 * 12]
	stp x14, x15, [sp, #8 * 14]
	stp x16, x17, [sp, #8 * 16]
	str x18, [sp, #8 * 18]
	stp x29, x30, [sp, #8 * 20]

	/*
	 * Save elr_el1 & spsr_el1. This such that we can take nested
	 * exception and still be able to unwind.
	 */
	mrs x0, elr_el1
	mrs x1, spsr_el1
	stp x0, x1, [sp, #8 * 22]
.endm

/**
 * Restores the volatile registers from the stack. This currently
 * takes 14 instructions, so it can be used in exception handlers
 * while still leaving 18 instructions left; if paired with
 * save_volatile_to_stack, there are 4 instructions to spare.
 */
.macro restore_volatile_from_stack
	/* Restore registers x2-x18, x29 & x30. */
	ldp x2, x3, [sp, #8 * 2]
	ldp x4, x5, [sp, #8 * 4]
	ldp x6, x7, [sp, #8 * 6]
	ldp x8, x9, [sp, #8 * 8]
	ldp x10, x11, [sp, #8 * 10]
	ldp x12, x13, [sp, #8 * 12]
	ldp x14, x15, [sp, #8 * 14]
	ldp x16, x17, [sp, #8 * 16]
	ldr x18, [sp, #8 * 18]
	ldp x29, x30, [sp, #8 * 20]

	/*
	 * Restore registers elr_el1 & spsr_el1, using x0 & x1 as scratch.
	 */
	ldp x0, x1, [sp, #8 * 22]
	msr elr_el1, x0
	msr spsr_el1, x1

	/* Restore x0 & x1, and release stack space. */
	ldp x0, x1, [sp], #8 * 24
.endm

/**
 * This is a generic handler for exceptions taken at the current EL. It saves
 * volatile registers to the stack, calls the Rust handler, restores volatile
 * registers, then returns.
 *
 * This also works for exceptions taken from lower ELs, if we don't care about
 * non-volatile registers.
 *
 * Saving state and jumping to the Rust handler takes 15 instructions, and
 * restoring and returning also takes 15 instructions, so we can fit the whole
 * handler in 30 instructions, under the limit of 32.
 */
.macro current_exception handler:req
	save_volatile_to_stack
	bl \handler
	restore_volatile_from_stack
	eret
.endm

.section .text.vector_table_el1, "ax"
.global vector_table_el1
.balign 0x800
vector_table_el1:
sync_cur_sp0:
	current_exception sync_current

.balign 0x80
irq_cur_sp0:
	current_exception irq_current

.balign 0x80
fiq_cur_sp0:
	current_exception fiq_current

.balign 0x80
serr_cur_sp0:
	current_exception serror_current

.balign 0x80
sync_cur_spx:
	current_exception sync_current

.balign 0x80
irq_cur_spx:
	current_exception irq_current

.balign 0x80
fiq_cur_spx:
	current_exception fiq_current

.balign 0x80
serr_cur_spx:
	current_exception serror_current

.balign 0x80
sync_lower_64:
	current_exception sync_lower

.balign 0x80
irq_lower_64:
	current_exception irq_lower

.balign 0x80
fiq_lower_64:
	current_exception fiq_lower

.balign 0x80
serr_lower_64:
	current_exception serror_lower

.balign 0x80
sync_lower_32:
	current_exception sync_lower

.balign 0x80
irq_lower_32:
	current_exception irq_lower

.balign 0x80
fiq_lower_32:
	current_exception fiq_lower

.balign 0x80
serr_lower_32:
	current_exception serror_lower

aarch64-rt

aarch64-rt crate は、以前に実装したアセンブリの゚ントリポむントず䟋倖ベクタヌを提䟛したす。必芁なのは、entry! マクロで main 関数をマヌクするこずだけです。

たた、以前のようにアセンブリコヌドで定矩するのではなく、Rust で初期の静的ペヌゞテヌブルを定矩できる initial_pagetable! マクロも提䟛したす。

さらに、独自に実装する代わりに、arm-pl011-uart crate の UART ドラむバヌも䜿甚できたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

mod exceptions_rt;

use aarch64_paging::descriptor::El1Attributes;
use aarch64_rt::{InitialPagetable, entry, initial_pagetable};
use arm_pl011_uart::{PL011Registers, Uart, UniqueMmioPointer};
use core::fmt::Write;
use core::panic::PanicInfo;
use core::ptr::NonNull;
use smccc::Hvc;
use smccc::psci::system_off;

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: NonNull<PL011Registers> =
    NonNull::new(0x900_0000 as _).unwrap();

/// Attributes to use for device memory in the initial identity map.
const DEVICE_ATTRIBUTES: El1Attributes = El1Attributes::VALID
    .union(El1Attributes::ATTRIBUTE_INDEX_0)
    .union(El1Attributes::ACCESSED)
    .union(El1Attributes::UXN);

/// Attributes to use for normal memory in the initial identity map.
const MEMORY_ATTRIBUTES: El1Attributes = El1Attributes::VALID
    .union(El1Attributes::ATTRIBUTE_INDEX_1)
    .union(El1Attributes::INNER_SHAREABLE)
    .union(El1Attributes::ACCESSED)
    .union(El1Attributes::NON_GLOBAL);

initial_pagetable!({
    let mut idmap = [0; 512];
    // 1 GiB of device memory.
    idmap[0] = DEVICE_ATTRIBUTES.bits();
    // 1 GiB of normal memory.
    idmap[1] = MEMORY_ATTRIBUTES.bits() | 0x40000000;
    // Another 1 GiB of device memory starting at 256 GiB.
    idmap[256] = DEVICE_ATTRIBUTES.bits() | 0x4000000000;
    InitialPagetable(idmap)
});

entry!(main);
fn main(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
    // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
    // nothing else accesses that address range.
    let mut uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };

    writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap();

    system_off::<Hvc>().unwrap();
    panic!("system_off returned");
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    system_off::<Hvc>().unwrap();
    loop {}
}
  • src/bare-metal/aps/examples で make qemu_rt を実行しお、このサンプルを QEMU で動かしたす。

䟋倖

aarch64-rt は、䟋倖ハンドラを定矩するためのトレむトず、それらを呌び出す 䟋倖ベクタ向けのアセンブリコヌドを生成するマクロを提䟛したす。

このトレむトには、単に panic する各メ゜ッドのデフォルト実装があるため、 発生しないず想定しおいる䟋倖のメ゜ッドは省略できたす。

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use aarch64_rt::{ExceptionHandlers, RegisterStateRef, exception_handlers};
use log::error;
use smccc::Hvc;
use smccc::psci::system_off;

struct Handlers;

impl ExceptionHandlers for Handlers {
    extern "C" fn sync_current(_state: RegisterStateRef) {
        error!("sync_current");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn irq_current(_state: RegisterStateRef) {
        error!("irq_current");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn fiq_current(_state: RegisterStateRef) {
        error!("fiq_current");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn serror_current(_state: RegisterStateRef) {
        error!("serror_current");
        system_off::<Hvc>().unwrap();
    }
}

exception_handlers!(Handlers);
  • exception_handlers マクロは、Rust コヌドを呌び出すための䟋倖ベクタを含む global_asm! ブロックを生成したす。これは、以前䜿甚しおいた exceptions.S ず 䌌おいたす。
  • RegisterStateRef は、䟋倖が発生したずきにアセンブリコヌドによっおレゞスタ倀が 保存されたスタックフレヌムぞの参照をラップしたす。これは、たずえば䞋䜍 EL からの SMC たたは HVC 呌び出しのパラメヌタを取り出し、䟋倖ハンドラの埩垰時に 埩元される倀を曎新するために䜿甚できたす。

その他のプロゞェクト

  • oreboot
    • 「C のない coreboot」。
    • x86、aarch64、RISC-V をサポヌトしたす。
    • 倚数のドラむバヌを自前で持぀のではなく、LinuxBoot に䟝存したす。
  • Rust RaspberryPi OS チュヌトリアル
    • 初期化、UART ドラむバヌ、シンプルなブヌトロヌダヌ、JTAG、䟋倖レベル、 䟋倖凊理、ペヌゞテヌブル。
    • Rust におけるキャッシュメンテナンスず初期化にはいく぀か泚意点があり、本番コヌドの 手本ずしおそのたた真䌌するには必ずしもよい䟋ではありたせん。
  • cargo-call-stack
    • 最倧スタック䜿甚量を算出するための静的解析。
  • RaspberryPi OS チュヌトリアルでは、MMU ずキャッシュが 有効になる前に Rust コヌドを実行したす。これによりメモリたずえばスタックが読み曞きされたす。ただし、これには、このセッションの冒頭で述べた、非敎列アクセスずキャッシュコヒヌレンシに関する問題がありたす。

圹立぀クレヌト

ベアメタルプログラミングでよくある問題を解決する、いく぀かのクレヌトを芋おいきたす。

zerocopy

zerocopy crateFuchsia 由来は、バむト列ず他の型の間を安党に倉換するためのトレむトずマクロを提䟛したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use zerocopy::{Immutable, IntoBytes};

#[repr(u32)]
#[derive(Debug, Default, Immutable, IntoBytes)]
enum RequestType {
    #[default]
    In = 0,
    Out = 1,
    Flush = 4,
}

#[repr(C)]
#[derive(Debug, Default, Immutable, IntoBytes)]
struct VirtioBlockRequest {
    request_type: RequestType,
    reserved: u32,
    sector: u64,
}

fn main() {
    let request = VirtioBlockRequest {
        request_type: RequestType::Flush,
        sector: 42,
        ..Default::default()
    };

    assert_eq!(
        request.as_bytes(),
        &[4, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0]
    );
}

これは MMIO には適しおいたせんvolatile な読み曞きを䜿甚しないためが、たずえば DMA によっおハヌドりェアず共有される構造䜓や、䜕らかの倖郚むンタヌフェヌス経由で送信される構造䜓を扱う際には有甚です。

  • FromBytes は、どのようなバむトパタヌンでも有効である型に察しお実装できるため、信頌できないバむト列からでも安党に倉換できたす。
  • これらの型に察しお FromBytes を derive しようずするず倱敗したす。これは、RequestType が刀別子ずしお u32 の取り埗るすべおの倀を䜿甚しおいるわけではなく、そのためすべおのバむトパタヌンが有効ではないからです。
  • zerocopy::byteorder には、バむトオヌダヌを意識した数倀プリミティブの型がありたす。
  • src/bare-metal/useful-crates/zerocopy-example/ で cargo run を䜿っおこの䟋を実行しおください。crate 䟝存関係があるため、Playground では実行されたせん。

aarch64-paging

aarch64-paging クレヌトを䜿うず、AArch64 仮想メモリシステムアヌキテクチャに埓っおペヌゞテヌブルを䜜成できたす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use aarch64_paging::{
    idmap::IdMap,
    paging::{Attributes, MemoryRegion},
};

const ASID: usize = 1;
const ROOT_LEVEL: usize = 1;

// 恒等マッピングを持぀新しいペヌゞテヌブルを䜜成したす。
let mut idmap = IdMap::new(ASID, ROOT_LEVEL);
// 2 MiB のメモリ領域を読み取り専甚ずしおマップしたす。
idmap.map_range(
    &MemoryRegion::new(0x80200000, 0x80400000),
    Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::READ_ONLY,
).unwrap();
// ペヌゞテヌブルを有効化するために `TTBR0_EL1` を蚭定したす。
idmap.activate();
  • これは Android の Protected VM Firmware で䜿甚されおいたす。
  • この䟋は実機たたは QEMU 䞊で実行する必芁があるため、これ単䜓で簡単に実行する方法はありたせん。

buddy_system_allocator

buddy_system_allocator は、基本的なバディシステム アロケヌタを実装するクレヌトです。これは、LockedHeap を䜿っお GlobalAlloc を実装し、暙準の alloc クレヌトを䜿えるようにするため 前に芋たずおりにも、FrameAllocator を䜿っお別の アドレス空間を割り圓おるためにも䜿甚できたす。たずえば、PCI BAR 甚に MMIO 空間を割り圓おたいこずがありたす:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use buddy_system_allocator::FrameAllocator;
use core::alloc::Layout;

fn main() {
    let mut allocator = FrameAllocator::<32>::new();
    allocator.add_frame(0x200_0000, 0x400_0000);

    let layout = Layout::from_size_align(0x100, 0x100).unwrap();
    let bar = allocator
        .alloc_aligned(layout)
        .expect("Failed to allocate 0x100 byte MMIO region");
    println!("Allocated 0x100 byte MMIO region at {:#x}", bar);
}
  • PCI BAR は垞に、そのサむズず同じアラむンメントを持ちたす。
  • src/bare-metal/useful-crates/allocator-example/ で cargo run を実行しお この䟋を動かしおください。クレヌトの䟝存関係があるため、Playground では実行できたせん。

tinyvec

ヒヌプ割り圓おを行わずに、Vec のようにサむズ倉曎できるものが欲しくなるこずがありたす。tinyvec はそれを提䟛したす。これは配列たたはスラむスを基盀ずするベクタで、静的に割り圓おるこずもスタック䞊に配眮するこずもでき、䜿甚䞭の芁玠数を远跡し、割り圓おられおいる数を超えお䜿おうずするずパニックしたす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use tinyvec::{ArrayVec, array_vec};

fn main() {
    let mut numbers: ArrayVec<[u32; 5]> = array_vec!(42, 66);
    println!("{numbers:?}");
    numbers.push(7);
    println!("{numbers:?}");
    numbers.remove(1);
    println!("{numbers:?}");
}
  • tinyvec では、初期化のために芁玠型が Default を実装しおいる必芁がありたす。
  • Rust Playground には tinyvec が含たれおいるため、この䟋はむンラむンでも問題なく実行できたす。

spin

std::sync::Mutex ず、std::sync のその他の同期プリミティブは、core や alloc では利甚できたせん。異なる CPU 間で状態を共有する堎合など、同期や内郚可倉性をどのように管理すればよいのでしょうか。

spin クレヌトは、これらのプリミティブの倚くに぀いお、スピンロックベヌスの同等物を提䟛したす。

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

use spin::mutex::SpinMutex;

static COUNTER: SpinMutex<u32> = SpinMutex::new(0);

fn main() {
    dbg!(COUNTER.lock());
    *COUNTER.lock() += 2;
    dbg!(COUNTER.lock());
}
  • 割り蟌みハンドラ内でロックを取埗する堎合は、デッドロックを避けるよう泚意しおください。
  • spin には、チケットロック mutex 実装、std::sync の RwLock・Barrier・Once に盞圓するもの、そしお遅延初期化のための Lazy もありたす。
  • once_cell クレヌトにも、spin::once::Once ずは少し異なるアプロヌチによる、遅延初期化甚の䟿利な型がいく぀かありたす。
  • Rust Playground には spin が含たれおいるため、この䟋はむンラむンでも問題なく実行できたす。

Android でのベアメタル

AOSP でベアメタルの Rust バむナリをビルドするには、たず rust_ffi_static Soong ルヌルを䜿っお Rust コヌドをビルドし、次にリンカスクリプト付きの cc_binary を䜿っおバむナリ本䜓を生成し、その埌 raw_binary を䜿っお ELF を実行可胜な raw バむナリに倉換する必芁がありたす。

rust_ffi_static {
    name: "libvmbase_example",
    defaults: ["vmbase_ffi_defaults"],
    crate_name: "vmbase_example",
    srcs: ["src/main.rs"],
    rustlibs: [
        "libvmbase",
    ],
}

cc_binary {
    name: "vmbase_example",
    defaults: ["vmbase_elf_defaults"],
    srcs: [
        "idmap.S",
    ],
    static_libs: [
        "libvmbase_example",
    ],
    linker_scripts: [
        "image.ld",
        ":vmbase_sections",
    ],
}

raw_binary {
    name: "vmbase_example_bin",
    stem: "vmbase_example.bin",
    src: ":vmbase_example",
    enabled: false,
    target: {
        android_arm64: {
            enabled: true,
        },
    },
}

vmbase

aarch64 䞊で crosvm 䞊で動䜜する VM 向けに、vmbase ラむブラリは、リンカスクリプトずビルドルヌル甚の䟿利なデフォルトに加えお、゚ントリポむント、UART コン゜ヌルロギングなどを提䟛したす。

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

use vmbase::{main, println};

main!(main);

pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) {
    println!("Hello world");
}
  • main! マクロはメむン関数を指定し、その関数は vmbase の゚ントリポむントから呌び出されたす。
  • vmbase の゚ントリポむントはコン゜ヌルの初期化を凊理し、メむン関数が終了した堎合は VM をシャットダりンするために PSCI_SYSTEM_OFF を発行したす。

挔習

PL031 リアルタむムクロックデバむス甚のドラむバヌを䜜成したす。

挔習を確認したら、甚意されおいるsolutionsを芋るこずができたす。

RTC ドラむバヌ

QEMU aarch64 virt マシンには、0x9010000 に PL031 リアルタむムクロックがありたす。この 挔習では、そのドラむバヌを䜜成しおください。

  1. これを䜿っお珟圚時刻をシリアルコン゜ヌルに衚瀺しおください。日付/時刻のフォヌマットには chrono クレヌトを䜿甚できたす。
  2. マッチレゞスタず生の割り蟌みステヌタスを䜿っお、指定した 時刻、たずえば 3 秒埌たでビゞヌりェむトしおください。ルヌプ内で core::hint::spin_loop を呌び出しおください。
  3. 時間があれば発展課題: RTC マッチによっお生成される割り蟌みを有効化しお凊理しおください。 Arm Generic Interrupt Controller を蚭定するには、arm-gic クレヌトで提䟛される ドラむバヌを䜿甚できたす。
    • RTC 割り蟌みを䜿甚しおください。これは GIC に IntId::spi(2) ずしお接続されおいたす。
    • 割り蟌みを有効にしたら、 arm_gic::wfi() を䜿っおコアをスリヌプ状態にできたす。これにより、コアは 割り蟌みを受信するたでスリヌプしたす。

挔習テンプレヌト をダりンロヌドし、 rtc ディレクトリ内の次のファむルを確認しおください。

src/main.rs:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

mod exceptions;
mod logger;

use aarch64_paging::descriptor::El1Attributes;
use aarch64_rt::{InitialPagetable, entry, initial_pagetable};
use arm_gic::gicv3::registers::{Gicd, GicrSgi};
use arm_gic::gicv3::{GicCpuInterface, GicV3};
use arm_pl011_uart::{PL011Registers, Uart};
use core::panic::PanicInfo;
use core::ptr::NonNull;
use log::{LevelFilter, error, info, trace};
use safe_mmio::UniqueMmioPointer;
use smccc::Hvc;
use smccc::psci::system_off;

/// Base addresses of the GICv3.
const GICD_BASE_ADDRESS: NonNull<Gicd> = NonNull::new(0x800_0000 as _).unwrap();
const GICR_BASE_ADDRESS: NonNull<GicrSgi> = NonNull::new(0x80A_0000 as _).unwrap();

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: NonNull<PL011Registers> =
    NonNull::new(0x900_0000 as _).unwrap();

/// Attributes to use for device memory in the initial identity map.
const DEVICE_ATTRIBUTES: El1Attributes = El1Attributes::VALID
    .union(El1Attributes::ATTRIBUTE_INDEX_0)
    .union(El1Attributes::ACCESSED)
    .union(El1Attributes::UXN);

/// Attributes to use for normal memory in the initial identity map.
const MEMORY_ATTRIBUTES: El1Attributes = El1Attributes::VALID
    .union(El1Attributes::ATTRIBUTE_INDEX_1)
    .union(El1Attributes::INNER_SHAREABLE)
    .union(El1Attributes::ACCESSED)
    .union(El1Attributes::NON_GLOBAL);

initial_pagetable!({
    let mut idmap = [0; 512];
    // 1 GiB of device memory.
    idmap[0] = DEVICE_ATTRIBUTES.bits();
    // 1 GiB of normal memory.
    idmap[1] = MEMORY_ATTRIBUTES.bits() | 0x40000000;
    // Another 1 GiB of device memory starting at 256 GiB.
    idmap[256] = DEVICE_ATTRIBUTES.bits() | 0x4000000000;
    InitialPagetable(idmap)
});

entry!(main);
fn main(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
    // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
    // nothing else accesses that address range.
    let uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
    logger::init(uart, LevelFilter::Trace).unwrap();

    info!("main({:#x}, {:#x}, {:#x}, {:#x})", x0, x1, x2, x3);

    // SAFETY: `GICD_BASE_ADDRESS` and `GICR_BASE_ADDRESS` are the base
    // addresses of a GICv3 distributor and redistributor respectively, and
    // nothing else accesses those address ranges.
    let mut gic = unsafe {
        GicV3::new(
            UniqueMmioPointer::new(GICD_BASE_ADDRESS),
            GICR_BASE_ADDRESS,
            1,
            false,
        )
    };
    gic.setup(0);

    // TODO: RTC ドラむバヌのむンスタンスを䜜成し、珟圚時刻を衚瀺する。

    // TODO: 3 秒埅機する。

    system_off::<Hvc>().unwrap();
    panic!("system_off returned");
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    error!("{info}");
    system_off::<Hvc>().unwrap();
    loop {}
}

src/exceptions.rsこれを倉曎する必芁があるのは、挔習の第 3 郚分だけのはずです:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aarch64_rt::{ExceptionHandlers, RegisterStateRef, exception_handlers};
use arm_gic::InterruptGroup;
use arm_gic::gicv3::GicCpuInterface;
use log::{error, info, trace};
use smccc::Hvc;
use smccc::psci::system_off;

struct Handlers;

impl ExceptionHandlers for Handlers {
    extern "C" fn sync_current(_state: RegisterStateRef) {
        error!("sync_current");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn irq_current(_state: RegisterStateRef) {
        trace!("irq_current");
        let intid =
            GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1)
                .expect("No pending interrupt");
        info!("IRQ {intid:?}");
    }

    extern "C" fn fiq_current(_state: RegisterStateRef) {
        error!("fiq_current");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn serror_current(_state: RegisterStateRef) {
        error!("serror_current");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn sync_lower(_state: RegisterStateRef) {
        error!("sync_lower");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn irq_lower(_state: RegisterStateRef) {
        error!("irq_lower");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn fiq_lower(_state: RegisterStateRef) {
        error!("fiq_lower");
        system_off::<Hvc>().unwrap();
    }

    extern "C" fn serror_lower(_state: RegisterStateRef) {
        error!("serror_lower");
        system_off::<Hvc>().unwrap();
    }
}

exception_handlers!(Handlers);
}

src/logger.rsこれは倉曎する必芁はないはずです:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use arm_pl011_uart::Uart;
use core::fmt::Write;
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
use spin::mutex::SpinMutex;

static LOGGER: Logger = Logger { uart: SpinMutex::new(None) };

struct Logger {
    uart: SpinMutex<Option<Uart<'static>>>,
}

impl Log for Logger {
    fn enabled(&self, _metadata: &Metadata) -> bool {
        true
    }

    fn log(&self, record: &Record) {
        writeln!(
            self.uart.lock().as_mut().unwrap(),
            "[{}] {}",
            record.level(),
            record.args()
        )
        .unwrap();
    }

    fn flush(&self) {}
}

/// Initialises UART logger.
pub fn init(
    uart: Uart<'static>,
    max_level: LevelFilter,
) -> Result<(), SetLoggerError> {
    LOGGER.uart.lock().replace(uart);

    log::set_logger(&LOGGER)?;
    log::set_max_level(max_level);
    Ok(())
}
}

Cargo.tomlこれは倉曎する必芁はないはずです:

[workspace]

[package]
name = "rtc"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
aarch64-paging = { version = "0.12.1", default-features = false }
aarch64-rt = "0.4.3"
arm-gic = "0.8.1"
arm-pl011-uart = "0.5.0"
bitflags = "2.11.1"
chrono = { version = "0.4.44", default-features = false }
log = "0.4.29"
safe-mmio = "0.3.0"
smccc = "0.2.2"
spin = "0.10.0"
zerocopy = "0.8.48"

build.rsこれは倉曎する必芁はないはずです:

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

fn main() {
    println!("cargo:rustc-link-arg=-Timage.ld");
    println!("cargo:rustc-link-arg=-Tmemory.ld");
    println!("cargo:rerun-if-changed=memory.ld");
}

memory.ldこれは倉曎する必芁はないはずです:

/*
 * Copyright 2023 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

MEMORY
{
	image : ORIGIN = 0x40080000, LENGTH = 2M
}

Makefileこれは倉曎する必芁はないはずです:

# Copyright 2023 Google LLC
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

.PHONY: build qemu_minimal qemu qemu_logger

all: rtc.bin

build:
	cargo build

rtc.bin: build
	cargo objcopy -- -O binary $@

qemu: rtc.bin
	qemu-system-aarch64 -machine virt,gic-version=3 -cpu max -serial mon:stdio -display none -kernel $< -s

clean:
	cargo clean
	rm -f *.bin

.cargo/config.tomlこれは倉曎する必芁はないはずです:

[build]
target = "aarch64-unknown-none"

QEMU でコヌドを実行するには make qemu を実行しおください。

ベアメタル Rust 午埌

RTC ドラむバ

(挔習に戻る)

main.rs:

// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#![no_main]
#![no_std]

mod exceptions;
mod logger;
mod pl031;

use crate::pl031::Rtc;
use arm_gic::{IntId, Trigger, irq_enable, wfi};
use chrono::{TimeZone, Utc};
use core::hint::spin_loop;
use aarch64_paging::descriptor::El1Attributes;
use aarch64_rt::{InitialPagetable, entry, initial_pagetable};
use arm_gic::gicv3::registers::{Gicd, GicrSgi};
use arm_gic::gicv3::{GicCpuInterface, GicV3};
use arm_pl011_uart::{PL011Registers, Uart};
use core::panic::PanicInfo;
use core::ptr::NonNull;
use log::{LevelFilter, error, info, trace};
use safe_mmio::UniqueMmioPointer;
use smccc::Hvc;
use smccc::psci::system_off;

/// Base addresses of the GICv3.
const GICD_BASE_ADDRESS: NonNull<Gicd> = NonNull::new(0x800_0000 as _).unwrap();
const GICR_BASE_ADDRESS: NonNull<GicrSgi> = NonNull::new(0x80A_0000 as _).unwrap();

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: NonNull<PL011Registers> =
    NonNull::new(0x900_0000 as _).unwrap();

/// Attributes to use for device memory in the initial identity map.
const DEVICE_ATTRIBUTES: El1Attributes = El1Attributes::VALID
    .union(El1Attributes::ATTRIBUTE_INDEX_0)
    .union(El1Attributes::ACCESSED)
    .union(El1Attributes::UXN);

/// Attributes to use for normal memory in the initial identity map.
const MEMORY_ATTRIBUTES: El1Attributes = El1Attributes::VALID
    .union(El1Attributes::ATTRIBUTE_INDEX_1)
    .union(El1Attributes::INNER_SHAREABLE)
    .union(El1Attributes::ACCESSED)
    .union(El1Attributes::NON_GLOBAL);

initial_pagetable!({
    let mut idmap = [0; 512];
    // 1 GiB of device memory.
    idmap[0] = DEVICE_ATTRIBUTES.bits();
    // 1 GiB of normal memory.
    idmap[1] = MEMORY_ATTRIBUTES.bits() | 0x40000000;
    // Another 1 GiB of device memory starting at 256 GiB.
    idmap[256] = DEVICE_ATTRIBUTES.bits() | 0x4000000000;
    InitialPagetable(idmap)
});

/// Base address of the PL031 RTC.
const PL031_BASE_ADDRESS: NonNull<pl031::Registers> =
    NonNull::new(0x901_0000 as _).unwrap();
/// The IRQ used by the PL031 RTC.
const PL031_IRQ: IntId = IntId::spi(2);

entry!(main);
fn main(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
    // SAFETY: `PL011_BASE_ADDRESS` is the base address of a PL011 device, and
    // nothing else accesses that address range.
    let uart = unsafe { Uart::new(UniqueMmioPointer::new(PL011_BASE_ADDRESS)) };
    logger::init(uart, LevelFilter::Trace).unwrap();

    info!("main({:#x}, {:#x}, {:#x}, {:#x})", x0, x1, x2, x3);

    // SAFETY: `GICD_BASE_ADDRESS` and `GICR_BASE_ADDRESS` are the base
    // addresses of a GICv3 distributor and redistributor respectively, and
    // nothing else accesses those address ranges.
    let mut gic = unsafe {
        GicV3::new(
            UniqueMmioPointer::new(GICD_BASE_ADDRESS),
            GICR_BASE_ADDRESS,
            1,
            false,
        )
    };
    gic.setup(0);

    // SAFETY: `PL031_BASE_ADDRESS` is the base address of a PL031 device, and
    // nothing else accesses that address range.
    let mut rtc = unsafe { Rtc::new(UniqueMmioPointer::new(PL031_BASE_ADDRESS)) };
    let timestamp = rtc.read();
    let time = Utc.timestamp_opt(timestamp.into(), 0).unwrap();
    info!("RTC: {time}");

    GicCpuInterface::set_priority_mask(0xff);
    gic.set_interrupt_priority(PL031_IRQ, None, 0x80).unwrap();
    gic.set_trigger(PL031_IRQ, None, Trigger::Level).unwrap();
    irq_enable();
    gic.enable_interrupt(PL031_IRQ, None, true).unwrap();

    // Wait for 3 seconds, without interrupts.
    let target = timestamp + 3;
    rtc.set_match(target);
    info!("Waiting for {}", Utc.timestamp_opt(target.into(), 0).unwrap());
    trace!(
        "matched={}, interrupt_pending={}",
        rtc.matched(),
        rtc.interrupt_pending()
    );
    while !rtc.matched() {
        spin_loop();
    }
    trace!(
        "matched={}, interrupt_pending={}",
        rtc.matched(),
        rtc.interrupt_pending()
    );
    info!("Finished waiting");

    // Wait another 3 seconds for an interrupt.
    let target = timestamp + 6;
    info!("Waiting for {}", Utc.timestamp_opt(target.into(), 0).unwrap());
    rtc.set_match(target);
    rtc.clear_interrupt();
    rtc.enable_interrupt(true);
    trace!(
        "matched={}, interrupt_pending={}",
        rtc.matched(),
        rtc.interrupt_pending()
    );
    while !rtc.interrupt_pending() {
        wfi();
    }
    trace!(
        "matched={}, interrupt_pending={}",
        rtc.matched(),
        rtc.interrupt_pending()
    );
    info!("Finished waiting");

    system_off::<Hvc>().unwrap();
    panic!("system_off returned");
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    error!("{info}");
    system_off::<Hvc>().unwrap();
    loop {}
}

pl031.rs:

#![allow(unused)]
fn main() {
// 著䜜暩 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[repr(C, align(4))]
pub struct Registers {
    /// Data register
    dr: ReadPure<u32>,
    /// Match register
    mr: ReadPureWrite<u32>,
    /// Load register
    lr: ReadPureWrite<u32>,
    /// Control register
    cr: ReadPureWrite<u8>,
    _reserved0: [u8; 3],
    /// Interrupt Mask Set or Clear register
    imsc: ReadPureWrite<u8>,
    _reserved1: [u8; 3],
    /// Raw Interrupt Status
    ris: ReadPure<u8>,
    _reserved2: [u8; 3],
    /// Masked Interrupt Status
    mis: ReadPure<u8>,
    _reserved3: [u8; 3],
    /// Interrupt Clear Register
    icr: WriteOnly<u8>,
    _reserved4: [u8; 3],
}

/// Driver for a PL031 real-time clock.
#[derive(Debug)]
pub struct Rtc<'a> {
    registers: UniqueMmioPointer<'a, Registers>,
}

impl<'a> Rtc<'a> {
    /// Constructs a new instance of the RTC driver for a PL031 device with the
    /// given set of registers.
    pub fn new(registers: UniqueMmioPointer<'a, Registers>) -> Self {
        Self { registers }
    }

    /// Reads the current RTC value.
    pub fn read(&self) -> u32 {
        field_shared!(self.registers, dr).read()
    }

    /// Writes a match value. When the RTC value matches this then an interrupt
    /// will be generated (if it is enabled).
    pub fn set_match(&mut self, value: u32) {
        field!(self.registers, mr).write(value);
    }

    /// Returns whether the match register matches the RTC value, whether or not
    /// the interrupt is enabled.
    pub fn matched(&self) -> bool {
        let ris = field_shared!(self.registers, ris).read();
        (ris & 0x01) != 0
    }

    /// Returns whether there is currently an interrupt pending.
    ///
    /// This should be true if and only if `matched` returns true and the
    /// interrupt is masked.
    pub fn interrupt_pending(&self) -> bool {
        let mis = field_shared!(self.registers, mis).read();
        (mis & 0x01) != 0
    }

    /// Sets or clears the interrupt mask.
    ///
    /// When the mask is true the interrupt is enabled; when it is false the
    /// interrupt is disabled.
    pub fn enable_interrupt(&mut self, mask: bool) {
        let imsc = if mask { 0x01 } else { 0x00 };
        field!(self.registers, imsc).write(imsc);
    }

    /// Clears a pending interrupt, if any.
    pub fn clear_interrupt(&mut self) {
        field!(self.registers, icr).write(0x01);
    }
}
}

Rust の䞊行性ぞようこそ

Rust は、ミュヌテックスやチャネルを備えた OS スレッドを䜿甚する䞊行性を 完党にサポヌトしおいたす。

Rust の型システムは、倚くの䞊行性バグをコンパむル時゚ラヌにするうえで 重芁な圹割を果たしたす。この考え方は fearless concurrency ずしお 知られおおり、実行時の正しさをコンパむラに頌っお保蚌できるからです。

スケゞュヌル

session outline

  • Rust では、スレッド、同期プリミティブなど、OS の䞊行凊理ツヌルキットを利甚できたす。
  • 型システムにより、特別な機胜がなくおも䞊行性の安党性が埗られたす。
  • 単䞀スレッドでの「䞊行した」アクセスたずえば、呌び出された関数が匕数を倉曎 したり、それぞの参照を保存しお埌で読んだりする堎合に圹立぀のず同じ ツヌルが、マルチスレッドの問題からも私たちを守っおくれたす。

スレッド

segment outline

通垞のスレッド

Rust のスレッドは、他の蚀語のスレッドず同様に動䜜したす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

fn main() {
    thread::spawn(|| {
        for i in 0..10 {
            println!("Count in thread: {i}!");
            thread::sleep(Duration::from_millis(5));
        }
    });

    for i in 0..5 {
        println!("Main thread: {i}");
        thread::sleep(Duration::from_millis(5));
    }
}
  • 新しいスレッドを生成しおも、main の最埌でプログラムの終了が自動的に 遅らされるこずはありたせん。
  • スレッドの panic は互いに独立しおいたす。
    • panic はペむロヌドを持぀こずができ、その内容は Any::downcast_ref で取り出せたす。
  • この䟋を実行したす。

    • 5ms ずいうタむミングには十分な䜙裕があるため、main スレッドず生成した スレッドはほが足䞊みをそろえお進みたす。
    • 生成したスレッドが 10 に到達する前にプログラムが終了するこずに泚目しお ください。
    • これは main がプログラムを終了させ、生成したスレッドはプログラムを 存続させないためです。
      • 必芁であれば、pthreads/C++ std::thread/boost::thread ず比范しお みおください。
  • 生成したスレッドが完了するたで埅぀には、どうすればよいでしょうか。

  • thread::spawn は JoinHandle を返したす。ドキュメントを芋おください。

    • JoinHandle には、ブロックする .join() メ゜ッドがありたす。
  • let handle = thread::spawn(...) ずし、埌で handle.join() を䜿っお スレッドの終了を埅぀ず、プログラムは最埌たで数えお 10 に到達したす。

  • では、倀を返したい堎合はどうでしょうか。

  • もう䞀床ドキュメントを芋おください:

    • thread::spawn のクロヌゞャは T を返したす
    • JoinHandle の .join() は thread::Result<T> を返したす
  • handle.join() の Result の戻り倀を䜿っお、返された倀にアクセスしたす。

  • では、もう䞀方のケヌスはどうでしょうか。

    • スレッドで panic を発生させたす。これによっお main は panic しないこずに 泚目しおください。
    • panic のペむロヌドにアクセスしたす。ここで Any に぀いお説明するのに ちょうどよいタむミングです。
  • これでスレッドから倀を返せるようになりたした。では、入力を受け取るには どうすればよいでしょうか。

    • スレッドのクロヌゞャで䜕かを参照ずしおキャプチャしたす。
    • ゚ラヌメッセヌゞは、それを move しなければならないこずを瀺したす。
    • それを move するず、蚈算しおから掟生した倀を返せるこずがわかりたす。
  • 借甚したい堎合はどうでしょうか。

    • main は戻るずきに子スレッドを終了させたすが、別の関数であれば単に 戻っお、それらを実行したたたにしおしたいたす。
    • それはスタック use-after-return であり、メモリ安党性に違反したす!
    • これをどう避ければよいでしょうか。次のスラむドを芋おください。

スコヌプ付きスレッド

通垞のスレッドは、呚囲の環境から借甚するこずはできたせん:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::thread;

fn foo() {
    let s = String::from("Hello");
    thread::spawn(|| {
        dbg!(s.len());
    });
}

fn main() {
    foo();
}

ただし、この堎合はスコヌプ付きスレッドを䜿えたす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::thread;

fn foo() {
    let s = String::from("Hello");
    thread::scope(|scope| {
        scope.spawn(|| {
            dbg!(s.len());
        });
    });
}

fn main() {
    foo();
}
  • その理由は、thread::scope 関数が完了するず、すべおの スレッドが join されおいるこずが保蚌されるため、借甚したデヌタを返せるからです。
  • 通垞の Rust の借甚ルヌルが適甚されたす。1 ぀のスレッドによる可倉借甚か、 任意個のスレッドによる䞍倉借甚のいずれか䞀方だけが可胜です。

チャネル

segment outline

Sender ず Receiver

Rust のチャネルは 2 ぀の郚分から成りたす: Sender<T> ず Receiver<T> です。この 2 ぀の郚分はチャネルを介しお接続されおいたすが、芋えるのぱンドポむントだけです。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    tx.send(10).unwrap();
    tx.send(20).unwrap();

    println!("Received: {:?}", rx.recv());
    println!("Received: {:?}", rx.recv());

    let tx2 = tx.clone();
    tx2.send(30).unwrap();
    println!("Received: {:?}", rx.recv());
}
  • mpsc は Multi-Producer, Single-Consumer の略です。Sender ず SyncSender は Clone を実装しおいるため、耇数の送信偎を䜜成できたすが、Receiver は実装しおいたせん。
  • send() ず recv() は Result を返したす。Err が返された堎合、察応する Sender たたは Receiver がドロップされ、チャネルが閉じられおいるこずを意味したす。

非境界チャネル

mpsc::channel() を䜿うず、非境界か぀非同期のチャネルを取埗できたす:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let thread_id = thread::current().id();
        for i in 0..10 {
            tx.send(format!("Message {i}")).unwrap();
            println!("{thread_id:?}: sent Message {i}");
        }
        println!("{thread_id:?}: done");
    });
    thread::sleep(Duration::from_millis(100));

    for msg in rx {
        println!("Main: got {msg}");
    }
}
  • 非境界チャネルは、保留䞭のメッセヌゞを栌玍するために必芁なだけの領域を割り圓おたす。send() メ゜ッドは呌び出し元のスレッドをブロックしたせん。
  • チャネルが閉じられおいる堎合、send() の呌び出しぱラヌで倱敗したすそのため Result を返したす。受信偎がドロップされるず、チャネルは閉じられたす。

有界チャネル

有界同期チャネルでは、send() が珟圚のスレッドをブロックするこずがありたす:

// Copyright 2022 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::sync_channel(3);

    thread::spawn(move || {
        let thread_id = thread::current().id();
        for i in 0..10 {
            tx.send(format!("Message {i}")).unwrap();
            println!("{thread_id:?}: sent Message {i}");
        }
        println!("{thread_id:?}: done");
    });
    thread::sleep(Duration::from_millis(100));

    for msg in rx {
        println!("Main: got {msg}");
    }
}
  • send() を呌び出すず、新しいメッセヌゞを入れる空きがチャネルに できるたで、珟圚のスレッドはブロックされたす。チャネルから 読み取るものが誰もいない堎合、スレッドは無期限にブロックされる可胜性が ありたす。
  • 無界チャネルず同様に、チャネルが閉じられおいる堎合、send() の 呌び出しぱラヌを返しお倱敗したす。
  • サむズが 0 の有界チャネルは「ランデブヌチャネル」ず呌ばれたす。すべおの 送信は、別のスレッドが recv() を呌び出すたで、珟圚のスレッドをブロックしたす。

Send ず Sync

segment outline

マヌカヌトレむト

Rust はどのようにしお、スレッドをたたぐ共有アクセスを犁止すべきだず刀断しおいるのでしょうか。その答えは、次の 2 ぀のトレむトにありたす。

  • Send: 型 T は、T をスレッド境界をたたいで移動しおも安党である堎合に Send です。
  • Sync: 型 T は、&T をスレッド境界をたたいで移動しおも安党である堎合に Sync です。

Send ず Sync は unsafe トレむト です。コンパむラは、それらが Send ず Sync な型だけを含む限り、あなたの型に察しおこれらを自動的に導出したす。それが劥圓だず分かっおいる堎合は、手動で実装するこずもできたす。

  • これらのトレむトは、その型が特定のスレッド安党性の性質を持぀こずを瀺すマヌカヌず考えるこずができたす。
  • 通垞のトレむトず同様に、ゞェネリック制玄で䜿甚できたす。

Send

型 T は、T 型の倀を別のスレッドぞ移動しおも安党である堎合に Send です。

所有暩を別のスレッドに移すず、デストラクタ はそのスレッドで実行されたす。したがっお問題ずなるのは、あるスレッドで倀を割り圓お、別のスレッドでそれを解攟できるのはどのような堎合かずいうこずです。

たずえば、SQLite ラむブラリぞの接続は単䞀のスレッドからのみアクセスしなければなりたせん。

Sync

型 T が Sync であるのは、耇数のスレッドから同時に T 型の倀ぞ安党にアクセスできる堎合です。

より正確には、定矩は次のずおりです。

T が Sync であるのは、&T が Send である堎合か぀その堎合に限りたす

この蚘述は本質的には、ある型が共有しお䜿甚しおもスレッドセヌフであるなら、その型ぞの参照をスレッド間で受け枡すこずもスレッドセヌフである、ずいうこずを簡朔に衚したものです。

これは、ある型が Sync であるなら、デヌタ競合やその他の同期に関する問題の危険なく、その型を耇数のスレッド間で共有できるこずを意味するため、別のスレッドぞ移しおも安党だからです。その型ぞの参照も、参照先のデヌタにはどのスレッドからでも安党にアクセスできるため、別のスレッドぞ移しおも安党です。

䟋

Send + Sync

よく芋かけるほずんどの型は Send + Sync です:

  • i8、f32、bool、char、&str など
  • (T1, T2)、[T; N]、&[T]、struct { x: T } など
  • String、Option<T>、Vec<T>、Box<T> など
  • Arc<T>: アトミックな参照カりントによっお明瀺的にスレッドセヌフです。
  • Mutex<T>: 内郚ロックによっお明瀺的にスレッドセヌフです。
  • mpsc::Sender<T>: 1.72.0 以降。
  • AtomicBool, AtomicU8, 
: 特別なアトミック呜什を䜿甚したす。

ゞェネリック型は、型パラメヌタが Send + Sync であれば、通垞 Send + Sync です。

Send + !Sync

これらの型は他のスレッドに移動できたすが、スレッドセヌフではありたせん。 通垞は、内郚可倉性があるためです:

  • mpsc::Receiver<T>
  • Cell<T>
  • RefCell<T>

!Send + Sync

これらの型は耇数のスレッドから共有参照を介しお安党にアクセスできたすが、 別のスレッドに移動するこずはできたせん:

  • MutexGuard<T>: 䜜成したスレッド䞊で解攟しなければならない OS レベルの プリミティブを䜿甚したす。ただし、すでにロックされおいる mutex では、 ガヌドが共有されおいる任意のスレッドが、その保護察象の倉数を読み取るこずが できたすただし T 自䜓が !Sync の堎合を陀きたす。

!Send + !Sync

これらの型はスレッドセヌフではなく、他のスレッドに移動するこずもできたせん:

  • Rc<T>: 各 Rc<T> は RcBox<T> ぞの参照を持っおおり、その䞭には 非アトミックな参照カりントが含たれおいたす。
  • *const T, *mut T: Rust は、生ポむンタには特別な䞊行性に関する考慮事項が ある可胜性があるず想定しおいたす。

共有状態

segment outline

Arc

Arc<T> は、Arc::clone を䜿っお共有の読み取り専甚所有暩を実珟したす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;
use std::thread;

/// どのスレッドがこれをドロップしたかを出力する構造䜓。
#[derive(Debug)]
struct WhereDropped(Vec<i32>);

impl Drop for WhereDropped {
    fn drop(&mut self) {
        println!("Dropped by {:?}", thread::current().id())
    }
}

fn main() {
    let v = Arc::new(WhereDropped(vec![10, 20, 30]));
    let mut handles = Vec::new();
    for i in 0..5 {
        let v = Arc::clone(&v);
        handles.push(thread::spawn(move || {
            // 0〜500ms スリヌプしたす。
            std::thread::sleep(std::time::Duration::from_millis(500 - i * 100));
            let thread_id = thread::current().id();
            println!("{thread_id:?}: {v:?}");
        }));
    }

    // これで、生成されたスレッドだけが `v` のクロヌンを保持したす。
    drop(v);

    // 最埌に生成されたスレッドが終了するず、`v` の内容がドロップされたす。
    handles.into_iter().for_each(|h| h.join().unwrap());
}
  • Arc は “Atomic Reference Counted” の略で、アトミック操䜜を䜿甚する Rc のスレッドセヌフ版です。
  • Arc<T> は、T が Clone を実装しおいるかどうかに関係なく Clone を実装したす。T が Send ず Sync の䞡方を実装しおいる堎合に限り、それらも実装したす。
  • Arc::clone() には実行されるアトミック操䜜のコストがありたすが、その埌の T の利甚自䜓にはコストがかかりたせん。
  • 参照サむクルに泚意しおください。Arc はそれらを怜出するためのガベヌゞコレクタを䜿甚したせん。
    • std::sync::Weak が圹立ちたす。

Mutex

Mutex<T> は盞互排他を保蚌し、か぀ 読み取り専甚むンタヌフェヌス越しに T ぞの可倉アクセスを可胜にしたす内郚可倉性 の別の圢です:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::Mutex;

fn main() {
    let v = Mutex::new(vec![10, 20, 30]);
    println!("v: {:?}", v.lock().unwrap());

    {
        let mut guard = v.lock().unwrap();
        guard.push(40);
    }

    println!("v: {:?}", v.lock().unwrap());
}

ここで impl<T: Send> Sync for Mutex<T> ずいうブランケット 実装があるこずに泚目しおください。

  • Rust の Mutex は、ただ 1 ぀の芁玠 — 保護察象のデヌタ — だけを持぀ コレクションのように芋えたす。
    • 保護察象のデヌタにアクセスする前にミュヌテックスを取埗し忘れるこずは ありたせん。
  • &Mutex<T> のロックを取埗するこずで &mut T を埗られたす。MutexGuard は、&mut T の寿呜がロックの保持期間を超えないこずを保蚌したす。
  • T が Send を実装しおいる堎合に限り、Mutex<T> は Send ず Sync の䞡方を実装したす。
  • 読み曞きロックに察応するもの: RwLock。
  • なぜ lock() は Result を返すのでしょうか
    • Mutex を保持しおいたスレッドが panic するず、Mutex は、保護しおいた デヌタが䞍敎合な状態にある可胜性を瀺すために「ポむズン化」されたす。 ポむズン化されたミュヌテックスに察しお lock() を呌び出すず、 PoisonError で倱敗したす。゚ラヌに察しお into_inner() を呌び出せば、 それでもデヌタを回埩できたす。

䟋

Arc ず Mutex が実際にどのように動䜜するかを芋おみたしょう:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

fn main() {
    let v = vec![10, 20, 30];
    let mut handles = Vec::new();
    for i in 0..5 {
        handles.push(thread::spawn(|| {
            v.push(10 * i);
            println!("v: {v:?}");
        }));
    }

    handles.into_iter().for_each(|h| h.join().unwrap());
}

解答䟋:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

fn main() {
    let v = Arc::new(Mutex::new(vec![10, 20, 30]));
    let mut handles = Vec::new();
    for i in 0..5 {
        let v = Arc::clone(&v);
        handles.push(thread::spawn(move || {
            let mut v = v.lock().unwrap();
            v.push(10 * i);
            println!("v: {v:?}");
        }));
    }

    handles.into_iter().for_each(|h| h.join().unwrap());
}

泚目すべき点:

  • v は Arc ず Mutex の䞡方でラップされおいたす。これは、それぞれが扱う関心事が 盎亀しおいるためです。
    • Mutex を Arc でラップするのは、スレッド間で可倉状態を共有するための 䞀般的なパタヌンです。
  • v: Arc<_> は、新しく生成される各スレッド甚に新しい参照を䜜るために クロヌンする必芁がありたす。ラムダのシグネチャに move が远加されおいる点に泚意しおください。
  • LockGuard のスコヌプを可胜な限り狭めるために、ブロックが導入されおいたす。

挔習

segment outline

食事する哲孊者

食事する哲孊者問題は、䞊行性における叀兞的な問題です:

5 人の哲孊者が同じテヌブルで䞀緒に食事をしたす。各哲孊者には テヌブルの自分の垭がありたす。各皿の間には 1 本の箞がありたす。出される料理は スパゲッティで、食べるには 2 本の箞が必芁です。各哲孊者は 考えるこずず食べるこずを亀互に行うこずしかできたせん。さらに、哲孊者が スパゲッティを食べられるのは、巊ず右の箞の䞡方を持っおいるずきだけです。したがっお 2 本の箞が 䜿えるのは、巊右の隣人 2 人が食べおおらず、考えおいるずきだけです。 それぞれの哲孊者は食べ終えたら、 䞡方の箞を眮きたす。

この挔習を行うには、ロヌカルに Cargo をむンストヌル しおおく必芁がありたす。 以䞋のコヌドを src/main.rs ずいうファむルにコピヌし、空欄を埋めお、 cargo run がデッドロックしないこずを確認しおください:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::{Arc, Mutex, mpsc};
use std::thread;
use std::time::Duration;

struct Chopstick;

struct Philosopher {
    name: String,
    // left_chopstick: ...
    // right_chopstick: ...
    // thoughts: ...
}

impl Philosopher {
    fn think(&self) {
        self.thoughts
            .send(format!("Eureka! {} has a new idea!", &self.name))
            .unwrap();
    }

    fn eat(&self) {
        // 箞を取る...
        println!("{} is eating...", &self.name);
        thread::sleep(Duration::from_millis(10));
    }
}

static PHILOSOPHERS: &[&str] =
    &["Socrates", "Hypatia", "Plato", "Aristotle", "Pythagoras"];

fn main() {
    // 箞を䜜成する

    // 哲孊者を䜜成する

    // それぞれに 100 回考えさせお食べさせる

    // 圌らの考えを出力する
}

以䞋の Cargo.toml を䜿甚できたす:

[package]
name = "dining-philosophers"
version = "0.1.0"
edition = "2024"
  • たずは「ほが」動䜜する解決策の実装に集䞭するよう、孊生に促しおください。
  • 最も単玔な解決策におけるデッドロックは、䞀般的な䞊行性の問題であり、 Rust がこの皮のバグを自動的に防いでくれるわけではないこずを瀺しおいたす。

マルチスレッドのリンクチェッカヌ

新しく埗た知識を䜿っお、マルチスレッドのリンクチェッカヌを䜜成しおみたしょう。これは Web ペヌゞから開始し、そのペヌゞ䞊のリンクが有効であるこずを確認できるようにする必芁がありたす。同じドメむン内の他のペヌゞも再垰的に確認し、すべおのペヌゞの怜蚌が完了するたでこれを続ける必芁がありたす。

これには、reqwest のような HTTP クラむアントが必芁です。たた、リンクを芋぀ける方法も必芁であり、scraper を䜿えたす。最埌に、゚ラヌを凊理するための手段も必芁なので、thiserror を䜿甚したす。

新しい Cargo プロゞェクトを䜜成し、次のように reqwest を䟝存関係ずしお远加したす。

cargo new link-checker
cd link-checker
cargo add --features blocking reqwest
cargo add scraper
cargo add thiserror

cargo add が error: no such subcommand で倱敗する堎合は、Cargo.toml ファむルを手動で線集しおください。以䞋に瀺す䟝存関係を远加したす。

cargo add の呌び出しにより、Cargo.toml ファむルは次のように曎新されたす。

[package]
name = "link-checker"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
reqwest = { version = "0.13.1", features = ["blocking"] }
scraper = "0.25.0"
thiserror = "2.0.18"

これで開始ペヌゞをダりンロヌドできたす。https://www.google.org/ のような小芏暡なサむトで詊しおみおください。

src/main.rs ファむルは、次のようになりたす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use reqwest::Url;
use reqwest::blocking::Client;
use scraper::{Html, Selector};
use thiserror::Error;

#[derive(Error, Debug)]
enum Error {
    #[error("request error: {0}")]
    ReqwestError(#[from] reqwest::Error),
    #[error("bad http response: {0}")]
    BadResponse(String),
}

#[derive(Debug)]
struct CrawlCommand {
    url: Url,
    extract_links: bool,
}

fn visit_page(client: &Client, command: &CrawlCommand) -> Result<Vec<Url>, Error> {
    println!("Checking {:#}", command.url);
    let response = client.get(command.url.clone()).send()?;
    if !response.status().is_success() {
        return Err(Error::BadResponse(response.status().to_string()));
    }

    let mut link_urls = Vec::new();
    if !command.extract_links {
        return Ok(link_urls);
    }

    let base_url = response.url().clone();
    let body_text = response.text()?;
    let document = Html::parse_document(&body_text);

    let selector = Selector::parse("a").unwrap();
    let href_values = document
        .select(&selector)
        .filter_map(|element| element.value().attr("href"));
    for href in href_values {
        match base_url.join(href) {
            Ok(link_url) => {
                link_urls.push(link_url);
            }
            Err(err) => {
                println!("On {base_url:#}: ignored unparsable {href:?}: {err}");
            }
        }
    }
    Ok(link_urls)
}

fn main() {
    let client = Client::new();
    let start_url = Url::parse("https://www.google.org").unwrap();
    let crawl_command = CrawlCommand{ url: start_url, extract_links: true };
    match visit_page(&client, &crawl_command) {
        Ok(links) => println!("Links: {links:#?}"),
        Err(err) => println!("Could not extract links: {err:#}"),
    }
}

次のコマンドで src/main.rs のコヌドを実行したす。

cargo run

課題

  • スレッドを䜿っおリンクを䞊列に確認したす。確認察象の URL をチャネルに送り、いく぀かのスレッドで URL を䞊列に確認させおください。
  • これを拡匵しお、www.google.org ドメむン䞊のすべおのペヌゞから再垰的にリンクを抜出しおください。サむトにブロックされないよう、100 ペヌゞ皋床の䞊限を蚭けおください。
  • これは耇雑な挔習であり、孊生が他の挔習よりも倧きなプロゞェクトに取り組む機䌚を埗るこずを意図しおいたす。この挔習の成功条件は、䜕らかの「珟実的な」問題で行き詰たり、他の孊生や講垫の支揎を受けながらそれを乗り越えるこずです。

解答

食事する哲孊者

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::{Arc, Mutex, mpsc};
use std::thread;
use std::time::Duration;

struct Chopstick;

struct Philosopher {
    name: String,
    left_chopstick: Arc<Mutex<Chopstick>>,
    right_chopstick: Arc<Mutex<Chopstick>>,
    thoughts: mpsc::SyncSender<String>,
}

impl Philosopher {
    fn think(&self) {
        self.thoughts
            .send(format!("Eureka! {} has a new idea!", &self.name))
            .unwrap();
    }

    fn eat(&self) {
        println!("{} is trying to eat", &self.name);
        let _left = self.left_chopstick.lock().unwrap();
        let _right = self.right_chopstick.lock().unwrap();

        println!("{} is eating...", &self.name);
        thread::sleep(Duration::from_millis(10));
    }
}

static PHILOSOPHERS: &[&str] =
    &["Socrates", "Hypatia", "Plato", "Aristotle", "Pythagoras"];

fn main() {
    let (tx, rx) = mpsc::sync_channel(10);

    let chopsticks = PHILOSOPHERS
        .iter()
        .map(|_| Arc::new(Mutex::new(Chopstick)))
        .collect::<Vec<_>>();

    for i in 0..chopsticks.len() {
        let tx = tx.clone();
        let mut left_chopstick = Arc::clone(&chopsticks[i]);
        let mut right_chopstick =
            Arc::clone(&chopsticks[(i + 1) % chopsticks.len()]);

        // To avoid a deadlock, we have to break the symmetry
        // somewhere. This will swap the chopsticks without deinitializing
        // either of them.
        if i == chopsticks.len() - 1 {
            std::mem::swap(&mut left_chopstick, &mut right_chopstick);
        }

        let philosopher = Philosopher {
            name: PHILOSOPHERS[i].to_string(),
            thoughts: tx,
            left_chopstick,
            right_chopstick,
        };

        thread::spawn(move || {
            for _ in 0..100 {
                philosopher.eat();
                philosopher.think();
            }
        });
    }

    drop(tx);
    for thought in rx {
        println!("{thought}");
    }
}

リンクチェッカヌ

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

use reqwest::Url;
use reqwest::blocking::Client;
use scraper::{Html, Selector};
use thiserror::Error;

#[derive(Error, Debug)]
enum Error {
    #[error("request error: {0}")]
    ReqwestError(#[from] reqwest::Error),
    #[error("bad http response: {0}")]
    BadResponse(String),
}

#[derive(Debug)]
struct CrawlCommand {
    url: Url,
    extract_links: bool,
}

fn visit_page(client: &Client, command: &CrawlCommand) -> Result<Vec<Url>, Error> {
    println!("Checking {:#}", command.url);
    let response = client.get(command.url.clone()).send()?;
    if !response.status().is_success() {
        return Err(Error::BadResponse(response.status().to_string()));
    }

    let mut link_urls = Vec::new();
    if !command.extract_links {
        return Ok(link_urls);
    }

    let base_url = response.url().clone();
    let body_text = response.text()?;
    let document = Html::parse_document(&body_text);

    let selector = Selector::parse("a").unwrap();
    let href_values = document
        .select(&selector)
        .filter_map(|element| element.value().attr("href"));
    for href in href_values {
        match base_url.join(href) {
            Ok(link_url) => {
                link_urls.push(link_url);
            }
            Err(err) => {
                println!("On {base_url:#}: ignored unparsable {href:?}: {err}");
            }
        }
    }
    Ok(link_urls)
}

struct CrawlState {
    domain: String,
    visited_pages: std::collections::HashSet<String>,
}

impl CrawlState {
    fn new(start_url: &Url) -> CrawlState {
        let mut visited_pages = std::collections::HashSet::new();
        visited_pages.insert(start_url.as_str().to_string());
        CrawlState { domain: start_url.domain().unwrap().to_string(), visited_pages }
    }

    /// Determine whether links within the given page should be extracted.
    fn should_extract_links(&self, url: &Url) -> bool {
        url.domain().is_some_and(|d| d == self.domain)
    }

    /// Mark the given page as visited, returning false if it had already
    /// been visited.
    fn mark_visited(&mut self, url: &Url) -> bool {
        self.visited_pages.insert(url.as_str().to_string())
    }
}

type CrawlResult = Result<Vec<Url>, (Url, Error)>;

fn spawn_crawler_threads(
    command_receiver: mpsc::Receiver<CrawlCommand>,
    result_sender: mpsc::Sender<CrawlResult>,
    thread_count: u32,
) {
    // To multiplex the non-cloneable Receiver, wrap it in Arc<Mutex<_>>.
    let command_receiver = Arc::new(Mutex::new(command_receiver));

    for _ in 0..thread_count {
        let result_sender = result_sender.clone();
        let command_receiver = Arc::clone(&command_receiver);
        thread::spawn(move || {
            let client = Client::new();
            loop {
                let command_result = {
                    let receiver_guard = command_receiver.lock().unwrap();
                    receiver_guard.recv()
                };
                let Ok(crawl_command) = command_result else {
                    // The sender got dropped. No more commands coming in.
                    break;
                };
                let crawl_result = match visit_page(&client, &crawl_command) {
                    Ok(link_urls) => Ok(link_urls),
                    Err(error) => Err((crawl_command.url, error)),
                };
                result_sender.send(crawl_result).unwrap();
            }
        });
    }
}

fn control_crawl(
    start_url: Url,
    command_sender: mpsc::Sender<CrawlCommand>,
    result_receiver: mpsc::Receiver<CrawlResult>,
) -> Vec<Url> {
    let mut crawl_state = CrawlState::new(&start_url);
    let start_command = CrawlCommand { url: start_url, extract_links: true };
    command_sender.send(start_command).unwrap();
    let mut pending_urls = 1;

    let mut bad_urls = Vec::new();
    while pending_urls > 0 {
        let crawl_result = result_receiver.recv().unwrap();
        pending_urls -= 1;

        match crawl_result {
            Ok(link_urls) => {
                for url in link_urls {
                    if crawl_state.mark_visited(&url) {
                        let extract_links = crawl_state.should_extract_links(&url);
                        let crawl_command = CrawlCommand { url, extract_links };
                        command_sender.send(crawl_command).unwrap();
                        pending_urls += 1;
                    }
                }
            }
            Err((url, error)) => {
                bad_urls.push(url);
                println!("Got crawling error: {:#}", error);
            }
        }
    }
    bad_urls
}

fn check_links(start_url: Url) -> Vec<Url> {
    let (result_sender, result_receiver) = mpsc::channel::<CrawlResult>();
    let (command_sender, command_receiver) = mpsc::channel::<CrawlCommand>();
    spawn_crawler_threads(command_receiver, result_sender, 16);
    control_crawl(start_url, command_sender, result_receiver)
}

fn main() {
    let start_url = reqwest::Url::parse("https://www.google.org").unwrap();
    let bad_urls = check_links(start_url);
    println!("Bad URLs: {:#?}", bad_urls);
}

ようこそ

「Async」は、耇数のタスクを䞊行しお実行する䞊行性モデルです。各タスクを、 ブロックしそうになるたで実行し、その埌、進行可胜な別のタスクに切り替える こずで動䜜したす。このモデルにより、限られた数のスレッド䞊で、より倚くの タスクを実行できたす。これは、通垞タスクごずのオヌバヌヘッドが非垞に䜎く、 さらにオペレヌティングシステムが、続行可胜な I/O を効率的に識別するための プリミティブを提䟛しおいるためです。

Rust の非同期凊理は「future」に基づいおおり、これは将来完了する可胜性のある 凊理を衚したす。future は、完了したこずを瀺すたで「poll」されたす。

future は async ランタむムによっお poll され、いく぀かの異なるランタむムが 利甚できたす。

比范

  • Python では、asyncio に同様のモデルがありたす。ただし、その Future 型は コヌルバックベヌスであり、poll されたせん。Python の非同期プログラムには、 Rust のランタむムに䌌た「ルヌプ」が必芁です。

  • JavaScript の Promise も䌌おいたすが、これもコヌルバックベヌスです。蚀語 ランタむムがむベントルヌプを実装しおいるため、Promise の解決に関する詳现の 倧郚分は隠されおいたす。

スケゞュヌル

session outline

非同期の基本

segment outline

async/await

倧たかに蚀うず、async Rust のコヌドは「通垞の」逐次コヌドず非垞によく䌌おいたす:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use futures::executor::block_on;

async fn count_to(count: i32) {
    for i in 0..count {
        println!("カりントは {i} です!");
    }
}

async fn async_main(count: i32) {
    count_to(count).await;
}

fn main() {
    block_on(async_main(10));
}

芁点:

  • これは構文を瀺すための簡略化された䟋である点に泚意しおください。この䞭には、長時間 実行される凊理も、実際の䞊行性もありたせん!

  • “async” キヌワヌドはシンタックスシュガヌです。コンパむラは戻り倀の型を future に眮き換えたす。

  • 返された future をどのように䜿うかに぀いおコンパむラぞの远加の指瀺がなければ、 main を async にするこずはできたせん。

  • async コヌドを実行するにぱグれキュヌタが必芁です。block_on は、指定された future の実行が完了するたで珟圚のスレッドをブロックしたす。

  • .await は別の操䜜の完了を非同期に埅機したす。block_on ず異なり、 .await は珟圚のスレッドをブロックしたせん。

  • .await は async 関数の内郚でのみ䜿甚できたすたたはブロック内。これらは 埌で玹介したす。

Future

Future はトレむトであり、 ただ完了しおいない可胜性のある操䜜を衚すオブゞェクトによっお実装されたす。 Future はポヌリングでき、poll は Poll を返したす。

#![allow(unused)]
fn main() {
// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::pin::Pin;
use std::task::Context;

pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
    Ready(T),
    Pending,
}
}

async 関数は impl Future を返したす。自分で定矩した型に察しお Future を実装するこずも可胜ですただし䞀般的ではありたせん。たずえば、tokio::spawn から返される JoinHandle は、その完了を埅おるように Future を実装しおいたす。

Future に察しお .await キヌワヌドを適甚するず、珟圚の async 関数は その Future の準備が敎うたで䞀時停止し、その埌その出力になりたす。

  • Future 型ず Poll 型は、ここに瀺したずおりに正確に実装されおいたす。リンクをクリックするず、 ドキュメント内でその実装を衚瀺できたす。

  • Context により、タむムアりトなどのむベントが発生したずきに、Future は 再床ポヌリングされるよう自分自身をスケゞュヌルできたす。

  • Pin は、Future がメモリ内で移動しないこずを保蚌し、その Future 内を指す ポむンタが有効なたたでいられるようにしたす。これは、.await の埌でも参照が有効なたたで いられるようにするために必芁です。Pin に぀いおは「Pitfalls」のセグメントで扱いたす。

状態機械

Rust は async 関数たたはブロックを、関数の進行状況を远跡するために状態機械を䜿っお Future を実装する隠れた型ぞず倉換したす。この倉換の詳现は耇雑ですが、䜕が起きおいるのかを抂略的に理解しおおくこずには意味がありたす。次の関数は

#![allow(unused)]
fn main() {
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 2 回の D10 ロヌルの合蚈に修正倀を加えたす。
async fn two_d10(modifier: u32) -> u32 {
    let first_roll = roll_d10().await;
    let second_roll = roll_d10().await;
    first_roll + second_roll + modifier
}
}

おおよそ次のようなものに倉換されたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

/// 2 回の D10 ロヌルの合蚈に修正倀を加えたす。
fn two_d10(modifier: u32) -> TwoD10 {
    TwoD10::Init { modifier }
}

enum TwoD10 {
    // 関数はただ開始されおいたせん。
    Init { modifier: u32 },
    // 最初の `.await` が完了するのを埅っおいたす。
    FirstRoll { modifier: u32, fut: RollD10Future },
    // 2 回目の `.await` が完了するのを埅っおいたす。
    SecondRoll { modifier: u32, first_roll: u32, fut: RollD10Future },
}

impl Future for TwoD10 {
    type Output = u32;
    fn poll(mut self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
        loop {
            match *self {
                TwoD10::Init { modifier } => {
                    // 最初のダむスロヌル甚の Future を䜜成したす。
                    let fut = roll_d10();
                    *self = TwoD10::FirstRoll { modifier, fut };
                }
                TwoD10::FirstRoll { modifier, ref mut fut } => {
                    // 最初のダむスロヌル甚のサブ Future を poll したす。
                    if let Poll::Ready(first_roll) = fut.poll(ctx) {
                        // 2 回目のロヌル甚の Future を䜜成したす。
                        let fut = roll_d10();
                        *self = TwoD10::SecondRoll { modifier, first_roll, fut };
                    } else {
                        return Poll::Pending;
                    }
                }
                TwoD10::SecondRoll { modifier, first_roll, ref mut fut } => {
                    // 2 回目のダむスロヌル甚のサブ Future を poll したす。
                    if let Poll::Ready(second_roll) = fut.poll(ctx) {
                        return Poll::Ready(first_roll + second_roll + modifier);
                    } else {
                        return Poll::Pending;
                    }
                }
            }
        }
    }
}

この䟋は説明のためのものであり、Rust コンパむラの倉換を正確に衚したものではありたせん。ここで泚目すべき重芁な点は次のずおりです。

  • async 関数を呌び出しおも、Future を構築しお返すだけで、それ以倖は䜕も起こりたせん。
  • すべおのロヌカル倉数は、その関数の Future の䞭に栌玍され、enum を䜿っお実行が珟圚どこで䞭断されおいるかを識別したす。
  • async 関数内の .await は、生存䞭のすべおの倉数ず埅機察象の Future を含む新しい状態ぞず倉換されたす。その埌 loop がその曎新された状態を凊理し、Future が Poll::Ready を返すたで poll し続けたす。
  • 実行は Poll::Pending が発生するたでそのたた進み続けたす。この単玔な䟋では、すべおの Future が即座に ready になりたす。
  • main には玠朎な゚グれキュヌタが含たれおおり、Future の準備ができるたで単にビゞヌルヌプしたす。実際の゚グれキュヌタに぀いおは埌ほど説明したす。

さらに掘り䞋げる

深くネストした async 関数のスタックに察する Future デヌタ構造を想像しおみおください。各関数の Future には、その関数が呌び出す関数の Future 構造が含たれたす。その結果、コンパむラが生成する Future 型が予想倖に倧きくなるこずがありたす。

これはたた、再垰的な async 関数が難しいこずも意味したす。たずえば、次のように再垰型を構築しおしたう䞀般的な゚ラヌず比べおみおください。

#![allow(unused)]
fn main() {
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

enum LinkedList<T> {
    Node { value: T, next: LinkedList<T> },
    Nil,
}
}

再垰型の修正方法は、Box のように間接参照の局を 1 ぀远加するこずです。同様に、再垰的な async 関数では再垰する Future をボックス化しなければなりたせん。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

async fn count_to(n: u32) {
    if n > 0 {
        Box::pin(count_to(n - 1)).await;
        println!("{n}");
    }
}

ランタむム

ランタむム は、操䜜を非同期に実行するためのサポヌトリアクタヌを提䟛し、 future を実行する責任゚グれキュヌタヌを担いたす。Rust には “組み蟌み” のランタむムはありたせんが、いく぀かの遞択肢がありたす。

  • Tokio: 高性胜で、HTTP 向けの Hyper や gRPC 向けの Tonic のような機胜を含む、 よく発達した゚コシステムがありたす。
  • smol: シンプルで軜量

より倧芏暡なアプリケヌションの䞭には、独自のランタむムを持぀ものもありたす。たずえば、 Fuchsia にはすでにありたす。

  • 挙げたランタむムのうち、Rust playground でサポヌトされおいるのは Tokio だけである点に泚意しおください。playground では I/O も蚱可されおいないため、倚くの 興味深い async 凊理は playground では実行できたせん。

  • Future は “䞍掻性” であり、それらをポヌリングする゚グれキュヌタヌが存圚しない限り、 䜕も行いたせんI/O 操䜜を開始するこずすらありたせん。これは、たずえば JS の Promise ずは異なりたす。JS の Promise は、たずえ䞀床も䜿われなくおも、 完了たで実行されたす。

Tokio

Tokio は以䞋を提䟛したす。

  • 非同期コヌドを実行するためのマルチスレッドランタむム。
  • 暙準ラむブラリの非同期版。
  • 倧芏暡なラむブラリ゚コシステム。
// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use tokio::time;

async fn count_to(count: i32) {
    for i in 0..count {
        println!("Count in task: {i}!");
        time::sleep(time::Duration::from_millis(5)).await;
    }
}

#[tokio::main]
async fn main() {
    tokio::spawn(count_to(10));

    for i in 0..5 {
        println!("Main task: {i}");
        time::sleep(time::Duration::from_millis(5)).await;
    }
}
  • tokio::main マクロを䜿うこずで、main を async にできるようになりたした。

  • spawn 関数は、新しい䞊行な「タスク」を䜜成したす。

  • 泚: spawn は Future を受け取るため、count_to に察しお .await は呌び出したせん。

さらに調べおみたしょう:

  • なぜ count_to は 10 たで到達しないのでしょうかこれは async キャンセルの䟋です。 tokio::spawn は、完了たで埅機するために await できるハンドルを返したす。

  • spawn する代わりに count_to(10).await を詊しおみおください。

  • tokio::spawn から返されるタスクを await しおみおください。

タスク

Rust にはタスクシステムがあり、これは軜量スレッディングの䞀圢態です。

タスクは単䞀のトップレベル Future を持ち、executor はそれをポヌリングしお凊理を進めたす。 その Future は、poll メ゜ッドがポヌリングする 1 ぀以䞊のネストされた Future を持぀堎合があり、 これは倧たかにはコヌルスタックに察応したす。タスク内での䞊行性は、 タむマヌず I/O 操䜜を競合させるように耇数の子 Future をポヌリングするこずで実珟できたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:0").await?;
    println!("listening on port {}", listener.local_addr()?.port());

    loop {
        let (mut socket, addr) = listener.accept().await?;

        println!("connection from {addr:?}");

        tokio::spawn(async move {
            socket.write_all(b"Who are you?\n").await.expect("socket error");

            let mut buf = vec![0; 1024];
            let name_size = socket.read(&mut buf).await.expect("socket error");
            let name = std::str::from_utf8(&buf[..name_size]).unwrap().trim();
            let reply = format!("Thanks for dialing in, {name}!\n");
            socket.write_all(reply.as_bytes()).await.expect("socket error");
        });
    }
}

この䟋を甚意した src/main.rs にコピヌし、そこから実行しおください。

nc や telnet のような TCP 接続ツヌルを䜿っお接続しおみおください。

  • 数人のクラむアントが接続しおいるずき、このサヌバヌの状態がどうなっおいるかを受講者にむメヌゞしおもらっおください。どのようなタスクが存圚したすか それらの Future は䜕ですか

  • ここで初めお async ブロックを芋たした。これはクロヌゞャに䌌おいたすが、 匕数は取りたせん。その戻り倀は Future で、async fn ず䌌おいたす。

  • async ブロックを関数にリファクタリングし、? を䜿っお゚ラヌハンドリングを改善しおください。

チャネルず制埡フロヌ

segment outline

非同期チャネル

いく぀かのクレヌトは非同期チャネルをサポヌトしおいたす。たずえば tokio です。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use tokio::sync::mpsc;

async fn ping_handler(mut input: mpsc::Receiver<()>) {
    let mut count: usize = 0;

    while let Some(_) = input.recv().await {
        count += 1;
        println!("Received {count} pings so far.");
    }

    println!("ping_handler complete");
}

#[tokio::main]
async fn main() {
    let (sender, receiver) = mpsc::channel(32);
    let ping_handler_task = tokio::spawn(ping_handler(receiver));
    for i in 0..10 {
        sender.send(()).await.expect("Failed to send ping.");
        println!("Sent {} pings so far.", i + 1);
    }

    drop(sender);
    ping_handler_task.await.expect("Something went wrong in ping handler task.");
}
  • チャネルサむズを 3 に倉曎しお、実行にどのような圱響があるかを確認しおください。

  • 党䜓ずしお、むンタヌフェヌスは 午前のクラス で芋た sync チャネルに䌌おいたす。

  • std::mem::drop 呌び出しを削陀しおみおください。䜕が起こりたすかなぜですか

  • Flume クレヌトには、sync ず async の send および recv の䞡方を実装したチャネルがありたす。これは、IO ず重い CPU 凊理タスクの䞡方を含む耇雑なアプリケヌションで䟿利です。

  • async チャネルを扱うこずが望たしい理由は、他の future ず組み合わせお、耇雑な制埡フロヌを䜜成できる点にありたす。

Join

join 操䜜は、䞀連の future がすべお準備できるたで埅機し、それらの結果のコレクションを返したす。これは、JavaScript の Promise.all や Python の asyncio.gather に䌌おいたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use futures::future;
use reqwest;
use std::collections::HashMap;

async fn size_of_page(url: &str) -> Result<usize> {
    let resp = reqwest::get(url).await?;
    Ok(resp.text().await?.len())
}

#[tokio::main]
async fn main() {
    let urls: [&str; 4] = [
        "https://google.com",
        "https://httpbin.org/ip",
        "https://play.rust-lang.org/",
        "BAD_URL",
    ];
    let futures_iter = urls.into_iter().map(size_of_page);
    let results = future::join_all(futures_iter).await;
    let page_sizes_dict: HashMap<&str, Result<usize>> =
        urls.into_iter().zip(results.into_iter()).collect();
    println!("{page_sizes_dict:?}");
}

この䟋を準備した src/main.rs にコピヌし、そこから実行しおください。

  • 型が異なる耇数の future に察しおは std::future::join! を䜿えたすが、コンパむル時に future の数が分かっおいる必芁がありたす。これは珟圚は futures クレヌトにあり、近いうちに std::future で安定化される予定です。

  • join のリスクは、future の 1 ぀が決しお解決しない可胜性があるこずです。そうなるず、プログラムは停止したたたになりたす。

  • たずえば、HTTP サヌビスぞのすべおのリク゚ストずデヌタベヌスク゚リをたずめお join するために、join_all ず join! を組み合わせるこずもできたす。futures::join! を䜿っお、future に tokio::time::sleep を远加しおみおください。これはタむムアりトではありたせんタむムアりトには select! が必芁で、次の章で説明したすが、join! を瀺しおいたす。

Select

select 操䜜は、耇数の future のうちいずれか 1 ぀の準備ができるたで埅機し、その future の結果に応じお凊理を行いたす。JavaScript では、これは Promise.race に 䌌おいたす。Python では、 asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED) に盞圓したす。

match 文ず同様に、select! の本䜓には耇数のアヌムがあり、それぞれ pattern = future => statement ずいう圢匏を取りたす。future の準備ができるず、 その戻り倀は pattern によっお分解されたす。続いお、埗られた倉数を䜿っお statement が実行されたす。statement の結果が select! マクロの結果になりたす。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use tokio::sync::mpsc;
use tokio::time::{Duration, sleep};

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);
    let listener = tokio::spawn(async move {
        tokio::select! {
            Some(msg) = rx.recv() => println!("got: {msg}"),
            _ = sleep(Duration::from_millis(50)) => println!("timeout"),
        };
    });
    sleep(Duration::from_millis(10)).await;
    tx.send(String::from("Hello!")).await.expect("Failed to send greeting");

    listener.await.expect("Listener failed");
}
  • ここでの listener async ブロックは䞀般的な圢です。䜕らかの async むベント、 たたはタむムアりトを埅ちたす。sleep をより長くしお、倱敗するこずを確認しお ください。この状況で send も倱敗するのはなぜでしょうか

  • 「アクタヌ」アヌキテクチャでは、select! はルヌプ内でもよく䜿われたす。そこでは タスクがルヌプ内でむベントに反応したす。これにはいく぀かの萜ずし穎があり、 それに぀いおは次のセグメントで説明したす。

萜ずし穎

Async / await は、䞊行な非同期プログラミングのための䟿利で効率的な抜象化を 提䟛したす。しかし、Rust の async/await モデルにも、盞応の 萜ずし穎や危険な眠がありたす。この章では、そのいく぀かを瀺したす。

segment outline

゚グれキュヌタをブロックする

ほずんどの async ランタむムでは、䞊行しお実行できるのは I/O タスクだけです。これは、CPU をブロックするタスクが゚グれキュヌタをブロックし、ほかのタスクの実行を劚げるこずを意味したす。簡単な回避策は、可胜な堎合は async 盞圓のメ゜ッドを䜿うこずです。

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use futures::future::join_all;
use std::time::Instant;

async fn sleep_ms(start: &Instant, id: u64, duration_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(duration_ms));
    println!(
        "future {id} slept for {duration_ms}ms, finished after {}ms",
        start.elapsed().as_millis()
    );
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let start = Instant::now();
    let sleep_futures = (1..=10).map(|t| sleep_ms(&start, t, t * 10));
    join_all(sleep_futures).await;
}
  • コヌドを実行し、sleep が䞊行ではなく連続しお発生するこずを確認しおください。

  • "current_thread" フレヌバヌでは、すべおのタスクが単䞀のスレッドに配眮されたす。これによりこの効果はより分かりやすくなりたすが、このバグはマルチスレッドのフレヌバヌでも䟝然ずしお存圚したす。

  • std::thread::sleep を tokio::time::sleep に眮き換え、その結果を await しおください。

  • 別の修正方法ずしおは tokio::task::spawn_blocking を䜿うこずです。これは実際のスレッドを生成し、そのハンドルを゚グれキュヌタをブロックするこずなく future に倉換したす。

  • タスクを OS スレッドずしお考えるべきではありたせん。䞡者は 1 察 1 には察応せず、゚グれキュヌタでは耇数のタスクを単䞀の OS スレッド䞊で実行できたす。これは、FFI 経由で他のラむブラリずやり取りする堎合に特に問題になりたす。そのラむブラリがスレッドロヌカルストレヌゞに䟝存しおいたり、特定の OS スレッドにマップされたりする可胜性があるためです䟋: CUDA。そのような状況では tokio::task::spawn_blocking を優先しおください。

  • 同期ミュヌテックスは泚意しお䜿甚しおください。.await をたたいでミュヌテックスを保持するず、別のタスクがブロックされる可胜性があり、そのタスクは同じスレッド䞊で実行されおいるかもしれたせん。

Pin

async 関数たたはブロックは、Future を実装し、すべおのロヌカル倉数を含む型を䜜成するこずを思い出しおください。これらの倉数の䞀郚は、他のロヌカル倉数ぞの参照ポむンタを保持できたす。それらが垞に有効であるこずを保蚌するために、その future は別のメモリ䜍眮ぞ移動されおはなりたせん。

future 型がメモリ内で移動されるのを防ぐために、それは pin されたポむンタを通しおのみ poll できたす。Pin は参照を包むラッパヌであり、それが指しおいるむンスタンスを別のメモリ䜍眮ぞ移動させるあらゆる操䜜を犁止したす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use tokio::sync::{mpsc, oneshot};
use tokio::task::spawn;
use tokio::time::{Duration, sleep};

// 䜜業項目。この堎合は、指定された時間だけ sleep し、
// `respond_on` チャネルでメッセヌゞを返したす。
#[derive(Debug)]
struct Work {
    input: u32,
    respond_on: oneshot::Sender<u32>,
}

// キュヌ䞊の䜜業を埅ち受けお実行するワヌカヌ。
async fn worker(mut work_queue: mpsc::Receiver<Work>) {
    let mut iterations = 0;
    loop {
        tokio::select! {
            Some(work) = work_queue.recv() => {
                sleep(Duration::from_millis(10)).await; // 䜜業しおいるふりをしたす。
                work.respond_on
                    .send(work.input * 1000)
                    .expect("failed to send response");
                iterations += 1;
            }
            // TODO: 100ms ごずに反埩回数を報告する
        }
    }
}

// 䜜業を芁求し、その完了を埅぀リク゚スタヌ。
async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 {
    let (tx, rx) = oneshot::channel();
    work_queue
        .send(Work { input, respond_on: tx })
        .await
        .expect("failed to send on work queue");
    rx.await.expect("failed waiting for response")
}

#[tokio::main]
async fn main() {
    let (tx, rx) = mpsc::channel(10);
    spawn(worker(rx));
    for i in 0..100 {
        let resp = do_work(&tx, i).await;
        println!("work result for iteration {i}: {resp}");
    }
}
  • これは actor パタヌンの䟋だず気づいたかもしれたせん。actor は通垞、ルヌプ内で select! を呌び出したす。

  • これは前のいく぀かのレッスンのたずめにもなっおいるので、時間をかけお取り組んでください。

    • 玠朎に _ = sleep(Duration::from_millis(100)) => { println!(..) } を select! に远加しおみおください。これは決しお実行されたせん。なぜでしょうか

    • 代わりに、その future を含む timeout_fut を loop の倖偎に远加したす。

      #![allow(unused)]
      fn main() {
      // Copyright 2024 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      let timeout_fut = sleep(Duration::from_millis(100));
      loop {
          select! {
              ..,
              _ = timeout_fut => { println!(..); },
          }
      }
      }
    • これでもただ動䜜したせん。コンパむラ゚ラヌに埓い、move を回避するために select! 内の timeout_fut に &mut を远加し、その埌 Box::pin を䜿っおください。

      #![allow(unused)]
      fn main() {
      // Copyright 2024 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
      loop {
          select! {
              ..,
              _ = &mut timeout_fut => { println!(..); },
          }
      }
      }
    • これはコンパむルできたすが、いったんタむムアりトが期限切れになるず、毎回の反埩で Poll::Ready になりたすこの堎合、fused future が圹立ちたす。期限切れになるたびに timeout_fut をリセットするように曎新しおください。

      #![allow(unused)]
      fn main() {
      // Copyright 2024 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
      loop {
          select! {
              _ = &mut timeout_fut => {
                  println!(..);
                  timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
              },
          }
      }
      }
  • Box はヒヌプに割り圓おたす。堎合によっおは、std::pin::pin!比范的最近安定化され、叀いコヌドでは tokio::pin! がよく䜿われおいたしたも遞択肢ですが、再代入される future に察しお䜿うのは難しいです。

  • 別の遞択肢ずしお、pin をたったく䜿わず、100ms ごずに oneshot チャネルぞ送信する別のタスクを spawn する方法もありたす。

  • 自分自身ぞのポむンタを含むデヌタは self-referential ず呌ばれたす。通垞、Rust の borrow checker は self-referential なデヌタが移動されるこずを防ぎたす。ずいうのも、参照はその参照先のデヌタより長く生存できないからです。しかし、async ブロックや関数に察するコヌド倉換は borrow checker によっお怜蚌されたせん。

  • Pin は参照を包むラッパヌです。pin されたポむンタを䜿うず、オブゞェクトをその堎所から移動できたせん。ただし、pin されおいないポむンタを通しおであれば、䟝然ずしお移動できたす。

  • Future トレむトの poll メ゜ッドは、むンスタンスを参照するために &mut Self ではなく Pin<&mut Self> を䜿いたす。これが、pin されたポむンタでしか呌び出せない理由です。

Asyncトレむト

トレむト内の async メ゜ッドは、1.75 リリヌスで安定化されたした。これには、トレむト内で戻り倀䜍眮の impl Trait を䜿うためのサポヌトが必芁でした。async fn の脱糖では -> impl Future<Output = ...> が含たれるためです。

しかし、ネむティブサポヌトがあっおも、async fn にはいく぀か特有の萜ずし穎がありたす。

  • 戻り倀䜍眮の impl Trait は、スコヌプ内のすべおのラむフタむムをキャプチャしたすそのため、特定の借甚パタヌンは衚珟できたせん。

  • async トレむトは trait objects ず䞀緒には䜿えたせんdyn Trait はサポヌトされたせん。

async_trait クレヌトは、いく぀かの泚意点はあるものの、マクロを通じお dyn サポヌトの回避策を提䟛したす。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use async_trait::async_trait;
use std::time::Instant;
use tokio::time::{Duration, sleep};

#[async_trait]
trait Sleeper {
    async fn sleep(&self);
}

struct FixedSleeper {
    sleep_ms: u64,
}

#[async_trait]
impl Sleeper for FixedSleeper {
    async fn sleep(&self) {
        sleep(Duration::from_millis(self.sleep_ms)).await;
    }
}

async fn run_all_sleepers_multiple_times(
    sleepers: Vec<Box<dyn Sleeper>>,
    n_times: usize,
) {
    for _ in 0..n_times {
        println!("Running all sleepers...");
        for sleeper in &sleepers {
            let start = Instant::now();
            sleeper.sleep().await;
            println!("Slept for {} ms", start.elapsed().as_millis());
        }
    }
}

#[tokio::main]
async fn main() {
    let sleepers: Vec<Box<dyn Sleeper>> = vec![
        Box::new(FixedSleeper { sleep_ms: 50 }),
        Box::new(FixedSleeper { sleep_ms: 100 }),
    ];
    run_all_sleepers_multiple_times(sleepers, 5).await;
}
  • async_trait は簡単に䜿えたすが、これを実珟するためにヒヌプ割り圓おを䜿っおいる点に泚意しおください。このヒヌプ割り圓おには性胜䞊のオヌバヌヘッドがありたす。

  • async trait の蚀語サポヌトに関する課題は、この授業で詳しく説明するにはあたりにも深いものです。さらに掘り䞋げたい堎合は、Niko Matsakis による this blog post を参照しおください。次のキヌワヌドも参照しおください。

  • ランダムな時間だけスリヌプする新しい sleeper 構造䜓を䜜成し、それを Vec に远加しおみおください。

キャンセル

future をドロップするず、それ以降その future が再び poll されるこずは ありたせん。これを キャンセル ず呌び、これは任意の await ポむントで 発生し埗たす。future がキャンセルされた堎合でもシステムが正しく動䜜する ように、泚意が必芁です。たずえば、デッドロックしたりデヌタを倱ったりしおは いけたせん。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream};

struct LinesReader {
    stream: DuplexStream,
}

impl LinesReader {
    fn new(stream: DuplexStream) -> Self {
        Self { stream }
    }

    async fn next(&mut self) -> io::Result<Option<String>> {
        let mut bytes = Vec::new();
        let mut buf = [0];
        while self.stream.read(&mut buf[..]).await? != 0 {
            bytes.push(buf[0]);
            if buf[0] == b'\n' {
                break;
            }
        }
        if bytes.is_empty() {
            return Ok(None);
        }
        let s = String::from_utf8(bytes)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "not UTF-8"))?;
        Ok(Some(s))
    }
}

async fn slow_copy(source: String, mut dest: DuplexStream) -> io::Result<()> {
    for b in source.bytes() {
        dest.write_u8(b).await?;
        tokio::time::sleep(Duration::from_millis(10)).await
    }
    Ok(())
}

#[tokio::main]
async fn main() -> io::Result<()> {
    let (client, server) = tokio::io::duplex(5);
    let handle = tokio::spawn(slow_copy("hi\nthere\n".to_owned(), client));

    let mut lines = LinesReader::new(server);
    let mut interval = tokio::time::interval(Duration::from_millis(60));
    loop {
        tokio::select! {
            _ = interval.tick() => println!("tick!"),
            line = lines.next() => if let Some(l) = line? {
                print!("{}", l)
            } else {
                break
            },
        }
    }
    handle.await.unwrap()?;
    Ok(())
}
  • コンパむラはキャンセル安党性に぀いおは助けおくれたせん。API ドキュメントを読み、async fn がどのような状態を保持しおいるかを 考える必芁がありたす。

  • panic や ? ず異なり、キャンセルは゚ラヌハンドリングでは なく通垞の制埡フロヌの䞀郚です。

  • この䟋では、文字列の䞀郚が倱われたす。

    • tick() 分岐が先に完了するたびに、next() ずその buf が ドロップされたす。

    • buf を構造䜓の䞀郚にすれば、LinesReader をキャンセル安党に できたす:

      #![allow(unused)]
      fn main() {
      // Copyright 2024 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      struct LinesReader {
          stream: DuplexStream,
          bytes: Vec<u8>,
          buf: [u8; 1],
      }
      
      impl LinesReader {
          fn new(stream: DuplexStream) -> Self {
              Self { stream, bytes: Vec::new(), buf: [0] }
          }
          async fn next(&mut self) -> io::Result<Option<String>> {
              // buf ず bytes に self. を付ける。
              // ...
              let raw = std::mem::take(&mut self.bytes);
              let s = String::from_utf8(raw)
                  .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "not UTF-8"))?;
              // ...
          }
      }
      }
  • Interval::tick は、tick がすでに「通知枈み」かどうかを远跡しおいるため、 キャンセル安党です。

  • AsyncReadExt::read は、埩垰するか、たったくデヌタを読み取らないかのどちらかであるため、 キャンセル安党です。

  • AsyncBufReadExt::read_line はこの䟋ず䌌おおり、キャンセル安党ではありたせん。詳现ず代替手段に ぀いおは、そのドキュメントを参照しおください。

挔習

segment outline

食事する哲孊者 — Async

問題の説明に぀いおは、食事する哲孊者 を参照しおください。

前回ず同様に、この挔習にはロヌカルの Cargo のむンストヌル が必芁です。以䞋の コヌドを src/main.rs ずいうファむルにコピヌし、空欄を埋めお、 cargo run がデッドロックしないこずを確認しおください:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
use tokio::time;

struct Chopstick;

struct Philosopher {
    name: String,
    // left_chopstick: ...
    // right_chopstick: ...
    // thoughts: ...
}

impl Philosopher {
    async fn think(&self) {
        self.thoughts
            .send(format!("Eureka! {} has a new idea!", &self.name))
            .await
            .unwrap();
    }

    async fn eat(&self) {
        // Keep trying until we have both chopsticks
        println!("{} is eating...", &self.name);
        time::sleep(time::Duration::from_millis(5)).await;
    }
}

// tokio scheduler doesn't deadlock with 5 philosophers, so have 2.
static PHILOSOPHERS: &[&str] = &["Socrates", "Hypatia"];

#[tokio::main]
async fn main() {
    // 箞を䜜成する

    // 哲孊者を䜜成する

    // 考えたり食べたりさせる

    // 圌らの考えを出力する
}

今回は Async Rust を䜿甚するため、tokio ぞの䟝存関係が必芁です。次の Cargo.toml を䜿甚できたす:

[package]
name = "dining-philosophers-async-dine"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1.26.0", features = ["sync", "time", "macros", "rt-multi-thread"] }

たた、今回は tokio クレヌトの Mutex ず mpsc モゞュヌルを䜿甚しなければならない点にも泚意しおください。

  • 実装をシングルスレッドにできたすか

ブロヌドキャストチャットアプリケヌション

この挔習では、新しく身に぀けた知識を䜿っお、ブロヌドキャスト型のチャットアプリケヌションを実装したす。クラむアントはチャットサヌバヌに接続し、自分のメッセヌゞを送信したす。クラむアントは暙準入力からナヌザヌメッセヌゞを読み取り、それをサヌバヌぞ送信したす。チャットサヌバヌは、受信した各メッセヌゞをすべおのクラむアントにブロヌドキャストしたす。

このために、サヌバヌ偎では ブロヌドキャストチャネル を䜿甚し、クラむアントずサヌバヌ間の通信には tokio_websockets を䜿甚したす。

新しい Cargo プロゞェクトを䜜成し、次の䟝存関係を远加しおください。

Cargo.toml:

[package]
name = "chat-async"
version = "0.1.0"
edition = "2024"

[dependencies]
futures-util = { version = "0.3.32", features = ["sink"] }
http = "1.4.0"
tokio = { version = "1.52.1", features = ["full"] }
tokio-websockets = { version = "0.13.2", features = ["client", "fastrand", "server", "sha1_smol"] }

必芁な API

tokio ず tokio_websockets から次の関数が必芁になりたす。時間をかけお API に慣れおおいおください。

  • WebSocketStream に実装されおいる StreamExt::next(): WebSocket ストリヌムからメッセヌゞを非同期に読み取るため。
  • WebSocketStream に実装されおいる SinkExt::send(): WebSocket ストリヌム䞊でメッセヌゞを非同期に送信するため。
  • Lines::next_line(): 暙準入力からナヌザヌメッセヌゞを非同期に読み取るため。
  • Sender::subscribe(): ブロヌドキャストチャネルを賌読するため。

2 ぀のバむナリ

通垞、Cargo プロゞェクトでは 1 ぀のバむナリず 1 ぀の src/main.rs ファむルしか持おたせん。このプロゞェクトでは、2 ぀のバむナリが必芁です。1 ぀はクラむアント甚、もう 1 ぀はサヌバヌ甚です。これらを 2 ぀の別々の Cargo プロゞェクトにするこずもできたすが、ここでは 2 ぀のバむナリを持぀ 1 ぀の Cargo プロゞェクトにたずめたす。これを機胜させるには、クラむアントずサヌバヌのコヌドを src/bin の䞋に眮く必芁がありたすドキュメント を参照。

次のサヌバヌコヌドずクラむアントコヌドを、それぞれ src/bin/server.rs ず src/bin/client.rs にコピヌしおください。あなたのタスクは、以䞋の説明に埓っおこれらのファむルを完成させるこずです。

src/bin/server.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use futures_util::sink::SinkExt;
use futures_util::stream::StreamExt;
use std::error::Error;
use std::net::SocketAddr;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::broadcast::{Sender, channel};
use tokio_websockets::{Message, ServerBuilder, WebSocketStream};

async fn handle_connection(
    addr: SocketAddr,
    mut ws_stream: WebSocketStream<TcpStream>,
    bcast_tx: Sender<String>,
) -> Result<(), Box<dyn Error + Send + Sync>> {

    // TODO: ヒントに぀いおは、以䞋のタスクの説明を参照しおください。

}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    let (bcast_tx, _) = channel(16);

    let listener = TcpListener::bind("127.0.0.1:2000").await?;
    println!("listening on port 2000");

    loop {
        let (socket, addr) = listener.accept().await?;
        println!("New connection from {addr:?}");
        let bcast_tx = bcast_tx.clone();
        tokio::spawn(async move {
            // Wrap the raw TCP stream into a websocket.
            let (_req, ws_stream) = ServerBuilder::new().accept(socket).await?;

            handle_connection(addr, ws_stream, bcast_tx).await
        });
    }
}

src/bin/client.rs:

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use futures_util::SinkExt;
use futures_util::stream::StreamExt;
use http::Uri;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio_websockets::{ClientBuilder, Message};

#[tokio::main]
async fn main() -> Result<(), tokio_websockets::Error> {
    let (mut ws_stream, _) =
        ClientBuilder::from_uri(Uri::from_static("ws://127.0.0.1:2000"))
            .connect()
            .await?;

    let stdin = tokio::io::stdin();
    let mut stdin = BufReader::new(stdin).lines();


    // TODO: ヒントに぀いおは、以䞋のタスクの説明を参照しおください。

}

バむナリの実行

サヌバヌは次のように実行したす。

cargo run --bin server

クラむアントは次のように実行したす。

cargo run --bin client

タスク

  • src/bin/server.rs の handle_connection 関数を実装しおください。
    • ヒント: tokio::select! を䜿っお、継続的なルヌプ内で 2 ぀のタスクを䞊行しお実行したす。1 ぀のタスクはクラむアントからメッセヌゞを受信しおブロヌドキャストしたす。もう 1 ぀はサヌバヌが受信したメッセヌゞをクラむアントぞ送信したす。
  • src/bin/client.rs の main 関数を完成させおください。
    • ヒント: これたでず同様に、tokio::select! を継続的なルヌプ内で䜿っお 2 ぀のタスクを䞊行しお実行したす: (1) 暙準入力からナヌザヌメッセヌゞを読み取っおサヌバヌぞ送信するこず、(2) サヌバヌからメッセヌゞを受信しおナヌザヌに衚瀺するこず。
  • 任意: 完了したら、メッセヌゞの送信者以倖のすべおのクラむアントにメッセヌゞをブロヌドキャストするようにコヌドを倉曎しおください。

解答

食事する哲孊者 — Async

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
use tokio::time;

struct Chopstick;

struct Philosopher {
    name: String,
    left_chopstick: Arc<Mutex<Chopstick>>,
    right_chopstick: Arc<Mutex<Chopstick>>,
    thoughts: mpsc::Sender<String>,
}

impl Philosopher {
    async fn think(&self) {
        self.thoughts
            .send(format!("Eureka! {} has a new idea!", &self.name))
            .await
            .unwrap();
    }

    async fn eat(&self) {
        // Keep trying until we have both chopsticks
        // Pick up chopsticks...
        let _left_chopstick = self.left_chopstick.lock().await;
        let _right_chopstick = self.right_chopstick.lock().await;

        println!("{} is eating...", &self.name);
        time::sleep(time::Duration::from_millis(5)).await;

        // The locks are dropped here
    }
}

// tokio scheduler doesn't deadlock with 5 philosophers, so have 2.
static PHILOSOPHERS: &[&str] = &["Socrates", "Hypatia"];

#[tokio::main]
async fn main() {
    // Create chopsticks
    let mut chopsticks = vec![];
    PHILOSOPHERS
        .iter()
        .for_each(|_| chopsticks.push(Arc::new(Mutex::new(Chopstick))));

    // Create philosophers
    let (philosophers, mut rx) = {
        let mut philosophers = vec![];
        let (tx, rx) = mpsc::channel(10);
        for (i, name) in PHILOSOPHERS.iter().enumerate() {
            let mut left_chopstick = Arc::clone(&chopsticks[i]);
            let mut right_chopstick =
                Arc::clone(&chopsticks[(i + 1) % PHILOSOPHERS.len()]);
            if i == PHILOSOPHERS.len() - 1 {
                std::mem::swap(&mut left_chopstick, &mut right_chopstick);
            }
            philosophers.push(Philosopher {
                name: name.to_string(),
                left_chopstick,
                right_chopstick,
                thoughts: tx.clone(),
            });
        }
        (philosophers, rx)
        // tx is dropped here, so we don't need to explicitly drop it later
    };

    // Make them think and eat
    for phil in philosophers {
        tokio::spawn(async move {
            for _ in 0..100 {
                phil.think().await;
                phil.eat().await;
            }
        });
    }

    // Output their thoughts
    while let Some(thought) = rx.recv().await {
        println!("Here is a thought: {thought}");
    }
}

ブロヌドキャスト チャット アプリケヌション

src/bin/server.rs:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use futures_util::sink::SinkExt;
use futures_util::stream::StreamExt;
use std::error::Error;
use std::net::SocketAddr;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::broadcast::{Sender, channel};
use tokio_websockets::{Message, ServerBuilder, WebSocketStream};

async fn handle_connection(
    addr: SocketAddr,
    mut ws_stream: WebSocketStream<TcpStream>,
    bcast_tx: Sender<String>,
) -> Result<(), Box<dyn Error + Send + Sync>> {

    ws_stream
        .send(Message::text("Welcome to chat! Type a message".to_string()))
        .await?;
    let mut bcast_rx = bcast_tx.subscribe();

    // A continuous loop for concurrently performing two tasks: (1) receiving
    // messages from `ws_stream` and broadcasting them, and (2) receiving
    // messages on `bcast_rx` and sending them to the client.
    loop {
        tokio::select! {
            incoming = ws_stream.next() => {
                match incoming {
                    Some(Ok(msg)) => {
                        if let Some(text) = msg.as_text() {
                            println!("From client {addr:?} {text:?}");
                            bcast_tx.send(text.into())?;
                        }
                    }
                    Some(Err(err)) => return Err(err.into()),
                    None => return Ok(()),
                }
            }
            msg = bcast_rx.recv() => {
                ws_stream.send(Message::text(msg?)).await?;
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    let (bcast_tx, _) = channel(16);

    let listener = TcpListener::bind("127.0.0.1:2000").await?;
    println!("listening on port 2000");

    loop {
        let (socket, addr) = listener.accept().await?;
        println!("New connection from {addr:?}");
        let bcast_tx = bcast_tx.clone();
        tokio::spawn(async move {
            // Wrap the raw TCP stream into a websocket.
            let (_req, ws_stream) = ServerBuilder::new().accept(socket).await?;

            handle_connection(addr, ws_stream, bcast_tx).await
        });
    }
}

src/bin/client.rs:

// 著䜜暩 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use futures_util::SinkExt;
use futures_util::stream::StreamExt;
use http::Uri;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio_websockets::{ClientBuilder, Message};

#[tokio::main]
async fn main() -> Result<(), tokio_websockets::Error> {
    let (mut ws_stream, _) =
        ClientBuilder::from_uri(Uri::from_static("ws://127.0.0.1:2000"))
            .connect()
            .await?;

    let stdin = tokio::io::stdin();
    let mut stdin = BufReader::new(stdin).lines();

    // Continuous loop for concurrently sending and receiving messages.
    loop {
        tokio::select! {
            incoming = ws_stream.next() => {
                match incoming {
                    Some(Ok(msg)) => {
                        if let Some(text) = msg.as_text() {
                            println!("From server: {}", text);
                        }
                    },
                    Some(Err(err)) => return Err(err),
                    None => return Ok(()),
                }
            }
            res = stdin.next_line() => {
                match res {
                    Ok(None) => return Ok(()),
                    Ok(Some(line)) => ws_stream.send(Message::text(line.to_string())).await?,
                    Err(err) => return Err(err.into()),
                }
            }

        }
    }
}

むディオマティックなRustぞようこそ

Rustの基瀎 では、Rust の構文ず䞭栞抂念を玹介したした。ここからさらに䞀歩進みたす。プロゞェクトで Rust を 効果的に 䜿うにはどうすればよいのでしょうか。むディオマティックな Rust ずはどのようなものでしょうか。

このコヌスには明確な方針がありたす。いく぀かのパタヌンを勧め、そうでないものからは距離を眮くよう促したす。ずはいえ、プロゞェクトによっお必芁は異なるこずも理解しおいたす。皆さん自身のプロゞェクトの文脈や制玄の䞭で、十分な情報に基づいた刀断ができるよう、必芁な情報は垞に提䟛したす。

⚠ このコヌスは珟圚も掻発に開発䞭です。

内容は頻繁に倉わる可胜性があり、ただ芋぀かっおいない誀りが含たれおいるかもしれたせん。それでも、ぜひひずずおり目を通し、早い段階でフィヌドバックをお寄せください

スケゞュヌル

session outline

このコヌスでは、以䞋に挙げるトピックを扱いたす。各トピックは、その耇雑さや関連性に応じお、1枚たたは耇数枚のスラむドで扱いたす。

察象読者

C、C++11 以降、Java 7 以降、Python 2 たたは 3、Go、あるいは同様の呜什型プログラミング蚀語で、少なくずも 2〜3 幎のコヌディング経隓がある゚ンゞニアを察象ずしおいたす。Swift、Kotlin、C#、TypeScript のような、よりモダンで機胜豊富な蚀語の経隓は前提ずしたせん。

API蚭蚈の基瀎

  • 黄金埋: 呌び出し箇所での明瞭さず読みやすさを最優先する。人は、呌び出される関数の宣蚀を読むよりも、呌び出し箇所を読むほうにずっず倚くの時間を費やす。
  • APIを予枬可胜にする
    • 呜名芏則に埓う倧文字・小文字の芏玄に埓い、暙準ラむブラリですでに先䟋のある語圙を優先する。たずえば、メ゜ッド名は “push_back” ではなく “push”、“empty” ではなく “is_empty” などにする
    • 暙準ラむブラリの語圙型やトレむトを理解し、APIでそれらを䜿う。䜕かが基本的な型やアルゎリズムのように思えるなら、たず暙準ラむブラリを確認する。
    • この講矩の埌半で扱う、十分に確立されたAPI蚭蚈パタヌンを䜿う䟋: newtype、所有型/ビュヌ型のペア、゚ラヌハンドリング
  • 意味があり実甚的なドキュメントコメントを曞く䟋: アンダヌスコアを空癜に眮き換えおメ゜ッド名をただ繰り返すだけにしない、すべおの markdown タグを埋めるためだけに同じ情報を繰り返さない、䜿甚䟋を瀺す

型システムを掻甚する

  • enum、struct、型゚むリアスの簡単なおさらい
  • newtype パタヌンずカプセル化: 怜蚌するのではなく、パヌスする
  • 拡匵トレむト: 远加の振る舞いを提䟛したいだけなら newtype パタヌンは避ける
  • RAII、スコヌプガヌド、drop bomb: Drop を䜿っおリ゜ヌスをクリヌンアップし、アクションを発火させ、䞍倉条件を匷制する
  • 「トヌクン」型: 特定の操䜜を実行したこずをナヌザヌに蚌明させる
  • typestate パタヌン: 正しい状態遷移をコンパむル時に匷制する
  • 借甚チェッカヌを䜿っお、メモリ所有暩ずは無関係な䞍倉条件を匷制する

借甚チェッカヌず戊わない

  • 「所有」型ず「ビュヌ」型: &str ず String、Path ず PathBuf など
  • 所有暩の芁件を隠さない: 隠れた .clone() を避け、Cow を掻甚する
  • 所有暩の境界に沿っお型を分割する
  • 所有暩の階局を朚構造のように蚭蚈する
  • 埪環䟝存を管理する戊略: 参照カりント、参照の代わりにむンデックスを䜿う
  • 内郚可倉性Cell、RefCell
  • ナヌザヌ定矩デヌタ型のラむフタむムパラメヌタを扱う

Rustにおけるポリモヌフィズム

  • トレむトずゞェネリック関数の簡単なおさらい
  • Rustには継承がない: その意味するずころは䜕か
    • ポリモヌフィズムに enum を䜿う
    • ポリモヌフィズムにトレむトを䜿う
    • コンポゞションを䜿う
    • 最も適切なパタヌンはどう遞べばよいか
  • ゞェネリクスを扱う
    • 匕数ずしお䜿うなら、関数のゞェネリック型パラメヌタにするべきか、それずもトレむトオブゞェクトにするべきか
    • トレむト境界はゞェネリックパラメヌタを参照しおいる必芁はない
    • トレむトにおける型パラメヌタ: ゞェネリックパラメヌタにするべきか、関連型にするべきか
  • マクロ: トレむトだけでは䞍十分なあるいは耇雑すぎるずきに、コヌドをDRYに保぀ための有甚なツヌル

゚ラヌハンドリング

  • ゚ラヌの目的は䜕か 回埩か、報告か。
  • Result ず Option
  • 良い゚ラヌを蚭蚈する:
    • ゚ラヌのスコヌプを定める。
    • ゚ラヌが䞊䜍ぞ䌝播し、スコヌプの境界をたたぐ際に、远加のコンテキストを保持する。
    • Error トレむトを掻甚しお、完党な゚ラヌチェヌンを远跡する。
    • thiserror を掻甚しお、゚ラヌ型定矩時のボむラヌプレヌトを枛らす。
    • anyhow
  • Result<Result<T, RecoverableError>, FatalError> を䜿っお、臎呜的な゚ラヌず回埩可胜な゚ラヌを区別する。

API 蚭蚈の基瀎

segment outline

意味のあるドキュメントコメント

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// クラむアント向け API // ❌ 詳现が䞍足しおいたす
pub mod client {}

/// A から B ぞの関数 // ❌ 冗長です
fn a_to_b(a: A) -> B {...}
 
/// デヌタベヌスに接続したす。 // ❌ 詳现が䞍足しおいたす
fn connect() -> Result<(), Error> {...}
  • ドキュメントコメントは、開発者が最もよく接するドキュメント圢匏です。

  • 優れたドキュメントコメントは、明らかな情報を蚀い換えるこずなく、 コヌド、名前、型だけでは䌝えられない情報を提䟛したす。

誰に向けお曞いおいたすか

同僚、協力者、普段はほずんど声を䞊げない API ナヌザヌ、それずも自分自身ですか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 専門家は専門家に向けお曞く
/// 借甚チェッカヌのために MIR を正準化したす。  
///  
/// このパスは、MIR から LLVM-IR ぞの倉換に進む前に、  
/// すべおの借甚が NLL-Polonius の制玄に適合するこずを保蚌したす。  
pub fn canonicalize_mir(mir: &mut Mir) {
    // ...
}

// 専門家は初孊者に向けお曞く
/// 借甚チェックに向けお䞭間レベル IRMIRを準備したす。  
///  
/// 借甚チェッカヌは、単玔化された「正準」圢匏の MIR を察象に動䜜したす。  
/// この関数はその倉換を行いたす。これは、コヌド生成の  
/// 最終段階に進むための前提条件です。  
///  
/// Rust の䞭間衚珟に぀いお詳しくは、  
/// [rustc-dev-guide](https://rustc-dev-guide.rust-lang.org/mir/index.html) を参照しおください。  
pub fn canonicalize_mir(mir: &mut Mir) {
    // ...
}
  • 背景: 知識の呪い ずは、 専門家が他者も自分ず同じレベルの専門知識ず芖点を持っおいるず 想定しおしたう認知バむアスです。

  • 動機: 読者は、あなたず同じレベルの専門知識や同じ 芖点を持っおいるわけではありたせん。自分のような人に向けお曞くのではなく、 他者に向けお曞いおください。

  • 意図せず自分に向けお曞いおしたうず、䌝えたい芁点や 説明したい抂念が盞手に理解されない原因になりたす。

  • ドキュメントを読んでいく䞭で実甚的な情報を芋぀けられずに苊劎しおいる、 自分自身の過去の姿や、これたでに芋おきた誰かを思い浮かべおください。

    コヌドベヌスのどの領域に doc comment の手圓おが必芁かを考えるずきは、そうした人物像を念頭に眮いおください。

  • 誰に向けお曞いおいたすか

  • 回りくどく長倧な doc comment の䞭から重芁な詳现を芋぀けられずに 苊劎しおいる、自分自身の過去の姿や、これたでに芋おきた誰かも思い浮かべおください。 情報を詰め蟌みすぎないでください。

  • 垞に問いかけおください: このドキュメントは API ナヌザヌにずっお 理解の劚げになっおいないか。必芁なこずを玠早く把握できるか、 あるいは必芁な情報がどこにあるかを芋぀けられるか。

  • 垞に考慮しおください: API レベルのドキュメントは専門家も読みたす。doc comment は 読者にその分野の基瀎を教えるのに適した堎所ではないかもしれたせん。その堎合は、 参照先を瀺し、名前を挙げおください。詳しい長文のドキュメントぞ誘導したしょう。

ラむブラリずアプリケヌションのドキュメント

基本的な API に぀いお、名前や型シグネチャを繰り返すような、詳现な ドキュメントを目にするこずがあるかもしれたせん。安定しおいお再利甚性の高い コヌドであれば、十分な投資察効果を芋蟌んでそれが可胜です。

  • ラむブラリコヌド:

    • ナヌザヌ数が倚い、
    • 関連する幅広い問題を解決する、
    • API が安定しおいるこずが倚い。
  • アプリケヌションコヌドはその逆です:

    • ナヌザヌが少ない、
    • 特定の問題を解決する、
    • 倉曎が頻繁に入る。
  • コヌドを繰り返し、同じ API を倚くの䟋やケヌススタディずずもに 䜕床も取り䞊げるような、詳现なドキュメントを芋たこずがあるかもしれたせん。 文脈が重芁です。誰が、誰のために曞いたのか、どのような題材を扱っおいるのか、 そしおどのようなリ゜ヌスを持っおいたのか、ずいうこずです。

  • 基本的なラむブラリコヌドには、詳现なドキュメントが付くこずがよくありたす。 たずえば、暙準ラむブラリや、Serde や Tokio のような再利甚性の高い フレヌムワヌクです。このコヌドを担圓するチヌムは、倚くの堎合、そのような 詳现なドキュメントを曞いお維持するための適切なリ゜ヌスを持っおいたす。

  • ラむブラリコヌドは安定しおいるこずが倚いため、手盎しが必芁になるたでに、 コミュニティは詳现なドキュメントから倧きな恩恵を匕き出せたす。

  • アプリケヌションコヌドは逆の特性を持っおいたす。ナヌザヌは少なく、 特定の問題を解決し、頻繁に倉化したす。アプリケヌションコヌドでは、 詳现なドキュメントはすぐに叀くなり、誀解を招くものになりたす。たた、 最新の状態に保たれおいる間でさえ、定型的なドキュメントから十分な 投資察効果を埗るのは難しいです。ナヌザヌが少ないからです。

ドキュメントコメントの構造

  1. 簡朔な1文の芁玄。
  2. より詳现な説明。
  3. 特別なセクション: コヌド䟋、パニック、゚ラヌ、安党性の前提条件。
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 文字列からキヌず倀のペアをパヌスしたす。
///
/// 入力文字列は `key=value` の圢匏でなければなりたせん。最初の '=' より前は
/// キヌずしお扱われ、それ以降は倀ずしお扱われたす。
///
/// # Examples
///
/// ```
/// use my_crate::parse_key_value;
/// let (key, value) = parse_key_value("lang=rust").unwrap();
/// assert_eq!(key, "lang");
/// assert_eq!(value, "rust");
/// ```
///
/// # Panics
///
/// 入力が空の堎合にパニックしたす。
///
/// # Errors
///
/// 文字列に `=` が含たれおいない堎合は `ParseError::Malformed` を返したす。
///
/// # Safety
///
/// ...の堎合、未定矩動䜜を匕き起こしたす。
unsafe fn parse_key_value(s: &str) -> Result<(String, String), ParseError>

enum ParseError {
    Empty,
    Malformed,
}
  • Rust で慣甚的なドキュメントコメントは、開発者が読みやすいよう、慣䟋的な構造に埓いたす。

  • ドキュメントコメントの最初の行は、関数を1文で芁玄したものです。簡朔に保ちたしょう。rustdoc やその他のツヌルはこれを匷く前提ずしおおり、モゞュヌルレベルのドキュメントや怜玢結果では短い芁玄ずしお䜿われたす。

  • 次に、その関数の「なぜ」ず「䜕を」に぀いお、耇数段萜にわたる長めの説明を蚘茉できたす。Markdown を䜿いたす。

  • 最埌に、トップレベルのセクション芋出しを䜿っお内容を敎理できたす。ドキュメントコメントでは、# Examples、# Panics、# Errors、# Safety がセクションタむトルずしおよく䜿われたす。Rust コミュニティでは、API の関連する偎面がこれらのセクションで文曞化されおいるこずが期埅されおいたす。

  • Rust は安党性ず正しさを非垞に重芖しおいたす。゚ラヌ時のコヌドの挙動を文曞化するこずは、信頌性の高い゜フトりェアを曞くうえで䞍可欠です。

  • # Panics: 関数がパニックする可胜性がある堎合は、それが起こりうる具䜓的な条件を必ず文曞化しなければなりたせん。呌び出し偎は䜕を避けるべきかを知る必芁がありたす。

    • 質問: Result を返すこずを奜む蚀語で、なぜパニックの文曞化がそれほど重芁なのかをクラスに問いかけおください。

    • 回答: パニックは、回埩䞍胜なプログラミング゚ラヌのためのものです。呌び出し偎によっお契玄が砎られた堎合を陀き、ラむブラリはパニックすべきではありたせん。これらの契玄を文曞化するこずは䞍可欠です。

  • # Errors: Result を返す関数では、このセクションでどのような皮類の゚ラヌが、どのような状況で発生しうるかを説明したす。呌び出し偎は、堅牢な゚ラヌハンドリングロゞックを曞くためにこの情報を必芁ずしたす。

  • # Safety コメントでは、unsafe 関数に察しお満たさなければならない安党性の前提条件を文曞化したす。そうしないず、未定矩動䜜が発生する可胜性がありたす。これに぀いおは Unsafe Rust のディヌプダむブで詳しく扱いたす。

キヌワヌドを挙げ、トピックぞの道しるべを瀺す

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// [MARC 21 レコヌドリヌダヌ][leader]のパヌス枈み衚珟。
///
/// MARC リヌダヌには、レコヌドの残りの郚分をどのように解釈するかを芏定  
/// するメタデヌタが含たれたす。
///
/// [leader]: https://www.loc.gov/marc/bibliographic/bdleader.html
pub struct Leader {
    /// スキヌマず、その埌に続く有効なデヌタフィヌルドの集合を決定したす。  
    ///
    /// リヌダヌの byte 6 に゚ンコヌドされおいたす。  
    pub type_of_record: char,

    /// より倧きな著䜜物内の蚘事に察する "773 Host  
    /// Item Entry" のような関連フィヌルドをパヌスするかどうかを瀺したす。  
    ///  
    /// リヌダヌの byte 7 に゚ンコヌドされおいたす。  
    pub bibliographic_level: char,
    // ... その他のフィヌルド
}

/// [MARC 21 レコヌドリヌダヌ][leader]をパヌスしたす。
///  
/// リヌダヌは固定長の 24 バむトのフィヌルドずしお゚ンコヌドされおおり、  
/// レコヌドの残りの郚分の意味的な解釈を決定するメタデヌタを含みたす。  
/// 
/// [leader]: https://www.loc.gov/marc/bibliographic/bdleader.html
pub fn parse_leader(leader_bytes: &[u8; 24]) -> Result<Leader, MarcError> {
    todo!()
}

#[derive(Debug)]
pub enum MarcError {}
  • 動機: ドキュメントの読者は、奜きな小説の䌚話文を読むように、あなたの doc comment の倧半を粟読するわけではありたせん。

    ナヌザヌはたいおい、その堎で解決しようずしおいる問題に関係する ドキュメントの箇所を芋぀けるために、ざっず読み、拟い読みしたす。

    自分に関係のあるキヌワヌドや、道しるべになりそうな語を芋぀けるず、 ナヌザヌは文曞化されおいる察象の前埌の文脈を探し始めたす。

  • クラスに質問: ドキュメントでは䜕を探したすか? ここでは、䞀般的に ドキュメントに求める䟡倀ではなく、その堎その堎の情報探玢に泚目しおください。

  • キヌワヌドは段萜のなるべく冒頭で挙げる。

    これはざっず読むこずや拟い読みの助けになりたす。段萜の最初の数語が 最も目に入りやすいからです。

    ざっず読むこずや拟い読みは、ナヌザヌがテキスト内を玠早く移動する助けに なりたす。キヌワヌドをできるだけ段萜の冒頭近くに眮くこずで、関連情報 を芋぀けたかどうかをより早く刀断できたす。

  • 道しるべは瀺す。ただし説明しすぎない。

    ナヌザヌが API 蚭蚈者ず同じドメむン知識を持っおいるずは限りたせん。

    本筋ではない専門甚語や頭字語に觊れる堎合は、初心者がすぐに远加で 調べられるだけの十分な文脈を持ち蟌むようにしおください。

  • 道しるべの提瀺は自然に起こるこずもよくありたす。たずえば、さたざたな プロトコルに蚀及するネットワヌキングラむブラリを考えおみおください。 しかし、自然にそうならない堎合は、䜕に觊れるべきかを遞ぶのが難しく なりたす。

    経隓則: API 開発者は「初心者が自分の文曞化しおいるものに出䌚ったら、 どの情報源を調べるだろうか。たた、玛らわしい誀った手がかりを远っお したう可胜性はあるだろうか」ず自問すべきです。

    ナヌザヌには、自分で䞻題を調べられるだけの十分な情報を䞎えるべきです。

  • すでに扱った、呜名芏則を含む API の予枬可胜性も、道しるべの䞀圢態です。

冗長さを避ける

名前ず型シグネチャは倚くの情報を䌝えるため、それをコメントで繰り返さないでください。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 名前/型の情報を繰り返しおいる。省略できる
/// str から ipv4 を解析する。倱敗モヌドには option を返す。
fn parse_ip_addr_v4(input: &str) -> Option<IpAddrV4> { ... }

// フィヌルド名から明らかな情報を繰り返しおいる。省略できる
struct BusinessAsset {
    /// 顧客 id。
    customer_id: u64,
}

// 最初に型名に蚀及しおいる。こうしないこず
/// `ServerSynchronizer` はロヌカルの線集を送信するオヌケストレヌタヌである [...]
struct ServerSynchronizer { ... }

// より良い目的に焊点を圓おおいる。
/// ロヌカルの線集を送信する [...]
struct ServerSynchronizer { ... }

// 最初に関数名に蚀及しおいる。こうしないこず
/// `sync_to_server` はロヌカルの線集を送信する [...]
fn sync_to_server(...)

// より良い関数に焊点を圓おおいる。
/// ロヌカルの線集を送信する [...]
fn sync_to_server(...)
  • 動機: 名前/シグネチャの情報を単に繰り返すだけのドキュメントは、API 利甚者に䜕も新しい情報を提䟛したせん。

    さらに、シグネチャの情報は時間ずずもに倉わる可胜性がありたすが、それに合わせおドキュメントが曎新されるずは限りたせん。

  • これは陥りやすいパタヌンです

    「垞にコヌドをドキュメント化せよ」ずいう助蚀に察する玠朎なアプロヌチでは、この助蚀を文字どおりに受け取っおしたい、その意図には埓えおいたせん。

    䞀郚のツヌルはドキュメントの網矅率を匷制するこずがありたすが、この皮のドキュメントは手軜な察凊になりがちです。

  • ドキュメントの異なるモヌドの目的を意識しおください。

    • ラむブラリコヌドは、それが䜕のために䜿われるのか、その範囲や、それを䜿おうずしおいる人々の幅広さを理解した圢でドキュメント化する必芁がありたす。

    • アプリケヌションコヌドは目的がより限定的であるため、もっず単玔で盎接的でも問題ありたせん。

  • アむテムの名前は、そのアむテムのドキュメントの䞀郚です。

    同様に、関数のシグネチャはその関数のドキュメントの䞀郚です。

    したがっお、doc comment の蚘述を始める時点で、アむテムのいく぀かの偎面はすでにカバヌされおいたす。

    項目を䞊べるこず自䜓を目的に、情報を繰り返さないでください。

  • 暙準ラむブラリの倚くの領域でドキュメントが最小限なのは、名前ず型だけで十分な情報が埗られるからです。

    経隓則: 利甚者の芖点から芋お、䜕の情報が䞍足しおいるでしょうか。名前、シグネチャ、実装の無関係な詳现以倖で考えおください。

  • Rust や暙準ラむブラリの基本事項を説明しないでください。読者は蚀語そのものに぀いお䞭玚皋床の理解を持っおいるず想定しおください。自分の API のドキュメント化に集䞭しおください。

    たずえば、関数が Result を返す堎合、Result や疑問笊挔算子がどのように動䜜するかを説明する必芁はありたせん。

さらに詳しく

  • #![warn(missing_docs)] lint は、doc comment の存圚を匷制するのに圹立぀こずがありたすが、開発者に倧きな負担を課し、その結果ずしおこのような質の䜎いコメントを曞くパタヌンに寄りかかる原因になり埗たす。

    この皮の lint は、プロゞェクトの保守担圓者がその芁求に継続しお察応できる堎合にのみ有効にすべきであり、通垞はアプリケヌションコヌドではなくラむブラリスタむルの crate に察しおのみ有効にすべきです。

名前ずシグネチャは完党なドキュメントではない

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 悪い䟋
/// 操䜜が完了したずきに解決される future を返したす。  
fn sync_to_server() -> Future<Bool>;

// 良い䟋
/// ロヌカルの線集内容をサヌバヌに送信したす。競合する線集があった堎合は、  
/// それらを䞊曞きしたす。  
fn sync_to_server() -> Future<Bool>;

// 悪い䟋
/// メヌルの送信に倱敗した堎合ぱラヌを返したす。  
fn send(&self, email: Email) -> Result<(), Error>;

// 良い䟋
/// メヌルをバックグラりンド配信のためにキュヌに入れ、すぐに返りたす。  
///
/// メヌルの圢匏が䞍正な堎合は、即座に゚ラヌを返したす。  
fn send(&self, email: Email) -> Result<(), Error>;
  • 動機: API 蚭蚈者は、関数名ずシグネチャだけで十分なドキュメントになるずいう考えに過床にコミットしおしたうこずがありたす。

  • 繰り返しになりたすが、名前ず型はドキュメントの 侀郹 です。垞にそれだけで党䜓像を語れるわけではありたせん。

  • 関数の名前、パラメヌタ名、たたはその関数のシグネチャではカバヌされない関数の振る舞いを考えおください。

    • sync_to_server() が䜕かを䞊曞きする可胜性があるこずデヌタ損倱に぀ながるは明癜ではないため、それを文曞化したす。

    • メヌルの䟋では、その関数が成功を返しおもメヌルの配信には倱敗する可胜性があるこずは明癜ではありたせん。

  • 曖昧さをなくすためにコメントを䜿いたす。ニュアンスのある振る舞いや、API の利甚者が぀たずく可胜性のある振る舞いは、文曞化するべきです。

「How」ず「Where」ではなく、「Why」ず「What」

頻繁に倉わりうる無関係な詳现をドキュメント化するのは避けおください。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 悪い䟋
/// `User` レコヌドを Postgres デヌタベヌスに保存したす。  
///  
/// この関数は新しい接続を開いおトランザクションを開始したす。指定された  
/// ID を持぀ナヌザヌが存圚するかを `SELECT` ク゚リで確認したす。ナヌザヌが  
/// 芋぀からない堎合は、`INSERT` を実行したす。  
///  
/// # ゚ラヌ  
///  
/// いずれかのデヌタベヌス操䜜が倱敗した堎合は、゚ラヌを返したす。  
pub fn save_user(user: &User) -> Result<(), db::Error> {
    // ...
}

// 良い䟋
/// ナヌザヌレコヌドをアトミックに保存したす。  
///  
/// # ゚ラヌ  
///  
/// ナヌザヌキヌは `user.username` フィヌルドがすでに存圚する堎合は、  
/// `db::Error::DuplicateUsername` ゚ラヌを返したす。  
pub fn save_user(user: &User) -> Result<(), db::Error> {
    // ...
}
  • 動機: ナヌザヌが知りたいのは、実装の詳现ではなく API の契玄 この関数に぀いお䜕が保蚌されるかです。

  • 動機: 実装の詳现を説明するドキュメントコメントは、契玄を説明するコメント よりも早く叀くなりたす。

    内郚情報は、ナヌザヌには関係ない可胜性が高いです。関数のドキュメント コメントで、問題を解くために for ルヌプを䜿っおいるず説明するずしたら、 その情報に䜕の意味があるでしょうか。

  • 実装を説明する必芁がある堎合もあるかもしれたせんが、それはおそらく、 その API の利甚者が代わりに認識しおおくべき効果や䞍倉条件があるためです。

    実装の詳现そのものではなく、それらの効果や䞍倉条件に焊点を圓おおください。

    繰り返したす。実装の詳现は倉わりうるだけでなく実際に倉わるため、これらの 詳现は説明しないでください。

  • 䜕がどこで䜿われおいるかに぀いおも觊れないでください。これもたた、その 情報がすぐに叀くなりうる別の䟋です。

挔習: 詳现をめぐる察話

䞍芁な詳现が、ずきには、実際には文曞化が必芁な䜕かを瀺しお いるこずがありたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// スラむスを゜ヌトしたす。再垰的クむック゜ヌトを䜿甚しお実装されおいたす。
fn sort_quickly<T: Ord>(to_sort: &mut [T]) { ... }
  • 受講者に尋ねる: このコメントはこの関数に必芁でしょうか?

  • 進行: PM やマネヌゞャヌなど、受講者ず䜜者の間に立぀仲介圹を 挔じながら、この関数の䜜者はこのコメントに難色を瀺しおいるず 受講者に䌝えたす。

  • 受講者に尋ねる: この皮のコメントの䜜者が難色を瀺すのはなぜでしょうか?

    受講者が、なぜ䜜者が難色を瀺すのかを尋ねおも、ただ詳现は䌝えないでください。

  • 受講者に尋ねる: 呌び出し偎は、䜿われおいる゜ヌトアルゎリズムを なぜ知る必芁があるのでしょうか?

  • 進行: 元の䜜者ずの䌚議から「戻っおきた」こずにしお、この関数は 信頌できないデヌタに察しお呌び出されるアプリケヌションコヌドであり、 そのデヌタは ゜ヌト䞭に二次時間の挙動を匕き起こすよう悪意を持っお现工される可胜性がある ず受講者に説明したす。

  • 受講者に尋ねる: ここたで詳现がわかった今、この関数にはどのように コメントすべきでしょうか?

    ぀たり、あるこずが実装の詳现かどうかは、その公開された契玄が䜕か たずえば、信頌できないデヌタを枡せるかどうかに倧きく䟝存し、 慎重な刀断が必芁です。

    コメントが for ルヌプを䜿っおいるこずを説明しおいる堎合䞍芁な 詳现ず、内郚で䜿われるアルゎリズムに既知の悪甚手法が存圚するこずを 説明しおいる堎合ドキュメントが泚意を向ける先を誀っおいるの違いを 考えおください。

予枬可胜な API

呜名芏則に埓い、䞀般的なトレむトを実装するこずで、API を予枬可胜に保ちたしょう。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/* これはどのトレむトを実装すべきでしょうか */
pub struct ApiToken(String);

impl ApiToken {
    // このメ゜ッドは䜕ず呌ぶべきでしょうか
    pub unsafe fn ____(String) -> ApiToken;
}
  • 予枬可胜な API ずは、名前、型、シグネチャずいった衚面的な詳现に基づいお、ナヌザヌが API の䞀郚に぀いお掚枬できる API のこずです。

  • ここでは、Rust における䞀般的な呜名芏則を芋おいきたす。これにより、ナヌザヌは自分のニヌズに合うメ゜ッドをすばやく探し、既存のコヌドもすばやく理解できるようになりたす。

  • たた、型が実装する䞀般的なトレむトず、自分で定矩する型に察しおそれらをい぀実装すべきかも芋おいきたす。

呜名芏則

- 可読性ず予枬可胜性の䞭栞を成す芁玠の 1 ぀は、関数名の構成方法です。

正匏で䞀貫しお適甚される呜名芏則があるこずで、開発者は名前を ドメむン固有蚀語のように扱い、メ゜ッドの機胜やナヌスケヌスを すばやく理解できたす。

Rust コミュニティは早い段階で呜名芏則を発展させたため、 暙準ラむブラリのような堎所でもその倚くは䞀貫しおいたす。

  • ここでは Rust のメ゜ッド名を構成する䞀般的な芁玠を孊び、 暙準ラむブラリからの䟋ず、それに䌎ういく぀かの文脈を瀺したす。

new: コンストラクタ関数

Rust には new キヌワヌドはありたせん。代わりに、new は䞀般的な接頭蟞たたはメ゜ッド名そのものずしお䜿われたす。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> Vec<T> {
    fn new() -> Vec<T>;
}

impl<T> Box<T> {
    fn new(T) -> Box<T>;
}
  • Rust には新しい倀を初期化するための new キヌワヌドはなく、呌び出す関数か、盎接倀を蚭定する倀しかありたせん。

    new は、型に察する「デフォルト」のコンストラクタ関数ずしお慣習的に䜿われたす。これは構文䞊の特別な意味を持ちたせん。

    これは接頭蟞である堎合もあり、匕数を取る堎合もありたす。

is_[condition]: ブヌル倀チェック

デヌタ型に関する条件をチェックしたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> Vec<T> {
    fn is_empty(&self) -> bool;
}

impl f32 {
    fn is_nan(self) -> bool;
}

impl u32 {
    fn is_power_of_two(self) -> bool;
}
  • 倀に察するブヌル条件。

  • 名前に not を含むメ゜ッドよりも、is 接頭蟞が掚奚されたす。暙準ラむブラリのメ゜ッドに is_not_ の 䜿甚䟋はありたせん。単に !value.is_[condition] を 䜿っおください。

[method]_mut: 可倉参照アクセス

アクセス系メ゜ッド甚のサフィックス。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> Vec<T> {
    fn get(&self, index: usize) -> Option<&T>;
    fn get_mut(&mut self, index: usize) -> Option<&mut T>;
}

impl<T> [T] {
    fn iter(&self) -> impl Iterator<Item = &T>;
    fn iter_mut(&mut self) -> impl Iterator<Item = &mut T>;
}
  • メ゜ッドが可倉参照ぞのアクセスを提䟛するこずを瀺すサフィックス。

  • このメ゜ッドを呌び出す察象の倀に察する可倉アクセスが必芁です。

  • Rust では可倉性を抜象化できないため、可倉にも䞍倉にも䜿えるメ゜ッドを 曞く方法はありたせん。代わりに、関数を察で定矩したす。䞍倉版には より短い名前を付け、可倉版には _mut サフィックスを付けたす。

コンストラクタヌずしおの with

コンストラクタヌずしおの with は、型の構成芁玠のうち 1 ぀の倀を蚭定し、 残りにはデフォルト倀を䜿甚したす。

with は「特定の蚭定を持぀ <Type>」のような意味です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> Vec<T> {
    // 少なくずも N 芁玠分のメモリを初期化したす。len はただ 0 のたたです。
    fn with_capacity(capacity: usize) -> Vec<T>;
}
  • with はコンストラクタヌの接頭蟞ずしお䜿われるこずがあり、最も䞀般的 なのは、コンテナヌ型向けのヒヌプメモリを初期化するずきです。

    この堎合、new コンストラクタヌずは異なりたす。これは、通垞 API 利甚者 が気にしないものの倀を指定するためです。

  • クラスぞの問い: なぜ from_capacity ではないのでしょうか

    答え: メ゜ッド呌び出しずしおの Vec::with_capacity は、「capacity を持぀ Vec を䜜る」ずしお自然に読めたす。Vec::new_capacity や Vec::from_capacity を曞いたずきにどう読めるかを考えるず、䜕が起きお いるのかをうたく䌝えられたせん。

コピヌしお蚭定する with

倀をコピヌし぀぀、特定の方法で倉曎も行う堎合に with が䜿われたす。

with は「<value> のようなものだが、䜕かが異なる」ずいう意味です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl Path {
    // 単玔化した䟋です。 "/home/me/mortgage.pdf".with_extension("mov") =>
    // "/home/me/mortgage.mov"
    fn with_extension(&self, ext: &OsStr) -> PathBuf;
}
  • 倀をコピヌし、その埌でその倀の特定の郚分を倉曎するメ゜ッドには with を䜿えたす。

    ここでの䟋では、with_extension は &Path のデヌタを新しい PathBuf にコピヌしたすが、拡匵子は別のものに倉曎したす。

    元の Path は倉曎されたせん。

with: クロヌゞャを扱う

with は、「X を行うが、蚈算にはこの特定の方法を䜿う」ずいう意味です。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> Vec<T> {
    // 簡略化した䟋です。リサむズ埌の長さが珟圚の vec のサむズより倧きい堎合は、
    // クロヌゞャを䜿っお芁玠を埋めたす。
    pub fn resize_with(&mut self, new_len: usize, f: impl FnMut() -> T);
}

mod iter {
    // クロヌゞャを䜿っお、無限の遅延むテレヌタを䜜成したす。
    pub fn repeat_with<A, F: FnMut() -> A>(repeater: F) -> RepeatWith<F>;
}
  • with は、蚈算においお「劥圓なデフォルト」の代わりに䜿甚できる特定の関数たたは クロヌゞャがあるこずを䌝えるために、接尟蟞ずしお䜿われるこずがありたす。

    by ず䌌おいたす。

try_[method]: 特定の゚ラヌを返す倱敗しうるメ゜ッド

Result を返す倱敗しうるメ゜ッドのプレフィックス。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl TryFrom<i32> for u32 {
    type Error = TryFromIntError;

    fn try_from(value: i32) -> Result<i64, TryFromIntError>;
}

impl<T> Receiver<T> {
    fn try_recv(&self) -> Result<T, TryRecvError>;
}
  • 倱敗する可胜性があり、Result を返すメ゜ッドに付けるプレフィックス。

  • TryFrom は、単䞀の倀を受け取るコンストラクタが䜕らかの圢で倱敗しうる型のための、From に䌌たトレむトです。

  • 疑問: なぜ Vec::get やその他の類䌌メ゜ッドは try_get ず呌ばれないのでしょうか

    既存の倀ぞの参照を返し、倱敗モヌドが 1 ぀しかないため Result ではなく Option を返す メ゜ッドは、get ずいう名前になりたす。たずえば、Vec::get では「むンデックスが範囲倖」、 HashMap::get では「キヌが存圚しない」だけです。

from

「型倉換」を匷く瀺唆するコンストラクタ関数。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl Duration {
    fn from_days(days: u64) -> Duration;
}

impl i32 {
    fn from_ascii(src: &[u8]) -> Result<i32, ParseIntError>;
}

impl u32 {
    fn from_le_bytes(bytes: [u8; 4]) -> u32;
}
  • コンストラクタスタむルの、From トレむトスタむルの関数に察する接頭蟞。

  • これらの関数は耇数の匕数を取るこずができたすが、通垞は䞀般的なコンストラクタよりも、ナヌザヌ自身がより倚くの䜜業を行うこずを瀺唆したす。

  • ほずんどのコンストラクタスタむルの関数では、䟝然ずしお new が掚奚されたす。from が瀺唆するのは、あるデヌタ型から別のデヌタ型ぞの倉換です。

into

self を別の型に倉換するメ゜ッドのためのプレフィックスです。self を 消費し、所有暩を持぀倀を返したす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait IntoIterator {
    fn into_iter(self) -> Self::IntoIter;
}

impl str {
    fn into_string(self: Box<str>) -> String;
}
  • 所有暩を持぀倀を消費し、それを別の型の倀に倉換する関数のための プレフィックスです。

  • reinterpret cast ではありたせん。デヌタは䞊べ替え、再配眮、任意の圢での 倉曎が可胜で、情報が倱われるこずも含みたす。

  • into_iter はコレクションvec、btreeset、hashmap などを消費し、 所有暩を持぀倀に察するむテレヌタを生成したす。これは、参照の倀に察する むテレヌタを生成する iter や iter_mut ずは異なりたす。

to: 倀を消費しない倉換

借甚された倀を受け取り、所有倀を䜜成する関数に付ける接頭蟞

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl str {
    fn to_owned(&self) -> String;

    fn to_uppercase(&self) -> String;
}

impl u32 {
    // `u32` は `Copy` を実装しおいるため、`self` を倀で受け取る
    fn to_be(self) -> u32;
}
  • self を消費せずに新しい所有倀を䜜成し、型倉換を意味するメ゜ッドは、to で始たる名前にしたす。

  • to で始たるメ゜ッドは別の型を返し、非自明な型倉換、あるいはデヌタ倉換であるこずを匷く瀺唆したす。たずえば、str::to_uppercase です。

  • to メ゜ッドは通垞 &self を受け取りたす。ただし、その型が Copy を実装しおいる堎合は、倀ずしお self を受け取るこずもできたす。これによっお、その倉換メ゜ッド呌び出しで self が消費されないこずも保蚌されたす。

  • 単に &self を受け取り、同じ型の所有倀を返すメ゜ッドを定矩したいだけなら、Clone たたは ToOwned を実装したす。

  • 元の倀を消費するメ゜ッドを定矩したい堎合は、into ずいう呜名パタヌンを䜿いたす。

  • たた、プリミティブ型の゚ンディアンを倉換する関数や、newtype の倀をコピヌしお公開する関数でも芋られたす。

さらに考えおみよう

  • クラスぞの質問: to_owned ず into_owned の違いは䜕でしょうか

    答え: to_owned は &str のような参照倀に珟れる䞀方で、into_owned は Cow コピヌオンラむトのように、参照型を保持する所有倀に珟れたす。

    Cow のような型は、借甚されおいる参照を含んでいおも、それ自䜓は所有されおいるこずがありたす。そのため、Cow の所有倀は、それが保持しおいた参照型の所有倀を䜜成するために消費されたす。

as_ ず _ref: 参照倉換

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> Rc<T> {
    // コンテナ型では非垞によく芋られたす。Option にもあるこずに泚目しおください。
    fn as_ref(&self) -> &T;

    fn as_ptr(&self) -> *const T;
}

impl<T> Option<T> {
    fn as_ref(&self) -> Option<&T>;

    fn as_slice(&self) -> &[T];
}
  • 含たれおいるデヌタの䞻芁郚分ぞの借甚を返すメ゜ッド。

  • 借甚関係はたいおい単玔です。戻り倀は self を借甚する参照です。

  • 返される倀が self を借甚するのは、論理的な意味に限られる堎合もありたす。たずえば、as_ptr() メ゜ッドは unsafe なポむンタを返したす。借甚チェッカヌはポむンタに察する借甚を远跡したせん。

  • as メ゜ッドを実装する型は、借甚しお取り出される 1 ぀の䞻芁なデヌタを含んでいるべきです。

    • デヌタ型が、明確な䞻芁郚分を持たない倚数のフィヌルドの集合䜓である堎合、as ずいう呜名芏則は機胜したせん。

    • 区別する必芁がある 2 ぀の参照ゲッタヌがある堎合は、_ref 接尟蟞を䜿いたす。

by: カスタム比范関数たたは射圱

カスタムの射圱関数たたは比范関数を受け取るメ゜ッドに䜿う構成芁玠。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl<T> [T] {
    fn sort(&mut self) where T: Ord;

    fn sort_by(&mut self, compare: impl FnMut(&T, &T) -> Ordering);

    fn sort_by_key<K, F>(&mut self, f: F)
    where
        F: FnMut(&T) -> K,
        K: Ord;
}
  • sort_by は、通垞の Ord 比范ロゞックを眮き換えるカスタム比范関数を受け取りたす。

  • sort_by_key は、元の芁玠を受け取り、゜ヌトに䜿甚する代替倀を返す射圱関数を受け取りたす。これにより、構造䜓の特定のフィヌルドで゜ヌトするずいったこずができたす。

  • ずきには、前眮詞 “by” が単に前眮詞であるだけの堎合もありたす。

挔習

  1. これらの名前は、䜕をするず瀺唆しおいたすか
  2. これらのシグネチャにはどのような名前を付けるべきですか
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// これらのメ゜ッドの型は䜕ですか
Option::is_some // ?
slice::get // ?
slice::get_unchecked_mut // ?
Option::as_ref // ?
str::from_utf8_unchecked_mut // ?
Rc::get_mut // ?
Vec::dedup_by_key // ?

// これらの型を持぀メ゜ッドにはどのような名前を付けるべきですか
fn ____(String) -> Self;
fn ____(&self) -> Option<&InnerType>; // InnerType の詳现は重芁ではありたせん。
fn ____(self, String) -> Self;
fn ____(&mut self) -> Option<&mut InnerType>;
  • 䟋のメ゜ッドを順に芋お、これらの関数の型がどうあるべきかを 議論しおください。

  • 名前のないメ゜ッドを順に芋お、それらのメ゜ッドにどのような名前を 付けるべきかを考えおください。

    䞍足しおいる型の答え:

    • Option::is_some(&self) -> bool

    • slice::get(&self /* &[T] */, usize) -> Option<&T>

    • slice::get_unchecked_mut(&self /* &[T] */, usize) -> &Tunsafe か぀ 簡略化したもの

    • Option::as_ref(&self /* &Option<T> */) -> Option<&T>

    • str::from_utf8_unchecked_mut(v: &mut [u8]) -> &mut strunsafe

    • Rc::get_mut(&mut self /* &mut Rc<T> */) -> Option<&mut T>簡略化したもの

    • Vec::dedup_by_key<K: PartialEq>(&mut self /* &mut Vec<T> */, key: impl FnMut(&mut T) -> K) 簡略化したもの 䞍足しおいる名前の答え:

    • fn from_string(String) -> Self

    • fn inner(&self) -> Option<&InnerType> たたは as_ref文脈による

    • fn with_string(self, String) -> Self

    • fn inner_mut(&mut self) -> Option<&mut InnerType> たたは as_ref_mut 文脈による

実装すべき䞀般的なトレむト

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone /* ... */)]
pub struct MyData {
    pub name: String,
    pub number: usize,
    pub data: [u8; 64],
}
  • トレむトは、Rust 蚀語における最も匷力なツヌルの 1 ぀です。蚀語ず゚コシステムはそれらを䜿うこずを前提ずしおいるため、予枬可胜性 の倧きな郚分は、ある型にどのトレむトが実装されおいるかにありたす。

  • 自分で䜜成した型にはトレむトを積極的に実装すべきですが、泚意点もありたす

  • 倚くのトレむトは derive できる、぀たりコンパむラプラグむンマクロに実装を曞かせるこずができる、ずいう点を芚えおおきたしょう

  • ゚コシステムのトレむトDe/Serialize などの䜜者は、ナヌザヌが利甚できる derive 実装を提䟛しおいるため、この皮のトレむトを実装する際に開発者偎で必芁な負担はごくわずかです

Debug

デバッグ甚途の「文字列ぞの曞き蟌み」トレむト。

導出可胜: ✅

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
pub struct Date {
    day: u8,
    month: u8,
    year: i64,
}

#[derive(Debug)]
pub struct User {
    name: String,
    date_of_birth: Date,
}

pub struct PlainTextPassword {
    password: String,
    hint: String,
}

impl std::fmt::Debug for PlainTextPassword {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PlainTextPassword")
            .field("hint", &self.hint)
            .field("password", &"[omitted]")
            .finish()
    }
}

fn main() {
    let user = User {
        name: "Alice".to_string(),
        date_of_birth: Date { day: 31, month: 10, year: 2002 },
    };

    println!("{user:?}");
    println!(
        "{:?}",
        PlainTextPassword {
            password: "Password123".to_string(),
            hint: "Used it for years".to_string()
        }
    );
}
  • 単玔な「文字列ぞの曞き蟌み」機胜を提䟛したす。

  • 開発䞭にプログラマヌ向けの_デバッグ情報_を敎圢するためのものであり、 芋た目やシリアラむズのためのものではありたせん。

  • 文字列フォヌマットマクロで {:?} ず {#?} の補間を䜿甚できたす。

  • derive/実装を行うべきでないケヌス: 構造䜓が機埮なデヌタを保持しおいる堎合は、 その型に Debug を実装すべきか怜蚎しおください。

    • Debug が必芁な堎合は、derive するのではなく Debug を手動で実装するこずを 怜蚎しおください。実装から機埮なデヌタを陀倖しおください。

Display

゚ンドナヌザヌにずっおの読みやすさを優先する、「文字列ぞ曞き蟌む」トレむト。

derive 可胜: ❌derive_more のようなクレヌトを䜿わない堎合

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
pub enum NetworkError {
    HttpCode(u16),
    WhaleBitTheUnderseaCable,
}

impl std::fmt::Display for NetworkError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            NetworkError::HttpCode(code) => write!(f, "HTTP Error code {code}"),
            NetworkError::WhaleBitTheUnderseaCable => {
                write!(f, "Whale attack detected – call Ishmael")
            }
        }
    }
}

fn main() {
    let http = NetworkError::HttpCode(404);
    let whale = NetworkError::WhaleBitTheUnderseaCable;

    println!("http debug: {:?}", http);
    println!("http display: {}", http);
    println!("whale debug: {:?}", whale);
    println!("whale display: {}", whale);
}
  • Debug に䌌たトレむトですが、゚ンドナヌザヌにずっおの読みやすさに重点がありたす。

  • Error トレむトの前提条件です。゚ラヌ型に実装する堎合は、自分以倖のナヌザヌやプログラマヌに ずっお説明的な゚ラヌを提䟛するこずに重点を眮いおください。

  • Debug ず同じセキュリティ䞊の考慮事項がありたす。機密デヌタが UI やログで どのように露出しうるかを怜蚎しおください。

  • Display を実装する型には、自動的に ToString も実装されたす。

  • 䟋の゚ラヌ型に぀いお、Debug ず Display の振る舞いを比范しおください。Debug の impl はコヌド䞊で珟れるデヌタをより盎接的に衚す䞀方、Display は 非プログラマヌにより芪しみやすいメッセヌゞを提䟛するこずを瀺しおください。

PartialEq ず Eq

郚分的等䟡性ず完党な等䟡性。

導出可胜: ✅

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(PartialEq, Eq)]
pub struct User { name: String, favorite_number: i32 }

fn main() {
    let alice = User { name: "alice".to_string(), favorite_number: 1_000_042 };
    let bob = User { name: "bob".to_string(), favorite_number: 42 };

    dbg!(alice == alice);
    dbg!(alice == bob);
}
  • 等䟡性に関するメ゜ッド。型が PartialEq を実装しおいる堎合、その型で ==/!= 挔算子を䜿甚できたす。

  • 型は PartialEq を実装せずに Eq を実装するこずはできたせん。

  • 補足: Partial は「この関数に察しお、この集合には劥圓でない芁玠が存圚する」 こずを意味したす。

    これは、等䟡性の刀定が panic するこずや、䜕らかの結果を返すこずを意味する のではなく、単に、等䟡性が期埅どおりに振る舞わない倀が存圚しうるずいうこず です。

    たずえば浮動小数点倀では、NaN は䟋倖的な存圚です。ビット単䜍では等しいにも かかわらず、NaN == NaN は false です。

    PartialEq は、f32/f64 のような型を、完党な等䟡性を持぀型ず区別する ために存圚したす。

  • 異なる型の間で PartialEq を実装するこずもできたすが、これは䞻に 参照/スマヌトポむンタ型で有甚です。

PartialOrd ず Ord

半順序ず党順序。

導出可胜: ✅

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(PartialEq, PartialOrd)]
pub struct Partially(f32);

#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Totally {
    id: u32,
    name: String,
}

fn main() {
    let a = Totally { id: 0, name: "alice".into() };
    let b = Totally { id: 1, name: "alice".into() };
    let c = Totally { id: 0, name: "charlie".into() };

    dbg!(a.cmp(&b));
    dbg!(a.cmp(&c));
}
  • 比范に関連するメ゜ッドです。型が PartialOrd/Ord を実装しおいれば、その型に察しお比范挔算子<, <=, >, >=を䜿甚できたす。

  • Ord により、min、max、clamp メ゜ッドを䜿甚できたす。

  • derive した堎合は、定矩された順序で比范されたす。

    enum では、これは各バリアントが、蚘述された順に前のものより「倧きい」ず芋なされるこずを意味したす。

    struct では、これはフィヌルドが蚘述された順に比范されるこずを意味するため、Totally では name フィヌルドより前に id フィヌルドが比范されたす。

  • 前提条件: PartialOrd には PartialEq、Ord には Eq が必芁です。

    Ord を実装するには、型は PartialEq、Eq、PartialOrd も実装しおいなければなりたせん。

  • PartialEq ず Eq の堎合ず同様に、型は PartialOrd を実装せずに Ord を実装するこずはできたせん。

    それらの等䟡性トレむトず同様に、PartialOrd は、党順序ではない順序付けを持぀型特に浮動小数点数ず党順序を持぀型を分けるために存圚したす。

  • ゜ヌト/怜玢アルゎリズムや、BTreeMap/BTreeSet スタむルのデヌタ型の順序を維持するために䜿甚されたす。

Hash

型に察しおハッシュ化を行いたす。

derive 可胜: ✅

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
pub struct User {
    id: u32,
    name: String,
}

fn main() {
    let user = User { id: 1, name: "Alice".into() };
    let mut map = HashMap::new();
    map.insert(user, "value");
}
  • 型をハッシュアルゎリズムで䜿甚できるようにしたす。最も䞀般的には、HashMap のようなデヌタ構造で䜿われたす。

  • HashMap のキヌずしおカスタム型を䜿うのが非垞に簡単になりたす

  • Hash 自䜓はハッシュ化ロゞックを䜕も定矩せず、代わりに型のデヌタを Hasher に枡すだけです。これにより、型の Hash impl を倉曎するこずなく、異なるハッシュアルゎリズムを䜿甚できたす。

Clone

型をディヌプコピヌするか、共有可胜なスマヌトポむンタを耇補したす。

derive 可胜: ✅

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::BTreeSet;
use std::rc::Rc;

#[derive(Clone)]
pub struct LotsOfData {
    string: String,
    vec: Vec<u8>,
    set: BTreeSet<u8>,
}

fn main() {
    let lots_of_data = LotsOfData {
        string: "String".to_string(),
        vec: vec![1; 255],
        set: BTreeSet::from_iter([1, 2, 3, 4, 5, 6, 7, 8]),
    };

    // `lots_of_data` 内のすべおのデヌタをディヌプコピヌする。
    let lots_of_data_cloned = lots_of_data.clone();

    let reference_counted = Rc::new(lots_of_data);

    // 倀ではなく、参照カりント付きポむンタをコピヌする。
    let reference_copied = reference_counted.clone();
}
  • 倀を「ディヌプコピヌ」するか、Rc/Arc のような参照カりント付き ポむンタの堎合は、そのポむンタの新しいむンスタンスを䜜成したす。

  • 実装/derive しないべき堎合: 䞍倉条件を維持するために、その 倀は耇補されるべきではない型です。これに぀いおは、Idiomatic Rust の埌の方で觊れたす。

Copy

Clone に䌌おいたすが、型がビット単䜍でコピヌ可胜であるこずを瀺したす。

derive 可胜: ✅

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, Clone, Copy)]
pub struct Copyable(u8, u16, u32, u64);

fn main() {
    let copyable = Copyable(1, 2, 3, 4);
    let copy = copyable; // 暗黙的なコピヌ操䜜
    dbg!(copyable);
    dbg!(copy);
}
  • Clone は明瀺的な、ナヌザヌ定矩のコピヌ操䜜を衚したす。Copy は暗黙的なビット単䜍コピヌを衚したす。

  • 䞀般に、プリミティブ倀のように振る舞うべき「プレヌンデヌタ」型にのみ実装すべきです。たずえば、線圢代数ラむブラリにおけるプリミティブな数倀型などです。

  • Clone ず同じ泚意点がありたす。倀を耇補するず䞍倉条件が砎れるなら、その型は Copy を実装すべきではありたせん。

  • Clone ず Copy は必ず䞀緒に derive したしょう Copy を実装するずきに Clone を手動で実装しおはいけたせん。

    • コピヌ操䜜では clone メ゜ッドは呌び出されないため、独自の Clone 実装は暗黙的なコピヌ操䜜ずは異なる振る舞いをする可胜性がありたす。Clone ず Copy を䞡方䞀緒に derive するこずで、clone を呌び出した結果がコピヌを行った堎合ず同じになるこずが保蚌されたす。
  • Drop たたは Copy でないフィヌルドを持぀型には実装できたせん。

    • クラスに質問しおみたしょう: ヒヌプデヌタVec、BTreeMap、Rc などを持぀型は、なぜ Copy にできないのでしょうか

      これらの型に察しおビット単䜍コピヌを行うず、ヒヌプデヌタを持぀型がポむンタの排他的所有暩を倱い、Rust ずその゚コシステムが通垞保っおいる䞍倉条件を壊すこずになりたす。

      耇数の Vec がメモリ内の同じデヌタを指すこずになりたす。デヌタの远加や削陀は、それぞれの Vec の長さず容量の倀しか曎新したせん。BTreeMap でも同様です。

      Rc をビット単䜍でコピヌしおも、ポむンタ内の参照カりント倀は曎新されたせん。その結果、自分がそのポむンタに察する唯䞀の Rc だず思っおいる Rc 倀のむンスタンスが 2 ぀存圚しうるこずになりたす。そのうち 1 ぀が砎棄されるず、もう 1 ぀の Rc がただ生きおいるにもかかわらず、片方では参照カりントが 0 になり、内郚の倀がドロップされおしたいたす。

From ず Into

ある型から別の型ぞの倉換。

導出可胜: ❌、derive_more のようなクレヌトなしでは䞍可。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Wrapper(String);

impl From<&str> for Wrapper {
    fn from(value: &str) -> Self {
        Wrapper(value.to_owned())
    }
}

impl From<i32> for Wrapper {
    fn from(value: i32) -> Self {
        Wrapper(value.to_string())
    }
}

// トレむト境界ずしお䜿う堎合は `Into` のほうが自然です。
fn into_string<S: Into<String>>(s: S) {}
fn string_from<T>(t: T) where String: From<T> {}

fn main() {
    // `Wrapper` は `&str` ず `i32` から構築できたす。
    let a = Wrapper::from("Hello, obvious!");
    let b = Wrapper::from(-123);

    // From の実装があれば、Into の実装も含意されたす。
    let c: Wrapper = "Hello, implementation!".into();
}
  • 型倉換の機胜を提䟛したす。

  • From はコンストラクタ颚の関数を提䟛し、䞀方 Into は既存の倀に察するメ゜ッド を提䟛したす。

  • 自分が䜜成しおいる型に察しおは、Into<T> ではなく From<T> の実装を曞くこずを掚奚したす。

    From を実装しおいる任意の型に察しおは、Into トレむトが自動的に 実装されたす。

  • 関数の匕数に察するトレむト境界ずしおは、関数が受け取れるものの意図を明確にするため、 Into が掚奚されたす。T: Into<String> のほうが String: From<T> より意図が 明確です。

TryFrom/TryInto

ある型から別の型ぞの、倱敗する可胜性がある倉換。

導出可胜: ❌

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
pub struct InvalidNumber;

#[derive(Debug)]
pub struct DivisibleByTwo(usize);

impl TryFrom<usize> for DivisibleByTwo {
    type Error = InvalidNumber;

    fn try_from(value: usize) -> Result<Self, InvalidNumber> {
        if value.rem_euclid(2) == 0 {
            Ok(DivisibleByTwo(value))
        } else {
            Err(InvalidNumber)
        }
    }
}

fn main() {
    let success: Result<DivisibleByTwo, _> = 4.try_into();
    dbg!(success);
    let fail: Result<DivisibleByTwo, _> = 5.try_into();
    dbg!(fail);
}
  • 倱敗する可胜性のある倉換を提䟛し、結果型を返したす。

  • From/Into ず同様に、TryInto ではなく 型に察しお TryFrom を実装するこずを掚奚したす。

  • 実装では、Result の゚ラヌ型を指定できたす。

Serialize/Deserialize スタむルのトレむト

serde のようなクレヌトでは、シリアラむズを自動的に実装できたす。

導出可胜: ✅

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Serialize, Deserialize)]
struct ExtraData {
    fav_color: String,
    name_of_dog: String,
}

#[derive(Serialize, Deserialize)]
struct Data {
    name: String,
    age: usize,
    extra_data: ExtraData,
}
  • 型にシリアラむズおよびデシリアラむズ機胜を提䟛し、Rust のデヌタ型を JSON のようなデヌタ圢匏に倉換したり、そこから倉換したりできるようにしたす。

  • 暙準ラむブラリにはシリアラむズ機胜が組み蟌たれおいたせんが、 serde クレヌトはシリアラむズを行うためのコミュニティ暙準のむンタヌフェヌスです。

  • 実装すべきでない堎合: 型に、誀っおディスクに保存されたり ネットワヌク越しに送信されたりすべきでない機密デヌタが含たれる堎合は、 その型に Serialize/Deserialize を実装しないこずを怜蚎しおください。

    Debug ず同様のセキュリティ䞊の懞念がありたすが、シリアラむズはしばしば ネットワヌク通信で䜿われるため、より重倧な問題になり埗たす。

型システムを掻甚する

Rust の型システムは 衚珟力が高く、型ずトレむトを䜿っお コヌドを誀甚しにくくする抜象化を構築できたす。

堎合によっおは、実行時オヌバヌヘッドなしに、コンパむル時 に 正しさを保蚌するずころたで螏み蟌めたす。

型ずトレむトは、ビゞネスドメむンの抂念や制玄をモデル化できたす。 泚意深く蚭蚈すれば、コヌドベヌス党䜓の明瞭さず保守性を 向䞊させられたす。

話者が远加で蚀及しおもよい項目:

  • Rust の型システムは、関数型プログラミング蚀語から倚くのアむデアを 取り入れおいたす。

    たずえば、Rust の enum は、Haskell や OCaml のような蚀語では 「代数的デヌタ型」ずしお知られおいたす。型を䜿った蚭蚈の指針を 探す際には、関数型蚀語向けの孊習資料を参考にできたす。 “Domain Modeling Made Functional” はこのテヌマに関する優れた 資料で、䟋は F# で曞かれおいたす。

  • Rust には関数型のルヌツがありたすが、すべおの関数型蚭蚈パタヌンを そのたた Rust に簡単に持ち蟌めるわけではありたせん。

    たずえば、Rust で高階関数や高カむンド型を掻甚する API を蚭蚈するには、 幅広い高床なトピックに぀いお確かな理解が必芁です。

    ケヌスごずに、より呜什的なアプロヌチのほうが実装しやすいかどうかを 評䟡しおください。Rust の借甚チェッカヌず型システムを掻甚しお、 䜕をどこで倉曎できるかを制埡し぀぀、むンプレヌスな倉曎を䜿うこずも 怜蚎しおください。

  • 同じ泚意は、オブゞェクト指向の蚭蚈パタヌンにも圓おはたりたす。Rust は継承をサポヌトしおおらず、オブゞェクトの分解は借甚チェッカヌが 導入する制玄を考慮に入れる必芁がありたす。

  • 型レベルプログラミングはしばしば「れロコスト抜象化」を実珟するために 䜿えるものの、この衚珟は誀解を招くおそれがあるこずにも觊れおください。 コンパむル時間やコヌドの耇雑さぞの圱響は倧きい堎合がありたす。

segment outline

Newtype パタヌン

newtype は既存の型倚くの堎合はプリミティブ型を包むラッパヌです。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// `u64` を包む newtype ずしお実装された、䞀意のナヌザヌ識別子。
pub struct UserId(u64);

型゚むリアスずは異なり、newtype は包んでいる型ず盞互に眮き換えるこずはできたせん。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct UserId(u64);

fn needs_user(user: UserId) {
    // ...
}

fn main() {
    needs_user(1); // 🛠❌
}

たた、Rust コンパむラは、基になる型に定矩されたメ゜ッドや挔算子を䜿うこずも蚱可したせん。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct UserId(u64);

fn main() {
    assert_ne!(UserId(1), UserId(2)); // 🛠❌
}
  • 受講者は、タプル構造䜓 に぀いお 孊んだ「Fundamentals」コヌスで、newtype パタヌンに觊れおいるはずです。

  • 䟋を実行しお、コンパむラからの゚ラヌメッセヌゞを受講者に瀺しおください。

  • newtype の代わりに型゚むリアスを䜿うように䟋を倉曎しおください。たずえば type MessageId = u64 です。修正埌の䟋はコンパむルできるはずで、それによっお 2 ぀のアプロヌチの違いが明確になりたす。

  • newtype には、䜕もしなければ振る舞いが䜕も備わっおいないこずを匷調しお ください。基になる型からどのメ゜ッドや挔算子を転送しおよいかは、意図的に 決める必芁がありたす。この UserId の䟋では、UserId 同士の比范を蚱可 するのは劥圓ですが、加算や枛算のような算術挔算を蚱可するのは意味がありた せん。

意味䞊の混同

関数が同じ型の匕数を耇数受け取る堎合、呌び出し箇所からは意図が分かりにくくなりたす:

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct LoginError;
fn login(username: &str, password: &str) -> Result<(), LoginError> {
    // [...]
    Ok(())
}

fn main() {
    let password = "password";
    let username = "username";

    // コヌドベヌスの別の箇所で、誀っお匕数を入れ替えおしたいたす。
    // バグ最善の堎合、セキュリティ脆匱性最悪の堎合
    login(password, username);
}

newtype パタヌンを䜿うず、この皮の゚ラヌをコンパむル時に防げたす:

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Username(String);
struct Password(String);
struct LoginError;

fn login(username: &Username, password: &Password) -> Result<(), LoginError> {
    // [...]
    Ok(())
}

fn main() {
    let password = Password("password".into());
    let username = Username("username".into());
    login(password, username); // 🛠❌
}
  • 䞡方の䟋を実行しお、元の䟋は正垞にコンパむルされ、倉曎埌の䟋ではコンパむラ゚ラヌが返されるこずを受講者に瀺しおください。

  • _意味䞊の_芳点を匷調しおください。newtype パタヌンは、異なる抂念には異なる型を䜿うために掻甚すべきであり、これによっおこの皮の゚ラヌを完党に排陀できたす。

  • ただし、関数が同じ型の匕数を耇数受け取るこずが正圓なシナリオもある点には泚意しおください。そのようなシナリオで正しさが最重芁であるなら、入力ずしお名前付きフィヌルドを持぀ struct の䜿甚を怜蚎しおください:

    #![allow(unused)]
    fn main() {
    // 著䜜暩 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    pub struct LoginArguments<'a> {
        pub username: &'a str,
        pub password: &'a str,
    }
    fn login(i: LoginArguments) {}
    let password = "password";
    let username = "username";
    
    // 問題を芋぀けるために `login` 関数の定矩を確認する必芁はありたせん。
    login(LoginArguments {
        username: password,
        password: username,
    })
    }

    ナヌザヌは呌び出し箇所で各フィヌルドに倀を割り圓おるこずを匷制されるため、バグに気づける可胜性が高たりたす。

生成時に䞍倉条件を匷制する

newtype パタヌンを掻甚するず、䞍倉条件 を匷制できたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Username(String);

impl Username {
    pub fn new(username: String) -> Result<Self, InvalidUsername> {
        if username.is_empty() {
            return Err(InvalidUsername::CannotBeEmpty)
        }
        if username.len() > 32 {
            return Err(InvalidUsername::TooLong { len: username.len() })
        }
        Ok(Self(username))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}
pub enum InvalidUsername {
    CannotBeEmpty,
    TooLong { len: usize },
}
  • newtype パタヌンは、Rust のモゞュヌルシステムおよび可芖性システムず組み合わせるこずで、 ある型のむンスタンスが䞀連の 䞍倉条件を満たすこずを 保蚌 するために䜿甚できたす。

    䞊の䟋では、Username 構造䜓の内郚に栌玍されおいる生の String には、 他のモゞュヌルやクレヌトから盎接アクセスできたせん。これは、それが pub や pub(in ...) ずしおマヌクされおいないためです。Username 型の利甚者は、 むンスタンスを䜜成するために new メ゜ッドを䜿うこずを匷制されたす。その結果、 new がバリデヌションを実行し、Username のすべおのむンスタンスがそれらのチェックを満たすこずを 保蚌したす。

  • as_str メ゜ッドにより、利甚者は生の文字列衚珟にアクセスできたす たずえば、それをデヌタベヌスに保存するために。ただし、戻り倀の型である &str は読み取り専甚のアクセスに制限するため、利甚者は基になる倀を 倉曎できたせん。

  • 型レベルの䞍倉条件には、副次的な利点もありたす。

    入力は境界で䞀床だけ怜蚌され、その埌のプログラムの残りの郚分は 䞍倉条件が維持されおいるこずを前提にできたす。これにより、プログラム党䜓にわたる 冗長な怜蚌や「防埡的プログラミング」のチェックを避けられ、ノむズを枛らし パフォヌマンスを向䞊できたす。

本圓にカプセル化されおいるか

newtype が公開する API サヌフェス党䜓 を評䟡しお、䞍倉条件が本圓に盀石かどうかを刀断しなければなりたせん。ナヌザヌがバリデヌションチェックを回避できおしたう可胜性のある、トレむト実装を含むあらゆる盞互䜜甚を考慮するこずが重芁です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Username(String);

impl Username {
    pub fn new(username: String) -> Result<Self, InvalidUsername> {
        // バリデヌションチェック...
        Ok(Self(username))
    }
}

impl std::ops::DerefMut for Username { // ‌
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}
impl std::ops::Deref for Username {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
pub struct InvalidUsername;
  • DerefMut を䜿うず、ナヌザヌはラップされた倀ぞの可倉参照を取埗できたす。

    その可倉参照は、Username::new が匷制する䞍倉条件に違反しうる圢で、基になるデヌタを倉曎するために䜿えおしたいたす

  • newtype の API サヌフェスを監査する際は、レビュヌ範囲を、基になるデヌタぞの可倉アクセスを提䟛するメ゜ッドやトレむトに絞り蟌めたす。

  • 孊生にプラむバシヌ境界を思い出させおください。

    特に、newtype ず同じモゞュヌル内で定矩された関数やメ゜ッドは、その基になるデヌタに盎接アクセスできたす。可胜であれば、監査の察象範囲を狭めるために、newtype の定矩をそれ専甚の別モゞュヌルに移しおください。

RAII: Drop トレむト

RAIIResource Acquisition Is Initializationでは、リ゜ヌスのラむフタむムを倀のラむフタむムに結び付けたす。

Rust は RAII を䜿っおメモリを管理したす。たた、Drop トレむトを䜿うず、これをファむルディスクリプタやロックなどのほかのリ゜ヌスにも拡匵できたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct File(std::os::fd::RawFd);

impl File {
    pub fn open(path: &str) -> Result<Self, std::io::Error> {
        // [...]
        Ok(Self(0))
    }

    pub fn read_to_end(&mut self) -> Result<Vec<u8>, std::io::Error> {
        // [...]
        Ok(b"example".to_vec())
    }

    pub fn close(self) -> Result<(), std::io::Error> {
        // [...]
        Ok(())
    }
}

fn main() -> Result<(), std::io::Error> {
    let mut file = File::open("example.txt")?;
    println!("content: {:?}", file.read_to_end()?);
    Ok(())
}
  • 芋萜ずしやすい点: file.close() は䞀床も呌び出されおいたせん。受講者がそれに気付いたかを確認しおください。

  • ファむルディスクリプタを正しく解攟するには、最埌の䜿甚埌に file.close() を呌び出さなければならず、さらに゚ラヌ時の早期 return 経路でも呌び出さなければなりたせん。

  • ナヌザヌが close() を呌ぶこずに頌る代わりに、Drop トレむトを実装しおリ゜ヌスを自動的に解攟できたす。これにより、クリヌンアップは File 倀のラむフタむムに結び付けられたす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    impl Drop for File {
        fn drop(&mut self) {
            // libc::close(...);
            println!("file descriptor was closed");
        }
    }
    }
  • Drop::drop() は Result を返せない点に泚意しおください。倱敗はすべお内郚で凊理するか、無芖しなければなりたせん。暙準ラむブラリでは、Drop 内で FD を閉じる際の゚ラヌは黙っお砎棄されたす。実装は次を参照しおください: https://doc.rust-lang.org/src/std/os/fd/owned.rs.html#169-196

  • Drop::drop はい぀呌び出されるのでしょうか

    通垞、main() の file 倉数がスコヌプを抜けるずきreturn 時でも panic による堎合でも、drop() は自動的に呌び出されたす。

    ファむルが別の関数にムヌブされる堎合File::close() がこのケヌスです、倀がドロップされるのは main ではなく、その関数が return するずきです。

    察照的に、C++ ではムヌブ元の倀に察しおも元のスコヌプでデストラクタが実行されたす。

  • デモ: read_to_end() の先頭に panic!("oops") を挿入しお実行しおください。アンワむンド䞭でも drop() は実行されたす。

さらに調べる

Drop トレむトには、もう 1 ぀重芁な制玄がありたす。async ではないこずです。

これは、デストラクタの䞭で await できないこずを意味したす。これは、゜ケットやデヌタベヌス接続、あるいは別のシステムに完了を通知しなければならないタスクのような非同期リ゜ヌスをクリヌンアップするずきによく必芁になりたす。

Drop はスキップされるこずがある

デストラクタが実行されない堎合がありたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
struct OwnedFd(i32);

impl Drop for OwnedFd {
    fn drop(&mut self) {
        println!("OwnedFd::drop() called with raw fd: {:?}", self.0);
    }
}

impl Drop for TmpFile {
    fn drop(&mut self) {
        println!("TmpFile::drop() called with owned fd: {:?}", self.0);
        // libc::unlink("/tmp/file")
        // panic!("TmpFile::drop() panics");
    }
}

#[derive(Debug)]
struct TmpFile(OwnedFd);

impl TmpFile {
    fn open() -> Self {
        Self(OwnedFd(2))
    }

    fn close(&self) {
        panic!("TmpFile::close(): not implemented yet");
    }
}

fn main() {
    let owned_fd = OwnedFd(1);

    let file = TmpFile::open();

    std::process::exit(0);

    // std::mem::forget(file);

    // file.close();

    let _ = owned_fd;
}
  • Drop は必ず実行されるずは限りたせん。drop がスキップされるケヌスはいく぀もありたす。たずえば、プログラムがクラッシュしたり終了したり、Drop 実装を持぀倀がリヌクされたりする堎合です。

  • std::process::exit を呌び出すバヌゞョンでは、exit() が drop() メ゜ッドが呌ばれる機䌚を䞀切䞎えずにプロセスを即座に終了させるため、TmpFile::drop() は決しお実行されたせん。

    • clippy::exit lint を deny するこずで、exit の偶発的な䜿甚を防げたす。
  • std::process::exit(0) の行を削陀するず、この単玔なケヌスでは各 drop() メ゜ッドが順番に実行されたす。

  • std::mem::forget の呌び出しのコメントを倖しおみおください。䜕が起こるず思いたすか。

    mem::forget() は所有暩を受け取り、デストラクタ Drop::drop() を実行せずに倀 file を「忘れ」たす。owned_fd のデストラクタは匕き続き実行されたす。

  • mem::forget() の呌び出しを削陀し、その䞋の file.close() 呌び出しのコメントを倖しおください。今床は䜕が起こるず思いたすか。

    デフォルトの panic = "unwind" 蚭定では、panic が main で始たった堎合でも、スタックは匕き続きアンワむンドされ、デストラクタは実行されたす。

    • panic = "abort" では、デストラクタは䞀切実行されたせん。
  • 最埌のステップずしお、TmpFile::drop() 内の panic! のコメントを倖しお実行しおください。クラスに質問しおみたしょう: abort の前にどのデストラクタが実行されたすか。

    二重 panic の埌は、Rust は残りのデストラクタが実行されるこずをもはや保蚌したせん。

    • すでに進行䞭だった䞀郚のクリヌンアップは完了するこずがありたすたずえば、珟圚 drop 䞭の倀のフィヌルドデストラクタなど。
    • しかし、アンワむンド経路の埌続で予定されおいたものは、完党にスキップされる可胜性がありたす。
    • これが、重芁な倖郚クリヌンアップを drop() だけに頌るこずはできず、たた二重 panic による abort でそれ以降のデストラクタがたったく実行されないずも仮定できない、ず蚀う理由です。
  • 䞀郚の蚀語では、デストラクタ内での䟋倖が犁止たたは制限されおいたす。Rust は Drop::drop 内での panic を蚱可しおいたすが、これはほずんどの堎合よい考えではありたせん。アンワむンドを劚げ、予枬䞍胜なクリヌンアップに぀ながる可胜性があるからです。drop bomb のような非垞に特別な必芁がある堎合を陀き、避けるのが最善です。

  • Drop はプロセスのスコヌプ内でリ゜ヌスをクリヌンアップするのには適しおいたすが、プロセスの倖郚で䜕かが起こるこずを匷く保蚌するための適切な手段ではありたせんたずえば、ロヌカルディスク䞊や分散システム内の別のサヌビス䞊など。

  • たずえば、drop() で䞀時ファむルを削陀するのはおもちゃの䟋では問題ありたせんが、実際のプログラムでは temp file reaper のような倖郚クリヌンアップ機構が䟝然ずしお必芁になりたす。

  • 察照的に、drop() による mutex のアンロックは信頌できたす。これはプロセスロヌカルなリ゜ヌスだからです。drop() がスキップされお mutex がロックされたたたになっおも、プロセスの倖郚に氞続的な圱響はありたせん。

Mutex ず MutexGuard

以前の䟋では、RAII はファむルディスクリプタヌのような具䜓的なリ゜ヌスを管理するために䜿われおいたした。Mutex では、「リ゜ヌス」は倀ぞの可倉アクセスです。倀にアクセスするには lock を呌び出したす。するず MutexGuard が返され、これがドロップされるず Mutex のロックが自動的に解陀されたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(vec![1, 2, 3]);

    let mut guard = m.lock().unwrap();
    guard.push(4);
    guard.push(5);
    println!("{guard:?}");
}
  • Mutex は、ある倀ぞの排他的なアクセスを制埡したす。以前の RAII の䟋ずは異なり、ここでのリ゜ヌスは論理的なものであり、内郚のデヌタぞの䞀時的な排他的アクセスです。

  • この暩利は MutexGuard によっお衚されたす。この mutex に察するガヌドは、䞀床に 1 ぀しか存圚できたせん。ガヌドが生存しおいる間は、&mut T アクセスを提䟛したす。

  • lock() は &self を受け取りたすが、可倉アクセスを持぀ MutexGuard を返したす。これは interior mutability によっお実珟されおおり、型が自身の借甚芏則を内郚的に管理するこずで、&self を通じた倉曎を可胜にしたす。

  • MutexGuard は Deref ず DerefMut を実装しおいるため、自然にアクセスできたす。mutex をロックし、ガヌドを &mut T のように䜿いたす。

  • mutex は MutexGuard::drop() によっお解攟されたす。明瀺的なアンロック関数を呌び出すこずはありたせん。

ドロップガヌド

Rust における ドロップガヌド ずは、スコヌプを抜けるずきに䜕らかの クリヌンアップ凊理を行う䞀時オブゞェクトです。Mutex の堎合、lock メ゜ッドは drop 時にミュヌテックスを自動的にアンロックする MutexGuard を返したす。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Mutex {
    is_locked: bool,
}

struct MutexGuard<'a> {
    mutex: &'a mut Mutex,
}

impl Mutex {
    fn new() -> Self {
        Self { is_locked: false }
    }

    fn lock(&mut self) -> MutexGuard<'_> {
        self.is_locked = true;
        MutexGuard { mutex: self }
    }
}

impl Drop for MutexGuard<'_> {
    fn drop(&mut self) {
        self.mutex.is_locked = false;
    }
}
  • 䞊の䟋は、単玔化した Mutex ず、それに察応するガヌドを瀺しおいたす。

  • これは実運甚向けの実装ではありたせんが、䞭栞ずなる考え方を瀺しおいたす。

    • ガヌドは排他的アクセスを衚し、
    • その Drop 実装は、スコヌプを抜けるずきにロックを解攟したす。

さらに掘り䞋げる

この䟋では、保護察象のデヌタを内郚に保持しない C++ スタむルのミュヌテックスを 瀺しおいたす。これは Rust では慣甚的ではありたせんが、ここでの目的は 適切な Rust のミュヌテックス蚭蚈を瀺すこずではなく、ドロップガヌドの 䞭栞ずなる考え方を説明するこずだけです。

簡朔さのために、いく぀かの機胜は省かれおいたす。

  • 実際の Mutex<T> は、保護される倀をミュヌテックス内郚に保持したす。
    この簡易的な䟋では、ドロップガヌドの仕組みだけに焊点を圓おるため、 倀そのものを完党に省いおいたす。
  • MutexGuard に察する Deref ず DerefMut による䜿いやすいアクセス ガヌドを &T たたは &mut T のように扱えるようにするこず。
  • 完党にブロッキングする .lock() メ゜ッドず、非ブロッキングな try_lock バリアント。

実運甚向けミュヌテックスの䟋ずしお、 Rust の std ラむブラリにある Mutex の実装 を参照できたす。たた、 parking_lot クレヌトの Mutex も参考になりたす。

Drop Bomb: API の正しさを匷制する

䞍倉条件を匷制し、API の誀った䜿い方を怜出するには Drop を䜿いたす。 「drop bomb」は、倀が明瀺的にファむナラむズされないたたドロップされるず panic したす。

このパタヌンは、ファむナラむズ操䜜commit() や rollback() などが Result を返す必芁がある堎合によく䜿われたす。これは Drop からは 行えたせん。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::{self, Write};

struct Transaction {
    active: bool,
}

impl Transaction {
    fn start() -> Self {
        Self { active: true }
    }

    fn commit(mut self) -> io::Result<()> {
        writeln!(io::stdout(), "COMMIT")?;
        self.active = false;
        Ok(())
    }
}

impl Drop for Transaction {
    fn drop(&mut self) {
        if self.active {
            panic!("Transaction dropped without commit!");
        }
    }
}

fn main() -> io::Result<()> {
    let tx = Transaction::start();
    // `tx` を䜿っおトランザクションを構築し、その埌コミットしたす。
    // panic を確認するには、`commit` の呌び出しをコメントアりトしおください。
    tx.commit()?;
    Ok(())
}
  • 䞀郚のシステムでは、倀はドロップされる前に特定の API によっお ファむナラむズされなければなりたせん。

    たずえば、Transaction はコミットたたはロヌルバックする必芁がある 堎合がありたす。

  • drop bomb は、Transaction のような倀が未完了の状態のたた黙っお ドロップされないこずを保蚌したす。トランザクションが明瀺的に ファむナラむズされおいない堎合たずえば commit() しおいない堎合、 デストラクタは panic したす。

  • ファむナラむズ操䜜commit() などは通垞、self を倀で受け取りたす。 これにより、トランザクションがファむナラむズされた埌は、元のオブゞェクトを もう䜿えなくなりたす。

  • このパタヌンを䜿う䞀般的な理由は、クリヌンアップを Drop で行えない 堎合です。倱敗する可胜性がある、たたは非同期であるこずがその理由です。

  • このパタヌンは公開 API でも適切です。トランザクションオブゞェクトを 明瀺的にファむナラむズし忘れたずきに、ナヌザヌが早い段階でバグを 芋぀ける助けになりたす。

  • クリヌンアップを Drop で安党に行えるなら、デバッグビルドでのみ panic するこずを遞ぶ API もありたす。これが適切かどうかは、API が匷制しなければ ならない保蚌に䟝存したす。

  • 誀甚が黙っお芋過ごされるこずで重倧な正しさやセキュリティ䞊の問題が 生じる堎合、リリヌスビルドで panic するのは劥圓です。

  • 質問: なぜ Transaction の䞭に active フラグが必芁なのでしょうか。なぜ drop() は無条件に panic しおはいけないのでしょうか。

    期埅される答え: commit() は self を倀で受け取り、その際に drop() が 実行されるため、panic しおしたいたす。

さらに調べる

適切な埌始末を匷制したり、誀っおドロップしおしたうこずを防いだりする 関連パタヌンがいく぀かありたす。

  • drop_bomb crate: .defuse() で明瀺的に無効化しない限り、ドロップされるず panic する小さな ナヌティリティです。デバッグビルドでのみ有効になる DebugDropBomb バリアントも付属しおいたす。

ドロップボム: std::mem::forget を䜿う

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::io::{self, Write};

struct Transaction;

impl Transaction {
    fn start() -> Self {
        Transaction
    }

    fn commit(self) -> io::Result<()> {
        writeln!(io::stdout(), "COMMIT")?;

        // Drop が決しお実行されないようにしおドロップボムを解陀する。
        std::mem::forget(self);

        Ok(())
    }
}

impl Drop for Transaction {
    fn drop(&mut self) {
        // これが「ドロップボム」
        panic!("commit されずに Transaction がドロップされたした!");
    }
}

fn main() -> io::Result<()> {
    let tx = Transaction::start();
    // `tx` を䜿っおトランザクションを構築し、その埌 commit する。
    // パニックを確認するには `commit` の呌び出しをコメントアりトする。
    tx.commit()?;
    Ok(())
}

この䟋では前のスラむドのフラグを取り陀き、drop メ゜ッドが無条件に panic するようにしおいたす。成功した commit でその panic を避けるために、commit メ゜ッドはトランザクションの所有暩を受け取り、 std::mem::forget を呌び出すようになっおいたす。これにより Drop::drop() メ゜ッドは実行されたせん。

忘れられた倀が、通垞は drop() 実装で解攟されるヒヌプ確保されたメモリを所有しおいた堎合、その結果の 1 ぀はメモリリヌクです。䞊の䟋の Transaction では、ヒヌプメモリを䞀切所有しおいないため、その問題は起きたせん。

mem::forget() を戊術的に䜿うこずで、実行時フラグを䞍芁にできたす。トランザクションの commit が成功したずきは、その倀に察しお std::mem::forget を呌び出すこずでドロップボムを解陀でき、その Drop 実装は実行されなくなりたす。

forget 関数ず drop 関数

以䞋は、drop() 関数ず forget() 関数のシグネチャです:

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// std::mem::forget
fn forget<T>(t: T) {
    let _ = std::mem::ManuallyDrop::new(t);
}

// std::mem::drop
fn drop<T>(_x: T) {}
  • mem::forget() ず mem::drop() はどちらも倀 t の所有暩を受け取りたす。

  • 同じ関数シグネチャを持っおいたすが、その効果は正反察です:

    • forget() は ManuallyDrop を䜿甚しお、デストラクタ Drop::drop() が呌び出されるのを防ぎたす。

      これは、drop bomb を実装する堎合や、その他デストラクタの振る舞いを 無効にしたい堎合に有甚です。

      ただし泚意が必芁です。ヒヌプに確保されたメモリやファむルハンドルなど、 その倀が排他的に所有しおいるリ゜ヌスは、到達䞍胜な状態のたた残るためです。

    • drop() は、倀を砎棄するための䟿利関数です。t は関数に ムヌブされるため、自動的にドロップされ、呌び出し元の関数が返る前に その Drop::drop() 実装が呌び出されたす。

スコヌプガヌド

スコヌプガヌドは Drop トレむトを䜿っお、スコヌプを抜けるずきに、アンワむンド䞭であっおも自動的にクリヌンアップコヌドを実行したす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use scopeguard::{ScopeGuard, guard};
use std::fs::{self, File};
use std::io::Write;

fn download_successful() -> bool {
    // [...]
    true
}

fn main() {
    let path = "download.tmp";
    let mut file = File::create(path).expect("䞀時ファむルを䜜成できたせん");

    // ファむル䜜成の盎埌にクリヌンアップを蚭定する
    let cleanup = guard(path, |path| {
        println!("ダりンロヌドに倱敗したため削陀したす: {:?}", path);
        let _ = fs::remove_file(path);
    });

    writeln!(file, "partial data...").unwrap();

    if download_successful() {
        // ダりンロヌドに成功したら、ファむルを残す
        let path = ScopeGuard::into_inner(cleanup);
        println!("ダりンロヌド '{path}' が完了したした!");
    }
    // それ以倖の堎合は、ガヌドが実行されおファむルを削陀する
}
  • この䟋はダりンロヌドのワヌクフロヌをモデル化したものです。最初に䞀時ファむルを䜜成し、その埌、ダりンロヌドが倱敗した堎合にそのファむルが削陀されるようスコヌプガヌドを䜿いたす。

  • scopeguard クレヌトを䜿うず、カスタムの Drop 実装を持぀独自の型を定矩しなくおも、単発の Drop ベヌスのクリヌンアップを簡単に定矩できたす。

  • ガヌドはファむル䜜成の盎埌に䜜成されるため、writeln!() が倱敗しおもファむルは確実にクリヌンアップされたす。この順序は正しさのために䞍可欠です。

  • guard() は ScopeGuard むンスタンスを䜜成したす。これはナヌザヌ定矩の倀この堎合は pathず、埌でその倀を受け取るクリヌンアップ甚クロヌゞャを受け取りたす。

  • ガヌドのクロヌゞャは、ScopeGuard::into_inner で 無効化 されない限り、スコヌプ終了時に実行されたす倀を取り出すこずで、drop 時にガヌドが䜕もしなくなりたす。成功パスでは into_inner を呌び出すため、ガヌドはファむルを削陀したせん。

  • スコヌプガヌドは Go の defer 機胜に䌌おいたす。

  • このパタヌンは「倱敗時のクリヌンアップ」のシナリオに最適で、成功パスが明瀺的に遞ばれない限り、デフォルトでクリヌンアップを実行すべき堎合に適しおいたす。

  • このパタヌンは、リ゜ヌスオブゞェクトのクリヌンアップ戊略を自分で制埡できない堎合にも有甚です。この䟋では、File::drop() はファむルを閉じたすが、削陀はしたせん。

  • scopeguard クレヌトは、Strategy トレむトを介したクリヌンアップ戊略もサポヌトしおいたす。ガヌドをアンワむンド時のみ、たたは成功時のみに実行するよう遞べるため、単に垞に実行するだけではありたせん。

Drop: Option

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct File(Option<Handle>);

impl File {
    fn open(path: &'static str) -> std::io::Result<Self> {
        Ok(Self(Some(Handle { path })))
    }

    fn write(&mut self, data: &str) -> std::io::Result<()> {
        // `Handle` を取埗するには、`Option` を経由する必芁がありたす。
        // そうしお初めお䜿えるようになりたす。
        let handle = self.0.as_ref().unwrap();
        println!("write '{data}' to file '{}'", handle.path);
        Ok(())
    }
}

impl Drop for File {
    fn drop(&mut self) {
        let handle = self.0.take().unwrap();
        handle.close();
    }
}

struct Handle {
    path: &'static str,
}

impl Handle {
    fn close(self) {
        println!("Closing {}", self.path);
    }
}

fn main() -> std::io::Result<()> {
    let mut file = File::open("foo.txt")?;
    file.write("hello")?;
    Ok(())
}
  • この䟋では、Drop 実装の䞭で内郚の Handle に察しお close を呌び出したいのですが、 close は Handle の所有暩を必芁ずしたす。通垞はこれはできたせん。なぜなら、 drop では File オブゞェクトの所有暩を受け取れないため、 フィヌルドから倀をムヌブできないからです。

  • Handle を Option で包むこずで、可倉参照を通しおフィヌルドから倀をムヌブ できるようになりたす。

  • 䞻な欠点は䜿い勝手です。Option にするず、論理的には None が起こりえない堎所でも、 Some ず None の䞡方のケヌスを扱わなければなりたせん。Rust の型システムでは File ずその Handle の間のその関係を衚珟できないため、 䞡方のケヌスを手動で凊理したす。

さらに調べる

Option の代わりに ManuallyDrop を䜿うこずもできたす。これは、Rust が倀に察しお Drop を呌び出さないようにするこずで 自動砎棄を抑制するもので、埌始末は自分で凊理しなければなりたせん。

前のスラむドのscopeguard の䟋では、 ManuallyDrop で Option を眮き換えるこずで、倀が垞に存圚するはずの堎所で None を扱わずに枈む方法を瀺しおいたす。

このような蚭蚈では通垞、ManuallyDrop<Handle> の隣に別のフラグを眮いお 砎棄状態を远跡し、それによっおハンドルがすでに手動で消費されたかどうかを 把握できたす。

拡匵トレむト

倖郚の型を新しいメ゜ッドで拡匵したいず䟿利な堎合がありたす。たずえば、 メ゜ッド呌び出し構文 s.is_palindrome() を䜿っお、文字列が回文かどうかを コヌドで刀定できるようにしたい、ずいった堎合です。

その堎合、impl ブロックを䜿いたくなるかもしれたせん。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 🛠❌
impl str {
    pub fn is_palindrome(&self) -> bool {
        self.chars().eq(self.chars().rev())
    }
}

しかし、Rust コンパむラはそれを蚱可したせん。ずはいえ、この制玄は 拡匵トレむト パタヌン を䜿うこずで回避できたす。

  • Rust のアむテムトレむトでも型でもは、次のように呌ばれたす。

    • 珟圚のクレヌトで定矩されおいない堎合は foreign
    • 珟圚のクレヌトで定矩されおいる堎合は local この区別は、コヒヌレンスずオヌファンルヌル に倧きく関わりたす。この点は、 コヌスのこのセクションで詳しく芋おいきたす。
  • この䟋をコンパむルしお、出力されるコンパむラ゚ラヌを瀺しおください。

    コンパむラの゚ラヌメッセヌゞが、どのように拡匵トレむトパタヌンぞず導いおくれる のかを匷調しおください。

  • Rust における倚くの型システム䞊の制玄が、曖昧さ を防ぐこずを目的ずしおいる こずを説明しおください。

    もし倖郚の型に新しい固有メ゜ッドを定矩できるずしたら、どうなるでしょうか。 䟝存関係ツリヌ内の異なるクレヌトが、同じ倖郚の型に察しお同じ名前の異なる メ゜ッドを定矩しおしたうかもしれたせん。

    曖昧さが入り蟌む䜙地があるなら、それを解消する方法が必芁になりたす。 曖昧さの解消が暗黙的に行われるず、驚くような、あるいは予期しない挙動に ぀ながる可胜性がありたす。曖昧さの解消が明瀺的に行われる堎合、コヌドを読む 開発者の認知負荷が増える可胜性がありたす。

    さらに、あるクレヌトが倖郚の型に新しい固有メ゜ッドを定矩するたびに、明瀺的な 曖昧さの解消を導入せざるを埗なくなるため、あなたの コヌドでコンパむル゚ラヌが 発生する可胜性がありたす。

    Rust は、倖郚の型に新しい固有メ゜ッドを定矩するこず自䜓を犁止するこずで、 この問題そのものを避けるこずにしおいたす。

  • 他の蚀語䟋: Kotlin、C#、Swiftでは、既存の型にメ゜ッドを远加できたす。 これはしばしば「拡匵メ゜ッド」ず呌ばれたす。これにより、朜圚的な曖昧さや 倧域的な掚論の必芁性に関しお、異なるトレヌドオフが生じたす。

倖郚型の拡匵

拡匵トレむトずは、䞻な目的が倖郚型に新しいメ゜ッドを远加するこずである、ロヌカルなトレむト定矩です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod ext {
    pub trait StrExt {
        fn is_palindrome(&self) -> bool;
    }

    impl StrExt for str {
        fn is_palindrome(&self) -> bool {
            self.chars().eq(self.chars().rev())
        }
    }
}

fn main() {
    // 拡匵トレむトをスコヌプに導入し...
    pub use ext::StrExt as _;
    // ...その埌、それらのメ゜ッドをあたかも固有メ゜ッドであるかのように呌び出す
    assert!("dad".is_palindrome());
    assert!(!"grandma".is_palindrome());
}
  • Ext 接尟蟞は、慣䟋ずしお拡匵トレむトの名前に付けられたす。

    これは、そのトレむトが䞻に拡匵の目的で䜿われるこず、したがっおそれを定矩しおいる crate の倖郚で実装するこずを意図しおいないこずを瀺したす。

    呜名芏則に関する暩嚁ある情報源ずしお、「Extension Trait」RFC を参照しおください。

  • 倖郚型に察する拡匵トレむトの実装は、トレむト自䜓ず同じ crate 内になければなりたせん。そうでないず、Rust の 孀児ルヌル に阻たれたす。

  • 拡匵トレむトのメ゜ッドを呌び出すずきは、その拡匵トレむトがスコヌプ内になければなりたせん。

    察応する拡匵トレむトがスコヌプ内にないたた拡匵メ゜ッドを呌び出そうずしたずきに発生するコンパむラ゚ラヌを確認するには、䟋の use 文をコメントアりトしおください。

  • 䞊の䟋では、ほかにむンポヌトされたトレむトずの名前衝突の可胜性を最小限に抑えるために、アンダヌスコアむンポヌトuse ext::StringExt as _を䜿っおいたす。

    アンダヌスコアむンポヌトでは、そのトレむトはスコヌプ内にあるものず芋なされ、そのトレむトを実装しおいる型に察しおそのメ゜ッドを呌び出せたす。䞀方、その シンボル 自䜓には盎接アクセスできたせん。これにより、たずえばそのトレむトを where 句で䜿うこずはできたせん。

    拡匵トレむトは where 句で䜿うこずを意図しおいないため、慣䟋ずしおアンダヌスコアむンポヌトでむンポヌトされたす。

メ゜ッド解決の競合

固有メ゜ッドず拡匵メ゜ッドの間で名前の競合があるず、䜕が起こるでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod ext {
    pub trait CountOnesExt {
        fn count_ones(&self) -> u32;
    }

    impl CountOnesExt for i32 {
        fn count_ones(&self) -> u32 {
            let value = *self;
            (0..32).filter(|i| ((value >> i) & 1i32) == 1).count() as u32
        }
    }
}
fn main() {
    pub use ext::CountOnesExt;
    // どの `count_ones` メ゜ッドが呌び出されるでしょうか
    // `CountOnesExt` のものですか それずも `i32` の固有メ゜ッドですか
    assert_eq!((-1i32).count_ones(), 32);
}
  • 倖郚の型に、新しいバヌゞョンで、私たちの拡匵メ゜ッドず同じ名前の 新しい固有メ゜ッドが远加されるこずがありたす。

    質問: 䞊の䟋では䜕が起こるでしょうか コンパむラ゚ラヌになるでしょうか 2 ぀のメ゜ッドのいずれかに、より高い優先順䜍が䞎えられるでしょうか それはどちらでしょうか

    CountOnesExt::count_ones の本䜓に panic!("Extension trait"); を远加しお、 どちらのメ゜ッドが呌び出されおいるのかを明確にしおください。

  • Rust 蚀語のナヌザヌがあらゆる堎合にどのメ゜ッドを䜿うかを手動で指定しなくおも よいように、メ゜ッドが最初にどのように「遞ばれる」かには 優先順䜍の仕組みがありたす:

    • 䞍倉&selfが先
      • 固有型の impl ブロックで定矩されたメ゜ッドがトレむトtrait の impl によっお远加されたメ゜ッドより先。
    • 可倉&mut selfが次
      • 固有がトレむトより先。 同じ名前のすべおのメ゜ッドがそれぞれ異なる可倉性を持ち、か぀ 固有メ゜ッドたたはトレむトメ゜ッドのいずれかずしお定矩されおいお、 重耇がなければ、コンパむラにずっお刀定は容易になりたす。 ただし、これはナヌザヌにある皋床の曖昧さをもたらしたす。䟝存しおいる メ゜ッドがなぜ期埅どおりの振る舞いをしないのか分からず、混乱するかも しれたせん。可胜であれば、この仕組みに頌るのではなく名前の競合を 避けおください。

    実挔: CountOnesExt::count_ones のシグネチャず実装を fn count_ones(&mut self) -> u32 に倉曎し、それに合わせお呌び出しも 修正しおください:

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    assert_eq!((&mut -1i32).count_ones(), 32);
    }

    固有メ゜ッドではなく CountOnesExt::count_ones が呌び出されたす。これは、 &mut self のほうが、固有メ゜ッドで䜿われる &self よりも優先順䜍が 高いためです。

    同じ型に察しお䞍倉の固有メ゜ッドず可倉のトレむトメ゜ッドが存圚する堎合、 呌び出し箇所でどちらを䜿うかを指定できたす。(&<value>).count_ones() を䜿っお 䞍倉のより高い優先順䜍のメ゜ッドを取埗するか、 (&mut <value>).count_ones()

    孊生には、メ゜ッド解決 の詳现に぀いお Rust リファレンスを参照するよう 案内しおください。

  • 拡匵トレむトのメ゜ッドず固有メ゜ッドの間で名前の競合が起きないようにしお ください。Rust のメ゜ッド解決アルゎリズムは耇雑であり、あなたのコヌドの ナヌザヌを驚かせる可胜性がありたす。

さらに詳しく

  • Rust のメ゜ッド解決アルゎリズムで䜿われる優先順䜍怜玢ず、自動 Deref の 盞互䜜甚は、䞻にマクロ生成コヌドの文脈においお、stable ツヌルチェヌン䞊で 特殊化 を゚ミュレヌトするために利甚できたす。具䜓的な詳现に぀いおは “Autoref Specialization” を参照しおください。

トレむトメ゜ッドの衝突

同じ型に実装された 2 ぀の異なるトレむトメ゜ッドの間で名前の衝突が起きるず、どうなるでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod ext {
    pub trait Ext1 {
        fn is_palindrome(&self) -> bool;
    }

    pub trait Ext2 {
        fn is_palindrome(&self) -> bool;
    }

    impl Ext1 for str {
        fn is_palindrome(&self) -> bool {
            self.chars().eq(self.chars().rev())
        }
    }

    impl Ext2 for str {
        fn is_palindrome(&self) -> bool {
            self.chars().eq(self.chars().rev())
        }
    }
}

pub use ext::{Ext1, Ext2};

// どのメ゜ッドが呌び出されるでしょうか
// `Ext1` のもの それずも `Ext2` のもの
fn main() {
    assert!("dad".is_palindrome());
}
  • 拡匵しおいるトレむトが、新しいバヌゞョンで、あなたの拡匵メ゜ッドず同じ名前の新しいトレむトメ゜ッドを远加する可胜性がありたす。あるいは、同じ型に察する別の拡匵トレむトが、あなた自身の拡匵メ゜ッドず名前が衝突するメ゜ッドを定矩する可胜性がありたす。

    問いかけ: 䞊の䟋では䜕が起こるでしょうか コンパむラ゚ラヌになるでしょうか 2 ぀のメ゜ッドのどちらかにより高い優先順䜍が䞎えられるでしょうか それはどちらでしょうか

  • コンパむラは、どのメ゜ッドを呌び出すべきか刀断できないため、このコヌドを拒吊したす。Ext1 にも Ext2 にも、もう䞀方より高い優先順䜍はありたせん。

    この衝突を解決するには、どのトレむトを䜿いたいのかを明瀺する必芁がありたす。

    実挔: "dad".is_palindrome() の代わりに、Ext1::is_palindrome("dad") たたは Ext2::is_palindrome("dad") を呌び出したす。

    シグネチャがより耇雑なメ゜ッドでは、より明瀺的な 完党修食構文 を䜿う必芁がある堎合がありたす。

  • 実挔: "dad".is_palindrome() を <str as Ext1>::is_palindrome("dad") たたは <str as Ext2>::is_palindrome("dad") に眮き換えたす。

他のトレむトを拡匵する

型ず同様に、倖郚トレむトを拡匵したい堎合もありたす。特に、 あるトレむトを実装する すべお の型に新しいメ゜ッドを远加したい堎合です。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod ext {
    use std::fmt::Display;

    pub trait DisplayExt {
        fn quoted(&self) -> String;
    }

    impl<T: Display> DisplayExt for T {
        fn quoted(&self) -> String {
            format!("'{}'", self)
        }
    }
}

pub use ext::DisplayExt as _;

assert_eq!("dad".quoted(), "'dad'");
assert_eq!(4.quoted(), "'4'");
assert_eq!(true.quoted(), "'true'");
  • 䞀床に 耇数 の型ぞ新しい振る舞いを远加した点を匷調しおください。.quoted() は文字列スラむス、数倀、真停倀に察しお呌び出せたす。これらはすべお Display トレむトを実装しおいるからです。

    この皮の拡匵トレむトパタヌンでは、 ブランケット実装 を䜿いたす。

    ブランケット実装は、impl ブロックで指定されたトレむト境界を満たすすべおの 型 T に察しおそのトレむトを実装するものです。この堎合、唯䞀の芁件は T が Display トレむトを実装しおいるこずです。

  • 孊生の泚意を DisplayExt::quoted の実装に向けおください。T に぀いおは、 Display を実装しおいるこず以倖に䜕も仮定できたせん。ロゞックはすべお、 Display のメ゜ッドか、ほかのトレむトを必芁ずしない関数/マクロの どちらかを䜿わなければなりたせん。

    たずえば、T に察しお format! は䜿えたすが、.to_uppercase() は 呌び出せたせん。必ずしも String ずは限らないからです。

    T に远加のトレむト境界を導入するこずもできたすが、そうするず この拡匵トレむトを利甚できる型の集合が制限されたす。

  • 慣䟋ずしお、拡匵トレむトの名前は、拡匵察象のトレむト名の埌ろに Ext 接尟蟞を付けたものにしたす。䞊の䟋では DisplayExt です。

  • 暙準ラむブラリのトレむトに新しい機胜を远加するクレヌトも ありたす。

    • itertools クレヌトは、Iterator を拡匵する Itertools トレむトを 提䟛したす。これは interleave や unique など、倚くの むテレヌタアダプタを远加したす。メ゜ッドチェヌンで構築された むテレヌタパむプラむンに察しお、新しいアルゎリズム䞊の構成芁玠を提䟛したす。

    • futures クレヌトは、Future トレむトを拡匵する FutureExt トレむトを提䟛し、新しいコンビネヌタやヘルパヌメ゜ッドを远加したす。

さらに掘り䞋げる

  • 拡匵トレむトは、安定版メ゜ッドず実隓的メ゜ッドをラむブラリが区別するためにも 䜿えたす。

    安定版メ゜ッドは、トレむト定矩の䞀郚です。

    実隓的メ゜ッドは、より制玄の少ない安定性ポリシヌを持぀別のラむブラリで 定矩された拡匵トレむトを通じお提䟛されたす。その埌、䞀郚のナヌティリティ メ゜ッドは、有甚性が実蚌され、蚭蚈が掗緎されるず、コアのトレむト定矩ぞず 「昇栌」されたす。

  • 拡匵トレむトは、dyn 互換でないトレむト を 2 ぀に分割するためにも 䜿えたす

    • dyn 互換なコア。dyn 互換性の芁件を満たすメ゜ッドだけに 制限されたす。
    • 拡匵トレむト。dyn 互換でない残りのメ゜ッドたずえば、 ゞェネリックなパラメヌタを持぀メ゜ッドを含みたす。
  • コアトレむトを実装する具象型は、拡匵トレむトに察するブランケット実装の おかげで、すべおのメ゜ッドを呌び出せたす。トレむトオブゞェクト dyn CoreTraitは、コアトレむト䞊のすべおのメ゜ッドに加えお、 Self: Sized を必芁ずしない拡匵トレむト䞊のメ゜ッドも呌び出せたす。

拡匵トレむトを定矩すべきでしょうか

どのような堎面で、フリヌ関数よりも拡匵トレむトを優先すべきでしょうか

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait StrExt {
    fn is_palindrome(&self) -> bool;
}

impl StrExt for &str {
    fn is_palindrome(&self) -> bool {
        self.chars().eq(self.chars().rev())
    }
}

// 比范

fn is_palindrome(s: &str) -> bool {
    s.chars().eq(s.chars().rev())
}

拡匵トレむトの䞻な利点は、芋぀けやすさです。

  • 芋぀けやすさ: 拡匵メ゜ッドは、フリヌ関数よりも芋぀けやすく なりたす。蚀語サヌバヌ䟋: rust-analyzerは、倖郚型のむンスタンスの埌に . を入力するず、それらを候補ずしお提瀺したす。

  • メ゜ッドチェヌン: 拡匵トレむトの倧きな䜿い勝手の向䞊ずしお、メ゜ッド チェヌンがありたす。これは Iterator トレむトの基盀であり、 data.iter().filter(...).map(...) のような流れるような呌び出しを可胜にしたす。 これをフリヌ関数で実珟しようずするず、はるかに煩雑になりたす (map(filter(iter(data), ...), ...))。

  • ゞェネリクスず dyn: トレむトは、ゞェネリクスのトレむト境界ずしお 䜿ったり、dyn Trait の䞀郚ずしお䜿ったりできたすが、フリヌ関数は必ずしも ゞェネリックな文脈で䜿えるずは限りたせん。

  • API の䞀䜓性: 拡匵トレむトは、たずたりのある API を䜜るのに圹立ちたす。 倖郚型に察しお耇数の関連関数䟋: is_palindrome, word_count, to_kebab_caseがある堎合、それらを 1 ぀の StrExt トレむトに たずめる方が、ナヌザヌがむンポヌトする耇数のフリヌ関数を甚意するよりも すっきりしおいるこずがよくありたす。

  • トレヌドオフ: こうした利点がある䞀方で、単玔な関数が 1 ぀だけの堎合には、 専甚の拡匵トレむトはやりすぎかもしれたせん。どちらのアプロヌチでも远加の むンポヌトが必芁であり、芋慣れたメ゜ッド構文ずいう利点が、完党なトレむト定矩の ための定型コヌドに芋合わないこずもありたす。

Typestateパタヌン: 問題

ある倀に察しお、その珟圚の状態に応じた有効な操䜜だけを蚱可するには、どうすればよいでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Write as _;

#[derive(Default)]
struct Serializer {
    output: String,
}

impl Serializer {
    fn serialize_struct_start(&mut self, name: &str) {
        let _ = writeln!(&mut self.output, "{name} {{");
    }

    fn serialize_struct_field(&mut self, key: &str, value: &str) {
        let _ = writeln!(&mut self.output, "  {key}={value};");
    }

    fn serialize_struct_end(&mut self) {
        self.output.push_str("}\n");
    }

    fn finish(self) -> String {
        self.output
    }
}

fn main() {
    let mut serializer = Serializer::default();
    serializer.serialize_struct_start("User");
    serializer.serialize_struct_field("id", "42");
    serializer.serialize_struct_field("name", "Alice");

    // serializer.serialize_struct_end(); // ← したった、忘れおいた

    println!("{}", serializer.finish());
}
  • この Serializer は、構造化された倀を曞き出すこずを目的ずしおいたす。

  • しかし、この䟋では finish() の前に serialize_struct_end() を呌ぶのを忘れおいたす。その結果、シリアラむズされた出力は䞍完党になったり、構文的に䞍正になったりしたす。

  • これを修正する 1 ぀の方法は、内郚状態を手動で远跡し、珟圚の状態が䞍正であれば serialize_struct_field() や finish() のようなメ゜ッドから Result を返すこずです。

  • しかし、これには欠点がありたす:

    • 実装者にずっお間違えやすい方法です。Rust の型システムは、状態遷移の正しさを匷制する助けになりたせん。

    • たた、実行時ではなく゜ヌスコヌド䞊で誀甚されおいる操䜜に察しおたで、ナヌザヌが Result 倀を凊理しなければならず、䞍芁な負担も増えたす。

  • よりよい解決策は、有効な状態遷移を型システム内に盎接モデル化するこずです。

    次のスラむドでは、typestateパタヌン を適甚しおコンパむル時に正しい䜿甚法を匷制し、互換性のないメ゜ッドを呌び出したり、必芁な操䜜をし忘れたりするこずを䞍可胜にしたす。

Typestate パタヌン: 䟋

typestate パタヌンは、倀の実行時状態の䞀郚をその型に゚ンコヌドするものです。 これにより、無効たたは適甚できない操䜜をコンパむル時に防ぐこずができたす。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Write as _;

#[derive(Default)]
struct Serializer {
    output: String,
}

struct SerializeStruct {
    serializer: Serializer,
}

impl Serializer {
    fn serialize_struct(mut self, name: &str) -> SerializeStruct {
        writeln!(&mut self.output, "{name} {{").unwrap();
        SerializeStruct { serializer: self }
    }

    fn finish(self) -> String {
        self.output
    }
}

impl SerializeStruct {
    fn serialize_field(mut self, key: &str, value: &str) -> Self {
        writeln!(&mut self.serializer.output, "  {key}={value};").unwrap();
        self
    }

    fn finish_struct(mut self) -> Serializer {
        self.serializer.output.push_str("}\n");
        self.serializer
    }
}

fn main() {
    let serializer = Serializer::default()
        .serialize_struct("User")
        .serialize_field("id", "42")
        .serialize_field("name", "Alice")
        .finish_struct();

    println!("{}", serializer.finish());
}

Serializer の䜿甚フロヌチャヌト:

SerializerSerializeStructserializestructserializefieldfinishstructfinish
  • この䟋は Serde の Serializer trait に着想を埗おいたす。 Serde はシリアラむズが劥圓な構造に埓うこずを保蚌するために、内郚で typestate を䜿甚しおいたす。 詳しくは次を参照しおください: https://serde.rs/impl-serializer.html

  • typestate の背埌にある重芁な考え方は、状態遷移が倀を消費しお新しい倀を生成するこずで起こる、ずいうこずです。 各ステップでは、その状態に察しお有効な操䜜だけが利甚できたす。

  • この䟋では:

    • Serializer から開始し、これは構造䜓のシリアラむズを開始するこずだけを蚱可したす。

    • .serialize_struct(...) を呌び出すず、所有暩は SerializeStruct の倀ぞ移動したす。 その時点からは、構造䜓フィヌルドのシリアラむズに関連するメ゜ッドしか呌び出せたせん。

    • 元の Serializer にはもうアクセスできたせん。これにより、モヌドの混圚 たずえば、構造䜓 の途䞭で別の 構造䜓 を開始するこずや、 finish() を早すぎるタむミングで呌び出すこずを防げたす。

    • .finish_struct() を呌び出した埌にのみ、Serializer を受け取れたす。 その時点で、出力を確定したり再利甚したりできたす。

  • finish_struct() の呌び出しを忘れお SerializeStruct を早めにドロップするず、 Serializer も䞀緒にドロップされたす。 これにより、䞍完党な出力がシステムに挏れ出すこずを防げたす。

  • 察照的に、前のスラむドで芋たように、すべおを Serializer に盎接実装しおいた堎合は、 誰かが重芁な手順を飛ばしたり、シリアラむズのフロヌを混圚させたりするこずを防ぐものは䜕もありたせん。

単玔な Typestate を超えお

可胜な状態や遷移が数倚くある、たすたす耇雑な構成フロヌを、互換性のない操䜜を防ぎ぀぀どのように管理すればよいでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Serializer {/* [...] */}
struct SerializeStruct {/* [...] */}
struct SerializeStructProperty {/* [...] */}
struct SerializeList {/* [...] */}

impl Serializer {
    // TODO: 実装する
    //
    // fn serialize_struct(self, name: &str) -> SerializeStruct
    // fn finish(self) -> String
}

impl SerializeStruct {
    // TODO: 実装する
    //
    // fn serialize_property(mut self, name: &str) -> SerializeStructProperty

    // TODO:
    // この構造䜓はどのように完了すべきでしょうか これは、その構造䜓がどこに珟れるかによっお異なりたす:
    // - ルヌトレベル: `Serializer` を返す
    // - 別の構造䜓内のプロパティずしお: `SerializeStruct` を返す
    // - リスト内の倀ずしお: `SerializeList` を返す
    //
    // fn finish(self) -> ???
}

impl SerializeStructProperty {
    // TODO: 実装する
    //
    // fn serialize_string(self, value: &str) -> SerializeStruct
    // fn serialize_struct(self, name: &str) -> SerializeStruct
    // fn serialize_list(self) -> SerializeList
    // fn finish(self) -> SerializeStruct
}

impl SerializeList {
    // TODO: 実装する
    //
    // fn serialize_string(mut self, value: &str) -> Self
    // fn serialize_struct(mut self, value: &str) -> SerializeStruct
    // fn serialize_list(mut self) -> SerializeList

    // TODO:
    // `SerializeStruct::finish` ず同様に、戻り倀の型はネストに䟝存したす。
    //
    // fn finish(mut self) -> ???
}

有効な遷移の図:

structureserializerpropertylistString
  • これたでのシリアラむザヌを土台ずしお、今床は ネストした構造䜓 ず リスト をサポヌトしたいず考えたす。

  • しかし、これによっお 重耇 ず 構造的な耇雑さ の䞡方が生じたす。

  • さらに重倧なのは、ここで 型システムの制玄 に突き圓たるこずです。ネストのコンテキストごず䟋: ルヌト、構造䜓、リストにバリアントを耇補しない限り、finish() が䜕を返すべきかをすっきり衚珟できたせん。

  • 有効な遷移の図から、次のこずが分かりたす:

    • 遷移は再垰的である
    • 戻り倀の型は、サブ構造やリストが どこに 珟れるかに䟝存する
    • 各コンテキストは芪ぞ戻るための経路を必芁ずする
  • 具䜓型だけでは、これは管理䞍胜になりたす。珟圚のアプロヌチでは、型が爆発的に増え、手動で぀なぎ蟌む䜜業も必芁になりたす。

  • 次の章では、ゞェネリクス によっお、コンパむル時に有効な操䜜を匷制し぀぀、より少ないボむラヌプレヌトで再垰的なフロヌをモデル化できるこずを芋おいきたす。

ゞェネリクスを䜿った Typestate パタヌン

typestate モデリングをゞェネリクスず組み合わせるこずで、ロゞックを重耇させるこずなく、より幅広い有効な状態ず遷移を衚珟できたす。このアプロヌチは、状態数が増える堎合や、耇数の状態が振る舞いを共有し぀぀構造が異なる堎合に特に有甚です。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Serializer<S> {
    // [...]
    indent: usize,
    buffer: String,
    state: S,
}

struct Root;
struct Struct<S>(S);
struct Property<S>(S);
struct List<S>(S);

これで、Serializer ずその状態型定矩のメ゜ッドを実装するために必芁な芁玠がすべおそろいたした。これにより、次の図に瀺すように、API は有効な遷移のみを蚱可するようになりたす。

  • 芪コンテキストを远跡するためにゞェネリクスを掻甚するこずで、struct、list、property の各状態間で有効な遷移を匷制する、任意の深さにネストしたシリアラむザヌを構築できたす。

  • これにより、再垰的な構造を構築し぀぀、各状態でどのメ゜ッドにアクセスできるかを厳密に制埡できたす。

  • すべおの状態に共通するメ゜ッドは、Serializer<S> における任意の S に察しお定矩できたす。

  • マヌカヌ型䟋: List<S>は、れロサむズ型である可胜性のあるデヌタ以倖を含たないため、メモリや実行時のオヌバヌヘッドを生じたせん。その唯䞀の圹割は、型システムを通じお正しい API の䜿い方を匷制するこずです。

Serializer: Root を実装する

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Write as _;
struct Serializer<S> {
    // [...]
    indent: usize,
    buffer: String,
    state: S,
}

struct Root;
struct Struct<S>(S);

impl Serializer<Root> {
    fn new() -> Self {
        // [...]
        Self { indent: 0, buffer: String::new(), state: Root }
    }

    fn serialize_struct(mut self, name: &str) -> Serializer<Struct<Root>> {
        // [...]
        writeln!(self.buffer, "{name} {{").unwrap();
        Serializer {
            indent: self.indent + 1,
            buffer: self.buffer,
            state: Struct(self.state),
        }
    }

    fn finish(self) -> String {
        // [...]
        self.buffer
    }
}

元の有効な遷移の図を振り返るず、実装の冒頭は次のように芖芚化できたす。

Serializer<Root>Serializer<Struct<Root>>Stringserializestructfinishstructfinish
  • Serializer の「root」では、蚱可されるのは Struct だけです。

  • Serializer を String に最終化できるのは、この root レベルからのみです。

Serializer: Struct を実装する

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Write as _;
struct Serializer<S> {
    // [...]
    indent: usize,
    buffer: String,
    state: S,
}

struct Struct<S>(S);
struct Property<S>(S);

impl<S> Serializer<Struct<S>> {
    fn serialize_property(mut self, name: &str) -> Serializer<Property<Struct<S>>> {
        // [...]
        write!(self.buffer, "{}{name}: ", " ".repeat(self.indent * 2)).unwrap();
        Serializer {
            indent: self.indent,
            buffer: self.buffer,
            state: Property(self.state),
        }
    }

    fn finish_struct(mut self) -> Serializer<S> {
        // [...]
        self.indent -= 1;
        writeln!(self.buffer, "{}}}", " ".repeat(self.indent * 2)).unwrap();
        Serializer { indent: self.indent, buffer: self.buffer, state: self.state.0 }
    }
}

図は次のように拡匵できたす:

Serializer<Root>Serializer<Struct<S>>Serializer<Property<Struct<S>>>Stringfinishstructserializestructfinishstructserializepropertyfinish
  • Struct は Property しか含められたせん;

  • Struct を終了するず、その芪に制埡が戻りたす。前の スラむドではその芪を Root ず仮定しおいたしたが、実際には、ネストした “structs” の堎合の Struct のように、別のものになるこずもありたす。

Serializer: Property を実装する

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Write as _;
struct Serializer<S> {
    // [...]
    indent: usize,
    buffer: String,
    state: S,
}

struct Struct<S>(S);
struct Property<S>(S);
struct List<S>(S);

impl<S> Serializer<Property<Struct<S>>> {
    fn serialize_struct(mut self, name: &str) -> Serializer<Struct<Struct<S>>> {
        // [...]
        writeln!(self.buffer, "{name} {{").unwrap();
        Serializer {
            indent: self.indent + 1,
            buffer: self.buffer,
            state: Struct(self.state.0),
        }
    }

    fn serialize_list(mut self) -> Serializer<List<Struct<S>>> {
        // [...]
        writeln!(self.buffer, "[").unwrap();
        Serializer {
            indent: self.indent + 1,
            buffer: self.buffer,
            state: List(self.state.0),
        }
    }

    fn serialize_string(mut self, value: &str) -> Serializer<Struct<S>> {
        // [...]
        writeln!(self.buffer, "{value},").unwrap();
        Serializer { indent: self.indent, buffer: self.buffer, state: self.state.0 }
    }
}

Property 状態のメ゜ッドが远加されたこずで、図はほが完成です:

Serializer<Root>Serializer<Struct<S>>Serializer<Property<S>>StringSerializer<List<S>>finishstructserializestructfinishstructserializestringorstructserializepropertyfinishserializelist
  • プロパティは String、Struct<S>、たたは List<S> ずしお定矩できるため、ネストした構造を衚珟できたす。

  • これで段階的な実装は完了です。List<S> のサポヌトを含む完党な実装は、次のスラむドで瀺したす。

Serializer: 完党な実装

最初に目指しおいたフロヌを振り返るず、次のようになりたす。

structureserializerpropertylistString

これが、シリアラむザヌの型に盎接反映されおいるこずがわかりたす。

Serializer<Root>Serializer<Struct<S>>Serializer<Property<S>>StringSerializer<List<S>>finishstructserializestructfinishlistfinishstructserializestringorstructserializepropertyfinishfinishstructserializelistserializelistorstringorfinishlist

Serializer ずそのすべおの状態の完党な実装コヌドは、この Rust playground で確認できたす。

  • このパタヌンは銀の匟䞞ではありたせん。次のような問題は䟝然ずしお蚱しおしたいたす。

    • 空たたは無効なプロパティ名これは newtype パタヌン を䜿っお修正できたす
    • 重耇したプロパティ名これは Struct<S> で远跡し、Result で凊理できたす
  • 怜蚌の倱敗が発生する堎合は、メ゜ッドシグネチャを倉曎しお Result を返すようにし、回埩を可胜にするこずもできたす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    struct PropertySerializeError<S> {
        kind: PropertyError,
        serializer: Serializer<Struct<S>>,
    }
    
    impl<S> Serializer<Struct<S>> {
        fn serialize_property(
            self,
            name: &str,
        ) -> Result<Serializer<Property<Struct<S>>>, PropertySerializeError<S>> {
            /* ... */
        }
    }
    }
  • この API は匷力ですが、垞に䜿いやすいずは限りたせん。本番環境のシリアラむザヌでは、通垞はよりシンプルな API が奜たれ、typestate パタヌンは重芁な䞍倉条件を匷制するためにのみ䜿われたす。

  • 珟実の優れた䟋の 1 ぀が rustls::ClientConfig で、これはゞェネリクスず typestate を䜿っお、ナヌザヌを安党で正しい蚭定手順ぞ導きたす。

借甚チェッカヌを䜿っお䞍倉条件を匷制する

借甚チェッカヌは、メモリの所有暩を匷制するために远加されたものですが、他の問題をモデル化し、API の誀甚を防ぐこずもできたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// ドアは開いおいるか閉じおいるかのどちらかで、斜錠たたは解錠するには正しい鍵が必芁です。
/// これは Shared な鍵ず Owned なドアでモデル化されおいたす。
pub struct DoorKey {
    pub key_shape: u32,
}
pub struct LockedDoor {
    lock_shape: u32,
}
pub struct OpenDoor {
    lock_shape: u32,
}

fn open_door(key: &DoorKey, door: LockedDoor) -> Result<OpenDoor, LockedDoor> {
    if door.lock_shape == key.key_shape {
        Ok(OpenDoor { lock_shape: door.lock_shape })
    } else {
        Err(door)
    }
}

fn close_door(key: &DoorKey, door: OpenDoor) -> Result<LockedDoor, OpenDoor> {
    if door.lock_shape == key.key_shape {
        Ok(LockedDoor { lock_shape: door.lock_shape })
    } else {
        Err(door)
    }
}

fn main() {
    let key = DoorKey { key_shape: 7 };
    let closed_door = LockedDoor { lock_shape: 7 };
    let opened_door = open_door(&key, closed_door);
    if let Ok(opened_door) = opened_door {
        println!("鍵の圢状 '{}' でドアを開けたした", key.key_shape);
    } else {
        eprintln!(
            "ドアは開きたせんでしたあなたの鍵で開けられるのは圢状 '{}' の錠前だけです",
            key.key_shape
        );
    }
}
  • 借甚チェッカヌがメモリ安党性のバグ解攟埌の䜿甚、デヌタ競合を防ぐのは、すでに芋おきたした。

  • 型を䜿っお API を圢䜜り、制限する方法も、 Typestate パタヌン ですでに䜿っおいたす。

  • 蚀語機胜は、しばしば特定の目的のために導入されたす。

    時間が経぀に぀れお、ナヌザヌは、導入時には予想されおいなかった方法でその機胜を䜿うやり方を生み出すこずがありたす。

    Java 5 は 2004 幎に Generics を導入したした。その 䞻な公称目的は型安党なコレクションを可胜にするこず でした。

    圓初、採甚はゆっくりでしたが、䞀郚の新しいプロゞェクトでは最初からゞェネリクスを䞭心に API を蚭蚈し始めたした。

    それ以来、ナヌザヌず蚀語開発者は、ゞェネリクスの甚途を型安党な API 蚭蚈の他の領域ぞず広げおきたした。

    • クラス情報は、Java の Class<T> や Guava の TypeToken<T> を通じお保持できたす。
    • Builder パタヌンは、再垰的ゞェネリクスを䜿っお実装できたす。 ここで目指すのも䌌たようなこずです。借甚チェッカヌは解攟埌の䜿甚やデヌタ競合を防ぐために導入されたしたが、ここではそれを API 蚭蚈のための別のツヌルの 1 ぀ずしお扱いたす。 これは、メモリ安党性のバグを防ぐこずずは関係のないプログラムの性質をモデル化するためにも䜿えたす。
  • 借甚チェッカヌを問題解決のツヌルずしお䜿うには、その本来の目的が、解攟埌の䜿甚やデヌタ競合を防ぐ文脈で可倉゚むリアスを防止するこずだずいう点を、いったん「忘れる」必芁がありたす。

    ルヌルは同じでも意味が少し異なる状況の䞭で䜜業しおいるず考えるべきです。

  • この䟋では、所有暩ず借甚を䜿っお物理的なドアの状態をモデル化したす。

    open_door は LockedDoor を 消費 し、新しい OpenDoor を返したす。叀い LockedDoor の倀はもう䜿えたせん。

    間違った鍵が䜿われた堎合、ドアは斜錠されたたたです。その堎合、Result の Err ずしお返されたす。

    すでに開けたドアを䜿おうずするず、コンパむル時゚ラヌになりたす。

  • 同様に、lock_door は OpenDoor を消費するため、ドアを 2 回閉じるこずをコンパむル時に防ぎたす。

  • 借甚チェッカヌのルヌルはメモリ安党性のバグを防ぐために存圚したすが、その根底にある論理システムはメモリが䜕であるかを「知っお」いるわけではありたせん。

    借甚チェッカヌがしおいるこずは、ナヌザヌが操䜜をどのような順序で行えるかに぀いおの特定のルヌル集合を匷制するこずだけです。

    これは、誀甚しにくく、あるいは誀甚䞍可胜な API を蚭蚈するために、借甚チェッカヌのルヌルを掻甚する 1 ぀の䟋にすぎたせん。

ラむフタむムず借甚: 抜象的なルヌル

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 保持しおおく䜕かを甚意するための内郚デヌタ型。
pub struct Internal;
// 「倖偎」のデヌタ。
pub struct Data(Internal);

fn shared_use(value: &Data) -> &Internal {
    &value.0
}
fn exclusive_use(value: &mut Data) -> &mut Internal {
    &mut value.0
}
fn deny_future_use(value: Data) {}

fn demo_exclusive() {
    let mut value = Data(Internal);
    let shared = shared_use(&value);
    // let exclusive = exclusive_use(&mut value); // ❌🔚
    let shared_again = shared;
}

fn demo_denied() {
    let value = Data(Internal);
    deny_future_use(value);
    // let shared = shared_use(&value); // ❌🔚
}

fn main() {}
  • この䟋は、借甚チェッカヌのルヌルを参照から切り離し、 メモリ安党性以倖の文脈における意味論的な意味ぞず捉え盎したものです。

    䜕かが倉曎されおいるわけでも、䜕かがスレッド間で送られおいるわけでもありたせん。

  • Rust の借甚チェッカヌでは、倀を「取埗」する 3 ぀の異なる方法を利甚できたす。

    • 所有倀 T。別のスコヌプに返されない限り、スコヌプの終了時に倀は砎棄されたす。

    • 共有参照 &T。゚むリアシングを蚱可したすが、共有参照が䜿甚䞭である間は可倉アクセスを犁止したす。

    • 可倉参照 &mut T。ある時点で 1 ぀の倀に察しお存圚できるのは 1 ぀だけですが、共有参照を䜜成するために䜿甚できたす。

  • 問い: demo 関数内のコメントアりトされた 2 行は、なぜコンパむル゚ラヌを匕き起こすのでしょうか?

    demo_exclusive: exclusive 参照が取埗されたあずも shared 倀が䟝然ずしお゚むリアスされおいるためです。

    demo_denied: &value から shared_again_again 参照が取埗される 1 行前に、value が消費されおいるためです。

  • すべおの &T ず &mut T にはラむフタむムがあるこずを芚えおおいおください。倚くの堎合、ナヌザヌが泚釈を付けたり意識したりしなくおよいだけです。

    Rust コンパむラでは倚くの堎合ラむフタむムを 省略 できるため、私たちがラむフタむムを明瀺するこずはたれです。参照: ラむフタむム省略

単回䜿甚の倀

ずきには、_䞀床しか䜿えない_倀が欲しいこずがありたす。その重芁な䟋の 1 ぀が、暗号分野における「Nonce」です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Key(/* 詳现は省略 */);

/// 暗号甚途に適した単回䜿甚の数倀。
pub struct Nonce(u32);

/// 暗号孊的に安党な乱数生成関数。
pub fn new_nonce() -> Nonce {
    Nonce(4) // 公平なサむコロを振っお遞ばれたした, https://xkcd.com/221/
}

/// nonce を消費したすが、key や data は消費したせん。
pub fn encrypt(nonce: Nonce, key: &Key, data: &[u8]) {}

fn main() {
    let nonce = new_nonce();
    let data_1: [u8; 4] = [1, 2, 3, 4];
    let data_2: [u8; 4] = [4, 3, 2, 1];
    let key = Key(/* 詳现は省略 */);

    // key ず data は再利甚したり、コピヌしたりできたすが、nonce はできたせん。
    encrypt(nonce, &key, &data_1);
    // encrypt(nonce, &key, &data_2); // 🛠❌
}
  • 問題: 倀が䞀床しか䜿われないこずを、どうすれば保蚌できるでしょうか

  • 動機: nonce は、リプレむ攻撃を防ぐために暗号プロトコルで䜿甚される、ランダムで䞀意なデヌタです。

    背景: 実際には、誀っお nonce を再利甚しおしたった事䟋がありたす。 最も䞀般的には、その結果ずしお暗号プロトコルが完党に砎綻し、 本来の圹割を果たせなくなりたす。

    nonce の再利甚のされ方や察象の暗号方匏によっおは、秘密鍵たで 攻撃者に蚈算されおしたうこずもありたす。

  • Rust には、「これを䞀床䜿ったら、もう二床ず䜿えない」ずいう䞍倉条件を 実珟するための分かりやすい手段がありたす。倀を 所有暩を受け取る匕数 ずしお枡すこずです。

  • 泚目点: encrypt 関数は nonce を倀枡し所有暩を受け取る匕数で 受け取りたすが、key ず data は参照で受け取りたす。

  • 単回䜿甚の倀に察するテクニックは次のずおりです。

    • コンストラクタヌを非公開にしお、同じ内郚倀を持぀倀をナヌザヌが 2 回 構築できないようにする。

    • 䞀意に保ちたいデヌタをナヌザヌが耇補できないように、Clone/Copy トレむトや同等のメ゜ッドを実装しない。

    • 内郚の型を䞍透明にしnewtype パタヌンのように、ナヌザヌが既存の倀を 自分で倉曎できないようにする。

  • 問い: スラむドのコヌドにある newtype パタヌンには、䜕が欠けおいるでしょうか

    期埅する答え: モゞュヌル境界。

    実挔: モゞュヌル境界がないず、ナヌザヌは nonce を自分で構築できおしたいたす。

    修正: Key、Nonce、new_nonce をモゞュヌルの内偎に眮きたす。

さらに探る

  • 暗号における泚意点: nonce は、実際のランダム性がない疑䌌乱数過皋で 生成された堎合、䟝然ずしお 2 回䜿われる可胜性がありたす。これはこの方法では 防げたせん。この API 蚭蚈は 1 ぀の nonce を耇補するこずは防ぎたすが、 すべおのロゞックバグを防ぐわけではありたせん。

盞互排他的な参照 / 「Aliasing XOR Mutability」

&T 参照ず &mut T 参照の盞互排他性を利甚するず、準備が敎う前に デヌタが䜿われるのを防げたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct QueryResult;
pub struct DatabaseConnection {/* フィヌルドは省略 */}

impl DatabaseConnection {
    pub fn new() -> Self {
        Self {}
    }
    pub fn results(&self) -> &[QueryResult] {
        &[] // ダミヌの結果
    }
}

pub struct Transaction<'a> {
    connection: &'a mut DatabaseConnection,
}

impl<'a> Transaction<'a> {
    pub fn new(connection: &'a mut DatabaseConnection) -> Self {
        Self { connection }
    }
    pub fn query(&mut self, _query: &str) {
        // ク゚リを送信するが、結果は埅たない。
    }
    pub fn commit(self) {
        // トランザクションの実行を完了し、結果を取埗する。
    }
}

fn main() {
    let mut db = DatabaseConnection::new();

    // トランザクション `tx` は `db` を可倉借甚する。
    let mut tx = Transaction::new(&mut db);
    tx.query("SELECT * FROM users");

    // `db` はすでに `tx` によっお可倉借甚されおいるため、これはコンパむルされない。
    // let results = db.results(); // ❌🔚

    // `tx` が `commit()` によっお消費されるず、`db` の借甚は終了する。
    tx.commit();

    // これで `db` を再び借甚できる。
    let results = db.results();
}
  • 動機: このデヌタベヌス API では、ク゚リは非同期実行に向けお開始され、 結果が利甚可胜になるのはトランザクション党䜓が完了しおからです。

    ナヌザヌはク゚リが即座に実行されるず考えお、利甚可胜になる前に結果を 読み取ろうずするかもしれたせん。この API の誀甚により、アプリが 䞍完党たたは䞍正確なデヌタを読み取っおしたう可胜性がありたす。

    䞀芋するず明らかな誀解ですが、このような状況は実際に起こりえたす。

    問いかけ: 適切な䜿い方に぀いおドキュメントを読たなかったために、API を 誀解したこずはありたすか

    想定: キャリア初期や倧孊圚孊䞭にしたミスや誀解の䟋。

    API の芏暡ずナヌザヌ数が増えるに぀れお、その API が衚珟するシステムに ぀いお深い知識を持぀ナヌザヌの割合は小さくなりたす。

  • この䟋は、この皮の誀甚を防ぐために Aliasing XOR Mutability をどのように 利甚できるかを瀺しおいたす。

  • プログラマヌが、ク゚リは非同期実行ずしお開始されるのではなく即座に 実行されるず考えおしたうず、このコヌドは結果の準備が敎う前に 読み取っおしたう可胜性がありたす。

  • Transaction 型のコンストラクタヌはデヌタベヌス接続ぞの可倉参照を 受け取り、それを返される Transaction 倀の䞭に保存したす。

    ここで明瀺されおいるラむフタむムに気埌れする必芁はなく、この堎合は単に 「Transaction よりも、それに枡された DatabaseConnection のほうが 長生きする」こずを意味したす。

    この参照が可倉なのは、さらにトランザクションを開始したり結果を 読み取ったりするずいったほかの甚途で DatabaseConnection を 䜿えないようにするためです。

  • Transaction が存圚しおいる間は、その Transaction の䜜成元ずなった DatabaseConnection 倉数には觊れられたせん。

    実挔: db.results() の行をコメント解陀しおください。そうするず、db は すでに可倉借甚されおいるため、コンパむル゚ラヌになりたす。

  • 泚: ク゚リ結果を公開せず、ゲッタヌ関数の背埌に眮くこずで、 「アクティブなトランザクションが存圚しない堎合にのみ、ナヌザヌは ク゚リ結果を確認できる」ずいう䞍倉条件を匷制できたす。

    ク゚リ結果が構造䜓の公開フィヌルドに眮かれおいた堎合、この䞍倉条件は 砎られうるでしょう。

PhantomData 1/4: 同じデヌタずセマンティクスの重耇をなくす

newtype パタヌンは、ずきに DRY 原則に反するこずがありたす。これをどう解決すればよいでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct UserId(u64);
impl ChatUser for UserId { /* ... */ }

pub struct PatronId(u64);
impl ChatUser for PatronId { /* ... */ }

pub struct ModeratorId(u64);
impl ChatUser for ModeratorId { /* ... */ }
impl ChatModerator for ModeratorId { /* ... */ }

pub struct AdminId(u64);
impl ChatUser for AdminId { /* ... */ }
impl ChatModerator for AdminId { /* ... */ }
impl ChatAdmin for AdminId { /* ... */ }

// 以䞋同様 ...
fn main() {}
  • 問題: 暩限を区別するために newtype パタヌンを䜿いたいのですが、同じデヌタに察しお同じトレむトを䜕床も実装しなければなりたせん。

  • 問い: ここでの各実装の詳现が型ごずに同じだず仮定するず、繰り返しを避けるにはどのような方法があるでしょうか

    期埅する回答:

    • これを別々のデヌタ型ではなく enum にする。
    • ナヌザヌ ID を、struct Admin(u64, UserPermission, ModeratorPermission, AdminPermission); のような暩限トヌクンずひずたずめにする。
    • 暩限を゚ンコヌドする型パラメヌタを远加する。
    • 先取りしお PhantomData に蚀及するタむトルにありたす。

PhantomData 2/4: 型レベルのタグ付け

型パラメヌタを远加しお、前のスラむドの問題を解決したしょう。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// use std::marker::PhantomData;

pub struct ChatId<T> { id: u64, tag: T }

pub struct UserTag;
pub struct AdminTag;

pub trait ChatUser {/* ... */}
pub trait ChatAdmin {/* ... */}

impl ChatUser for UserTag {/* ... */}
impl ChatUser for AdminTag {/* ... */} // 管理者はナヌザヌです
impl ChatAdmin for AdminTag {/* ... */}

// impl <T> Debug for UserTag<T> {/* ... */}
// impl <T> PartialEq for UserTag<T> {/* ... */}
// impl <T> Eq for UserTag<T> {/* ... */}
// 以䞋同様 ...

impl <T: ChatUser> ChatId<T> {/* ナヌザヌ以䞊のすべおの機胜 */}
impl <T: ChatAdmin> ChatId<T> {/* 管理者専甚のすべおの機胜 */}

fn main() {}
  • ここでは型パラメヌタを䜿い、異なる暩限トレむトを実装する「タグ」型によっお暩限を制埡しおいたす。

    タグ型、あるいはマヌカヌ型は、利甚者や API 蚭蚈者にずっお䜕らかの意味を持぀れロサむズ型です。

  • 問い: これをその型の実際のむンスタンスにするず、どのような問題が生じたすか

    答え: それがれロサむズ型() や struct MyTag; などでない堎合、私たちが必芁ずしおいるのはコンパむル時にのみ関係する型情報だけなのに、必芁以䞊のメモリを割り圓おるこずになりたす。

  • 実挔: tag の倀を完党に削陀しおから、コンパむルしおみたしょう

    これはコンパむルできたせん。未䜿甚のphantom型パラメヌタがあるためです。

    ここで PhantomData の出番です

  • 実挔: PhantomData の import のコメントを倖し、ChatId<T> を次のようにしたす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    pub struct ChatId<T> {
        id: u64,
        tag: PhantomData<T>,
    }
    }
  • PhantomData<T> は、型パラメヌタを持぀れロサむズ型です。これの倀は、他の ZST ず同様に let phantom: PhantomData<UserTag> = PhantomData; のように、あるいは PhantomData::default() を䜿っお構築できたす。

    実挔: ChatId<T> に察しお From<u64> を実装し、PhantomData の構築を匷調したす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    impl<T> From<u64> for ChatId<T> {
        fn from(value: u64) -> Self {
            ChatId {
                id: value,
                // たたは `PhantomData::default()`
                tag: PhantomData,
            }
        }
    }
    }
  • PhantomData は Typestate パタヌンの䞀郚ずしお䜿甚でき、同じ構造を持ちながら異なるメ゜ッドを持぀デヌタを衚珟できたす。たずえば、TaggedData<Start> には、TaggedData<End> にはないメ゜ッドやトレむト実装を持たせるこずができたす。

PhantomData 3/4: 倖郚リ゜ヌスのラむフタむム

倖郚リ゜ヌスの䞍倉条件は、倚くの堎合、ラむフタむム芏則で衚珟できるこずず䞀臎したす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// use std::marker::PhantomData;

/// C のデヌタベヌスラむブラリぞの盎接的な FFI。
/// この API はそのたた提䟛されたものであり、こちらからは倉曎できたせん。
mod ffi {
    pub type DatabaseHandle = u8; // 同時に開けるデヌタベヌスは最倧 255 個

    fn database_open(name: *const std::os::raw::c_char) -> DatabaseHandle {
        unimplemented!()
    }
    // ... など。
}

struct DatabaseConnection(ffi::DatabaseHandle);

struct Transaction<'a>(&'a mut DatabaseConnection);

impl DatabaseConnection {
    fn new_transaction(&mut self) -> Transaction<'_> {
        Transaction(self)
    }
}

fn main() {}
  • Aliasing XOR Mutability の䟋にあった トランザクション API を思い出しおください。

    トランザクションがアクティブな間デヌタベヌスをロックするため、 トランザクション型の䞭でデヌタベヌス接続ぞの可倉参照を保持しおいたした。

    この䟋では、倖郚の非 Rust API の䞊に Transaction API を実装したいず考えおいたす。

    たず、&mut DatabaseConnection を保持する Transaction 型を定矩するずころから始めたす。

  • 質問: この実装の限界は䜕でしょうか。u8 は実装䞊正確であり、 倖郚 API を䜿うための情報ずしお十分であるず仮定しおください。

    想定:

    • 64 ビットプラットフォヌムでは、間接参照のために必芁以䞊に 7 バむト倚く䜿い、 実行時にはポむンタのデリファレンスのコストもかかりたす。
  • 問題: トランザクションには、それを䜜成したデヌタベヌス接続を借甚させたい䞀方で、 Transaction オブゞェクトには実際の参照を栌玍したくありたせん。

  • 質問: ラむフタむムパラメヌタを残したたた、 Transaction から可倉参照を取り陀くずどうなるでしょうか。

    想定: 未䜿甚のラむフタむムパラメヌタになりたす。

  • 前のスラむドの型タグ付けず同様に、この未䜿甚のラむフタむムパラメヌタを 衚すために PhantomData を䜿うこずができたす。

    違いは、このラむフタむムを別の型ず䞀緒に䜿う必芁があるこずですが、 その別の型自䜓はそれほど重芁ではないずいう点です。

  • 実挔: Transaction を次のように倉曎したす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    struct Transaction<'a> {
        connection: DatabaseConnection,
        _phantom: PhantomData<&'a mut DatabaseConnection>,
    }
    }

    DatabaseConnection::new_transaction() メ゜ッドを曎新したす。

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    impl DatabaseConnection {
        fn new_transaction<'a>(&'a mut self) -> Transaction<'a> {
            Transaction { connection: DatabaseConnection(self.0), _phantom: PhantomData }
        }
    }
    }

    これにより、それを䜜成した DatabaseConnection に結び付けられた 所有されたデヌタベヌス接続を埗られたすが、参照を栌玍する版よりも 実行時のメモリフットプリントを小さくできたす。

    PhantomData はれロサむズ型() や struct MyZeroSizedType; のようなものなので、 Transaction のサむズは теперь u8 ず同じになりたす。

    代わりに参照を保持しおいた実装は、usize ず同じ倧きさでした。

さらに探る

  • 型ず倀の間の関係をこのように゚ンコヌドする方法は、unsafe ず組み合わせるず 非垞に匷力です。ずいうのも、ラむフタむムを操䜜する方法がほずんど任意になるからです。 これは危険でもありたすが、倖郚の機械怜蚌枈み蚌明のようなツヌルず組み合わせるこずで、 関連するデヌタ型にラむフタむムず安党性に関する期埅を゚ンコヌドし぀぀、 埪環/自己参照型を安党に゚ンコヌドできたす。

  • GhostCell (2021) の論文ず、 その関連実装は、 この皮の取り組みを瀺しおいたす。borrow checker には制玄がありたすが、 それでも抜け道を䜿う方法はあり、そのうえで そうした抜け道の䜿い方が 䞀貫しおいお安党であるこずを瀺す こずができたす。

PhantomData 4/4: OwnedFd ず BorrowedFd

BorrowedFd は、PhantomData が実際に掻甚されおいる代衚的な䟋です。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;
use std::os::raw::c_int;

mod libc_ffi {
    use std::os::raw::{c_char, c_int};
    pub unsafe fn open(path: *const c_char, oflag: c_int) -> c_int {
        3
    }
    pub unsafe fn close(fd: c_int) {}
}

struct OwnedFd {
    fd: c_int,
}

impl OwnedFd {
    fn try_from_fd(fd: c_int) -> Option<Self> {
        if fd < 0 {
            return None;
        }
        Some(OwnedFd { fd })
    }

    fn as_fd<'a>(&'a self) -> BorrowedFd<'a> {
        BorrowedFd { fd: self.fd, _phantom: PhantomData }
    }
}

impl Drop for OwnedFd {
    fn drop(&mut self) {
        unsafe { libc_ffi::close(self.fd) };
    }
}

struct BorrowedFd<'a> {
    fd: c_int,
    _phantom: PhantomData<&'a ()>,
}

fn main() {
    // 曞き蟌み専甚ず䜜成暩限を指定した生の syscall でファむルを䜜成したす。
    let fd = unsafe { libc_ffi::open(c"c_str.txt".as_ptr(), 065) };
    // 敎数のファむルディスクリプタの所有暩を `OwnedFd` に枡したす。
    // `OwnedFd::drop()` はファむルディスクリプタを閉じたす。
    let owned_fd =
        OwnedFd::try_from_fd(fd).expect("syscall でファむルを開けたせんでした");

    // `OwnedFd` から `BorrowedFd` を䜜成したす。
    // `BorrowedFd::drop()` はファむルを所有しおいないため、ファむルを閉じたせん
    let borrowed_fd: BorrowedFd<'_> = owned_fd.as_fd();
    // std::mem::drop(owned_fd); // ❌🔚
    std::mem::drop(borrowed_fd);
    let second_borrowed = owned_fd.as_fd();
    // owned_fd はここで drop され、ファむルは閉じられたす。
}
  • ファむルディスクリプタは、特定のプロセスによるファむルぞのアクセスを衚したす。

    補足: デバむスや OS 固有の機胜は、unix 系システムではファむルであるかのように公開されたす。

  • OwnedFd は、ファむルディスクリプタの所有暩を持぀ラッパヌ型です。これはファむルディスクリプタを_所有_し、 drop されるずそれを閉じたす。

    泚: ここでは独自実装を䜿っおいるので、明瀺的な Drop 実装に泚目しおください。

    BorrowedFd はその借甚版にあたる型であり、drop されおもファむルを閉じる必芁はありたせん。

    泚: BorrowedFd には Drop を明瀺的に実装しおいたせん。

  • BorrowedFd は、PhantomData で捉えたラむフタむムを䜿っお、 「このファむルディスクリプタが存圚するなら、それを閉じる責任を負っおいなくおも、 OS のファむルディスクリプタは䟝然ずしお開いおいる」ずいう䞍倉条件を匷制したす。

    BorrowedFd のラむフタむムパラメヌタは、プログラム内に、その特定の BorrowedFd ず同じだけ長く生存するか、あるいはそれより長く生存する別の倀が存圚するこずを芁求したす この堎合は OwnedFd です。

    実挔: std::mem::drop(owned_fd) の行をコメント解陀しおコンパむルしおみるず、 borrowed_fd が owned_fd のラむフタむムに䟝存しおいるこずを瀺せたす。

    これは API 蚭蚈者によっお、「その別の倀こそがファむルぞのアクセスを開いたたたにしおいる」 ずいう意味になるよう API に゚ンコヌドされおいたす。

    Rust の borrow checker は、䞀方の倀がもう䞀方の倀ず少なくずも同じだけ長く生存しなければならない ずいうこの関係を匷制するため、この API の利甚者は、正しいファむルディスクリプタの゚むリアシングや クロヌズのロゞックを自分で扱う必芁がありたせん。

トヌクン型

プラむベヌトなコンストラクタを持぀型は、䞍倉条件の蚌明ずしお利甚できたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub mod token {
    // モゞュヌル境界の内偎にある、プラむベヌトフィヌルドを持぀公開型。
    pub struct Token { proof: () }

    pub fn get_token() -> Option<Token> {
        Some(Token { proof: () })
    }
}

pub fn protected_work(token: token::Token) {
    println!("We have a token, so we can make assumptions.")
}

fn main() {
    if let Some(token) = token::get_token() {
        // トヌクンがあるので、この䜜業を実行できたす。
        protected_work(token);
    } else {
        // トヌクンを取埗できなかったので、`protected_work` を呌び出せたせん。
    }
}
  • 動機: ナヌザヌが特定のタスクを実行するたで、機胜ぞのアクセスを 制限できるようにしたい。

    これは、struct ず module のプラむバシヌルヌルを通じお、API の利甚者が 自分では構築できない型を定矩するこずで実珟できたす。

    Newtypes も同様の方法でプラむバシヌルヌルを䜿い、 倀が実行時に䞍倉条件を満たすこずが保蚌されない限り、構築を 制限したす。

  • 問い: ここでの proof: () フィヌルドの目的は䜕ですか

    proof: () がなければ、Token にはプラむベヌトフィヌルドがなくなり、 ナヌザヌは Token の倀を任意に構築できるようになりたす。

    実挔: main でトヌクンを手動で構築しおみお、コンパむル゚ラヌを 瀺しおください。実挔: Token から proof フィヌルドを削陀しお、 プラむベヌトフィヌルドがなければナヌザヌが Token を構築できるこずを瀺しおください。

  • Token 型をモゞュヌル境界 (token) の内偎に眮くこずで、その モゞュヌルの倖偎のナヌザヌは proof フィヌルドにアクセスする暩限がないため、 自分でその倀を構築できたせん。

    API 開発者は、これらのトヌクンを生成するメ゜ッドや関数を定矩できたす。 ナヌザヌにはできたせん。

    そのトヌクンは、API 開発者がそのトヌクンぞのアクセスに察しお定めた条件を 満たしたこずの蚌明になりたす。

  • 問い: API 開発者が、これを回避する手段を誀っお持ち蟌んでしたうのは どのような堎合でしょうか

    「シリアラむズ実装」、ほかのパヌサヌ / 「from string」実装、 たたは Default の実装ずいった答えを期埅したす。

暩限トヌクン

トヌクン型は、確認枈みの暩限の蚌明ずしおうたく機胜したす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

mod admin {
    pub struct AdminToken(());

    pub fn get_admin(password: &str) -> Option<AdminToken> {
        if password == "Password123" { Some(AdminToken(())) } else { None }
    }
}

// 暩限があるこずを確認する必芁はありたせん。
// `AdminToken` 匕数が、そのような確認ず同等だからです。
pub fn add_moderator(_: &admin::AdminToken, user: &str) {}

fn main() {
    if let Some(token) = admin::get_admin("Password123") {
        add_moderator(&token, "CoolUser");
    } else {
        eprintln!("パスワヌドが正しくありたせん。暩限を蚌明できたせんでした。")
    }
}
  • この䟋では、パスワヌドによっおチャット クラむアントの管理者暩限を取埗し、それらの 暩限を取埗した埌にナヌザヌぞモデレヌタヌ暩限を䞎えるこずをモデル化しおいたす。 AdminToken 型は、「ナヌザヌが正しい暩限を持っおいるこずの蚌明」ずしお 機胜したす。

    コヌド内でナヌザヌにパスワヌドの入力を求め、パスワヌドが正しければ、 特定の環境ここではチャットクラむアント内で管理者アクションを実行するための AdminToken を取埗したす。

    暩限を取埗したら、add_moderator 関数を呌び出せたす。

    このトヌクン型なしではその関数を呌び出せないため、 そもそも呌び出せるずいうこず自䜓から、暩限を持っおいるずみなせたす。

  • 実挔: main でもう䞀床 AdminToken を構築しようずしおみおください。これは、 有甚なトヌクンの基盀が、それらを任意に 構築できないようにするこずにあるず改めお瀺すためです。

デヌタを持぀トヌクン型: MutexGuard

トヌクン型が远加のデヌタを必芁ずするこずもありたす。MutexGuard は、 暩限 + デヌタを衚すトヌクンの䞀䟋です。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

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

fn main() {
    let mutex = Arc::new(Mutex::new(42));
    let try_mutex_guard: Result<MutexGuard<'_, _>, _> = mutex.lock();
    if let Ok(mut guarded) = try_mutex_guard {
        // 取埗した MutexGuard は、排他的アクセスの蚌明です。
        *guarded = 451;
    }
}
  • Mutex は、倀ぞの読み取り/曞き蟌みアクセスに察しお盞互排他を匷制したす。すでに このコヌスの前半で Mutex は扱いたしたが参照: RAII/Mutex、ここでは 特に MutexGuard を芋おいきたす。

  • MutexGuard は、ある時点で読み取り/曞き蟌みアクセス暩があるこずを蚌明する、 Mutex によっお生成される倀です。

    MutexGuard はさらに、それを生成した Mutex ぞの参照も保持しおおり、 Deref ず DerefMut の実装によっお、基盀ずなる Mutex がそのデヌタを ナヌザヌから非公開のたた、Mutex のデヌタにアクセスできたす。

  • mutex.lock() が MutexGuard を返さない堎合、mutex 内の倀を倉曎する 暩限はありたせん。

    暩限がないだけでなく、MutexGuard を埗ない限り、mutex のデヌタにアクセスする 手段もありたせん。

    これは C++ ず察照的です。C++ では、mutex ず lock guard はデヌタ自䜓ぞのアクセスを 制埡せず、単に、デヌタを読むずきや操䜜するずきに毎回ナヌザヌが確認するこずを 芚えおおかなければならないフラグずしお機胜するだけです。

  • 実挔: mutex 倉数を可倉にしおから、それをデリファレンスしお倀を倉曎しようず しおみおください。これには Deref 実装がなく、mutex guard を取埗する以倖に、 それが保持しおいるデヌタぞ到達する方法がないこずを瀺しおください。

倉数固有のトヌクンブランディング 1/4

トヌクンを特定の倉数に結び付けたい堎合はどうすればよいでしょうか

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct Bytes {
    bytes: Vec<u8>,
}
struct ProvenIndex(usize);

impl Bytes {
    fn get_index(&self, ix: usize) -> Option<ProvenIndex> {
        if ix < self.bytes.len() { Some(ProvenIndex(ix)) } else { None }
    }
    fn get_proven(&self, token: &ProvenIndex) -> u8 {
        unsafe { *self.bytes.get_unchecked(token.0) }
    }
}

fn main() {
    let data_1 = Bytes { bytes: vec![0, 1, 2] };
    if let Some(token_1) = data_1.get_index(2) {
        data_1.get_proven(&token_1); // 問題なく動䜜したす

        // let data_2 = Bytes { bytes: vec![0, 1] };
        // data_2.get_proven(&token_1); // panic したす これを防げるでしょうか
    }
}
  • コヌド内の_特定の倉数_にトヌクンを結び付けたい堎合はどうでしょうか Rust の型 システムでこれを実珟できるでしょうか

  • 動機: バむト配列に察する、既知で有効なむンデックスを衚すトヌクン型を持ちたい のです。

    これらの蚌明枈みむンデックスがあれば、トヌクンが_存圚するむンデックスの蚌明_ず しお機胜するため、境界チェックを完党に回避できたす。

    むンデックスが有効であるこずは分かっおいるので、get_proven() は境界チェック を省略できたす。

  • この䟋では、ある配列の蚌明枈みむンデックスが別の配列で䜿われるのを防ぐ仕組み がありたせん。この堎合にむンデックスが範囲倖であれば、それは未定矩動䜜に なりたす。

  • 実挔: data_2.get_proven(&token_1); の行のコメントアりトを倖したす。

    ここでコヌドは panic したす むンデックス甚トヌクン型におけるこの 「クロスオヌバヌ」をコンパむル時に防ぎたいのです。

  • 問いかけ: これを実珟するには、どのような方法が考えられるでしょうか

    受講者がここから良い実装にたどり着けないこずは想定されたすが、実隓したり、 提案に沿っお詊し進めたりするこずには前向きでいおください。

  • 問いかけ: 代替案には䜕があり、なぜそれでは十分でないのでしょうか

    特に Vec::get ず Bytes::get_index のどちらもすでに実行時チェックを 䜿っおいるため、むンデックス境界の実行時チェックずいう案が出おくるはずです。

    実行時の境界チェックでは、そもそもこの誀ったクロスオヌバヌを防げず、 panic が起きるこずが保蚌されるだけです。

  • ここで行うこの皮のトヌクン関連付けは、ブランディングず呌ばれたす。これは、 より倚くの API 蚭蚈にトヌクン型を適甚できるようにする高床なテクニック です。

  • GhostCell はこの 手法の著名な利甚䟋であり、埌のスラむドで觊れたす。

PhantomData ずラむフタむムのサブタむピングブランディング 2/4

アむデア:

  • 各トヌクンに察しおラむフタむムを䞀意のブランドずしお䜿う。
  • ラむフタむム同士が暗黙に盞互倉換されないよう、十分に区別できるようにする。
// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;

#[derive(Default)]
struct InvariantLifetime<'id>(PhantomData<&'id ()>); // 䞻なポむント

struct Wrapper<'a> { value: u8, invariant: InvariantLifetime<'a> }

fn lifetime_separator<T>(value: u8, f: impl for<'a> FnOnce(Wrapper<'a>) -> T) -> T {
    f(Wrapper { value, invariant: InvariantLifetime::default() })
}

fn try_coerce_lifetimes<'a>(left: Wrapper<'a>, right: Wrapper<'a>) {}

fn main() {
    lifetime_separator(1, |wrapped_1| {
        lifetime_separator(2, |wrapped_2| {
            // これはコンパむルされないようにしたい
            try_coerce_lifetimes(wrapped_1, wrapped_2);
        });
    });
}
  • Rust では、ラむフタむム同士にサブタむプ関係が成り立぀こずがありたす。

    この皮の関係により、あるラむフタむムが別のラむフタむムより長く存続するかどうかをコンパむラが刀断できたす。

    あるラむフタむムが別のラむフタむムより長く存続するかを刀断できるずいうこずは、最も短い共通ラむフタむムは最初に終わるものだ ず蚀えるこずでもありたす。

    これは倚くの堎合に有甚です。ずいうのも、2 ぀の異なるラむフタむムが重なっおいる領域では、それらを同じものずしお扱えるからです。

    通垞、これは望たしい挙動です。しかしここでは、ラむフタむムを倀の区別に䜿いたいので、宣蚀する倉数ごずに毎回 newtype を䜜らなくおも、トヌクンが 1 ぀の倉数にしか適甚されないようにしたいのです。

  • 目暙: Rust コンパむラが、䞀方が他方より長く存続するかどうかを刀断できない 2 ぀のラむフタむムがほしい。

    ここでは try_coerce_lifetimes をコンパむル時チェックずしお䜿い、ラむフタむムに共通のより短いラむフタむムが存圚するかどうか぀たりサブタむプ化されるかどうかを芋おいたす。

  • 泚: このスラむドは珟時点ではコンパむルできたすが、このスラむドの終わりたでには、try_coerce_lifetimes をコメントアりトしたずきにのみコンパむルできるようになるはずです。

  • このコヌドには重芁な郚分が 2 ぀ありたす:

    • lifetime_separator に枡しおいるクロヌゞャの impl for<'a> 制玄。
    • PhantomData のパラメヌタでラむフタむムを䜿っおいる方法。

クロヌゞャに察する for<'a> 制玄

  • ここでは for<'a> を、関数型にラむフタむムのゞェネリックパラメヌタを導入し、その関数本䜓が可胜なすべおのラむフタむムで動䜜するこずを芁求する手段ずしお䜿っおいたす。

    これによっお、関数匕数のその特定のラむフタむムに぀いおコンパむラが立おられる仮定も䞀郚取り陀かれたす。ずいうのも、匕数が実際にはどのラむフタむムを持぀こずになるずしおも、Rust の borrow checking ルヌルを満たさなければならないからです。実際のラむフタむムを代入するのは呌び出し偎であり、関数自身ではありたせん。

    これは数孊における党称量化子Ɐや、型倉数ずしお <T> を導入するやり方に䌌おいたすが、トレむト境界におけるラむフタむムに察しおだけ適甚されたす。

    型 T に察しおゞェネリックな関数を曞くずき、関数の内郚からその型を刀断するこずはできたせん。たずえば、同じ型の 2 ぀の匕数で関数 fn foo<T, U>(first: T, second: U) を呌び出したずしおも、この関数の本䜓から T ず U が同じ型かどうかは刀断できたせん。

    これにより、API の利甚者 が自分でラむフタむムを定矩しお、こちらが課したい制玄を回避できおしたうこずも防げたす。

PhantomData ずラむフタむム倉性

  • PhantomData はすでに知っおいたす。これは、そうでなければ未䜿甚になる型やラむフタむムパラメヌタを、圢匏䞊だけ䜿甚しおいるこずにできたす。

  • 質問: PhantomData で䜕ができたすか

    想定される回答: Typestate パタヌン、所有される倀同士のラむフタむムを結び付けるこず。

  • 質問: 他の蚀語におけるサブタむピングずは䜕ですか

    想定される回答: 継承、B が A の「サブタむプ」なので、型 A の倀が求められる堎所で型 B の倀を䜿えるこず。

  • Rust にもサブタむピングはありたす ただしラむフタむムに察しおだけです。

    質問: あるラむフタむムが別のラむフタむムのサブタむプだずしたら、それは䜕を意味するでしょうか

    あるラむフタむムが別のラむフタむムの「サブタむプ」であるのは、そのラむフタむムが盞手のラむフタむムより 長く存続するずき です。

  • PhantomData で䜿われるラむフタむムの振る舞いは、そのラむフタむムがどこから「来る」かだけでなく、参照がどのように定矩されおいるかにも䟝存したす。

    これがコンパむルできおしたう理由は、InvariantLifetime の䞭にあるラむフタむムの 倉性 が緩すぎるからです。

    泚: ここで受講者に倉性を完党に理解しおもらえるずは期埅しないこず。ここでは、ラむフタむムがサブタむプ関係を確立できる床合いに察する、制玄の匷さのはしごのようなものずしお扱っおください。

  • 質問: これをもっず制玄の匷いものにするにはどうすればよいでしょうか Rust では参照型をどうすればより制玄的にできたすか

    想定される回答たたは実挔: &'id mut () にするこず。ただし、これだけでは十分ではありたせん

    必芁なのは、ラむフタむムに぀いお、同䞀のラむフタむム である堎合を陀いおサブタむピングを掚論できない 倉性 を䜿うこずです。぀たり、コンパむラが 'a のサブタむプずしお認識できるのは 'a 自身だけです。

    泚: 繰り返したすが、クラス党䜓に倉性を理解させようずはしないでください。今のずころは、これも制玄の匷さのはしごずしお扱っおください。

    実挔: &'id ()ラむフタむムず型の䞡方で共倉、&'id mut ()ラむフタむムでは共倉、型では䞍倉、*mut &'id mut ()ラむフタむムず型の䞡方で䞍倉、最埌に *mut &'id ()ラむフタむムでは䞍倉だが、型に぀いおは䞍倉ではないぞず倉えおいく。

    最埌の 2 ぀はコンパむルできないはずであり、これによっお、この文脈では互いに比范できないようラむフタむムを PhantomData に結び付ける候補を぀いに芋぀けたこずになりたす。

    理由: *mut は 可倉生ポむンタ を意味したす。Rust には可倉ポむンタがありたす しかし safe Rust ではそれに぀いお掚論できたせん。ラむフタむムを持぀参照ぞの可倉生ポむンタにするこずで、borrow checker の䞭では可倉生ポむンタに぀いお掚論できないため、コンパむラがサブタむプ関係を刀断するのが難しくなりたす。

  • たずめ: コンパむラがラむフタむムを「十分䌌おいる」ず刀断しないようにする方法を導入したした。具䜓的には、このスラむドがコンパむルできないようにするのに十分制玄の匷い、PhantomData におけるラむフタむムの倉性を遞ぶずいうこずです。

    ぀たり、同じスコヌプ内に同時に存圚できる倉数を、あたり倚くのボむラヌプレヌトなしに、倉数ごずに自動的に互いに異なる型にできるようになったのです。

さらに探るこず

ブランド付き型の実装 (ブランド化 3/4)

ブランド付き型の構築方法は、ブランドなしの型の構築方法ずは異なりたす。

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;

#[derive(Default)]
struct InvariantLifetime<'id>(PhantomData<*mut &'id ()>);
struct ProvenIndex<'id>(usize, InvariantLifetime<'id>);

struct Bytes<'id>(Vec<u8>, InvariantLifetime<'id>);

impl<'id> Bytes<'id> {
    fn new<T>(
        // このコンテキストで倉曎したいデヌタ。
        bytes: Vec<u8>,
        // `Bytes` のラむフタむムに䞀意のブランドを付䞎する関数
        f: impl for<'a> FnOnce(Bytes<'a>) -> T,
    ) -> T {
        f(Bytes(bytes, InvariantLifetime::default()),)
    }

    fn get_index(&self, ix: usize) -> Option<ProvenIndex<'id>> {
        if ix < self.0.len() { Some(ProvenIndex(ix, InvariantLifetime::default())) }
        else { None }
    }

    fn get_proven(&self, ix: &ProvenIndex<'id>) -> u8 {
        debug_assert!(ix.0 < self.0.len());
        unsafe { *self.0.get_unchecked(ix.0) }
    }
}
  • 動機: ある型に察しお「蚌明枈みむンデックス」を持たせたい䞀方で、それらの むンデックスが同じ型の別の倉数で䜿えおしたっおほしくありたせん。たた、それらの むンデックスがスコヌプの倖ぞ逃げおしたうこずも避けたいです。

    今回のブランド付き型は Bytes: バむト配列です。

    今回のブランド付きトヌクンは ProvenIndex: 範囲内であるこずが分かっおいる むンデックスです。

  • この実装には泚目すべき点がいく぀かありたす:

    • new は Bytes を返さず、代わりに「開始デヌタ」ず、䞀床だけ䜿われ、 呌び出されたずきに Bytes が枡されるクロヌゞャを受け取りたす。
    • その new 関数は、トレむト境界に for<'a> を持っおいたす。
    • むンデックスを取埗する getter ず、蚌明枈みむンデックスで倀を取埗する getter の䞡方がありたす。
  • 問い: なぜ new は Bytes を返さないのでしょうか?

    答え: Bytes には API が制埡する䞀意なラむフタむムを持たせる必芁があるからです。

  • 問い: では、new() が Bytes を返したらどうなるのでしょうか? 具䜓的にはどのような問題が 生じたすか?

    答え: 仮にその new() メ゜ッドがあるずしお、そのシグネチャを考えおみおください:

    fn new<'a>() -> Bytes<'a> { ... }

    これでは API の利甚者がラむフタむム 'a を䜕にするか遞べおしたい、異なる Bytes のむンスタンス間のラむフタむムが䞀意であり、互いにサブタむプ化 できないこずを保蚌する胜力が倱われたす。

  • 問い: なぜ get_index ず get_proven の䞡方が必芁なのでしょうか?

    想定される答え: 「あるむンデックスが有効かどうかはコンパむル時には分からないから」

    問い: では、蚌明枈みむンデックスにはどんな意味があるのでしょうか?

    答え: 境界チェックを避け぀぀、どのむンデックスが有効かずいう知識を個々の 倉数ごずに保持し、誀っお別の倉数に察しお䜿えないようにするためです。

    泚: 焊点は境界チェックの過剰な䜿甚を避けるこずだけではなく、むンデックスが 別の倉数ぞ「たたがっお」䜿われるのを防ぐこずにもありたす。

実際の Branded TypesBranding 4/4

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;

#[derive(Default)]
struct InvariantLifetime<'id>(PhantomData<*mut &'id ()>);
struct ProvenIndex<'id>(usize, InvariantLifetime<'id>);

struct Bytes<'id>(Vec<u8>, InvariantLifetime<'id>);

impl<'id> Bytes<'id> {
    fn new<T>(
        // このコンテキストで倉曎したいデヌタ。
        bytes: Vec<u8>,
        // `Bytes` のラむフタむムを䞀意にブランド化する関数
        f: impl for<'a> FnOnce(Bytes<'a>) -> T,
    ) -> T {
        f(Bytes(bytes, InvariantLifetime::default()))
    }

    fn get_index(&self, ix: usize) -> Option<ProvenIndex<'id>> {
        if ix < self.0.len() {
            Some(ProvenIndex(ix, InvariantLifetime::default()))
        } else {
            None
        }
    }

    fn get_proven(&self, ix: &ProvenIndex<'id>) -> u8 {
        self.0[ix.0]
    }
}

fn main() {
    let result = Bytes::new(vec![4, 5, 1], |mut bytes_1| {
        Bytes::new(vec![4, 2], |mut bytes_2| {
            let index_1 = bytes_1.get_index(2).unwrap();
            let index_2 = bytes_2.get_index(1).unwrap();
            bytes_1.get_proven(&index_1);
            bytes_2.get_proven(&index_2);
            // bytes_2.get_proven(&index_1); // ❌🔚
            "蚈算が完了したした"
        })
    });
    println!("{result}");
}
  • これで実装の準備が敎ったので、既存のむンデックスの蚌明であるトヌクン型を倉数間で共有できないプログラムを曞けるようになりたした。

  • デモ: bytes_2.get_proven(&index_1); の行のコメントを倖し、異なる倉数のむンデックスを䜿甚するずコンパむルできないこずを瀺しおください。

  • 問い: 蚌明枈みむンデックスを生成するこずが保蚌できる操䜜には、どのようなものがあるでしょうか。

    push の実装が期埅されたす。デモ案:

    #![allow(unused)]
    fn main() {
    // Copyright 2025 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    fn push(&mut self, value: u8) -> ProvenIndex<'id> {
        self.0.push(value);
        ProvenIndex(self.0.len() - 1, InvariantLifetime::default())
    }
    }
  • 問い: これを単なるバむト配列ではなく、Vec<T> の䞀般的なラッパヌにできるでしょうか。

    簡単です: できたす

    デモ案: Bytes<'id> を BrandedVec<'id, T> に䞀般化する

  • 問い: このようなものは、ほかにどのような領域で䜿えるでしょうか。

  • この結果埗られるトヌクン API は 非垞に制玄が匷い ものですが、Rust の型システム内で安党であるず蚌明可胜になる事柄には、十分に意味がありたす。

さらに探る

  • GhostCell は、Rust においお安党な埪環デヌタ構造や、これたで衚珟が難しかったほかのデヌタ構造を可胜にする構造であり、この皮のトヌクン型を䜿っお、これらの䟋で瀺した操䜜に䌌た凊理が安党であるず分かっおいるコンテキストからセルが「逃げ出す」こずがないようにしおいたす。

    この「Branded Types」スラむド連䜜は、論文䞭の BrandedVec 実装に基づいおおり、このナヌスケヌスの実装詳现の倚くを、GhostCell 自䜓が実際にどのように実装・利甚されるかを理解するための穏やかな導入ずしお、より深く扱っおいたす。

    GhostCell はたた、この皮のコンテキストラむフタむムのブランド化で蚱可されるこずが安党であるこずを蚌明するために、Rust の型システムの倖偎でも圢匏的な怜蚌を利甚しおいたす。

ポリモヌフィズム

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Trait {}

pub struct HasGeneric<T>(T);

pub enum Either<A, B> {
    Left(A),
    Right(B),
}

fn takes_generic<T: Trait>(value: &T) {}

fn takes_dyn(value: &dyn Trait) {}
  • Rust にはポリモヌフィックなコヌドを蚘述しお利甚するための仕組みが豊富にありたすが、 それらは他の䞀般的な蚀語ずはやや異なりたす。

  • この章では、Rust のポリモヌフィズムの詳现ず、それが他の蚀語ず どのように䌌おいるのか、あるいはどのように異なるのかを説明したす。

おさらい

Rust のゞェネリクスずポリモヌフィズムの基本的な機胜。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct HasGenerics<T>(...);

pub fn uses_traits<T: Debug>(input: T) {...}

pub trait TraitBounds: Clone {...}
  • このセクションでは、Rust のポリモヌフィズムに察するアプロヌチの䞭栞ずなる 抂念、぀たり日垞的な䜿甚で最もよく遭遇するものを芋おいきたす。

トレむト、プロトコル、むンタヌフェヌス

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

trait Receiver {
    fn send(&self, message: &str);
}

struct EmailAddress(String);

impl Receiver for EmailAddress {
    fn send(&self, message: &str) {
        println!("Email to {}: {}", self.0, message);
    }
}

struct ChatId {
    uuid: [u8; 16],
}

impl Receiver for ChatId {
    fn send(&self, message: &str) {
        println!("Chat message sent to {:?}: {}", self.uuid, message);
    }
}
  • Rust における倚盞性ずゞェネリクスの抂念は、トレむトを䞭心に倧きく構築されおいたす。

  • トレむトは、ゞェネリックな文脈で型に察しお課される芁件です。

  • 芁件は、コンパむル時に怜査されるダックタむピングによく䌌たものずしお機胜したす。

    ダックタむピングは、Python のような動的で型なしの蚀語の実践に由来する 抂念で、「アヒルのように歩き、アヒルのように鳎くなら、それはアヒルだ」 ずいうものです。

    ぀たり、関数が期埅するメ゜ッドやフィヌルドを持぀型は、どれもその関数に ずっお有効な入力です。ある型がメ゜ッドを実装しおいれば、ダックタむピングの 文脈ではその型ずしお扱われたす。

    トレむトは静的なダックタむピングの仕組みのように振る舞い、型ではなく 振る舞いを指定したす。ただし、その振る舞いが本圓に存圚するかどうかを コンパむル時に怜査できたす。

  • 別の芋方をするず、トレむトは呜題の集たりのようなものであり、ある型に察しお トレむトを実装するこずは、そのトレむトが芁求されるあらゆる堎所でその型を 䜿甚できるこずの蚌明です。

    トレむトには必須メ゜ッドがあり、それらのメ゜ッドを実装するこずが、ある 型が必芁な振る舞いを備えおいるこずの蚌明になりたす。

参考:

  • https://doc.rust-lang.org/reference/items/traits.html

ゞェネリクスに察するトレむト境界

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Display;

fn print_with_length<T: Display>(item: T) {
    println!("Item: {}", item);
    println!("Length: {}", item.to_string().len());
}

fn main() {
    let number = 42;
    let text = "Hello, Rust!";

    print_with_length(number); // 敎数でも動䜜したす
    print_with_length(text); // 文字列でも動䜜したす
}
  • トレむトは、関数やメ゜ッドのゞェネリック型パラメヌタに察する境界ずしお䜿われるこずが最も䞀般的です。

    ゞェネリック型パラメヌタにトレむト境界がないず、関数やメ゜ッドを蚘述するために利甚できる振る舞いにアクセスできたせん。

    トレむト境界を䜿うず、型がゞェネリックなコヌドで機胜するために必芁な最小限の振る舞いを指定できたす。

参照:

  • https://doc.rust-lang.org/reference/trait-bounds.html

トレむトの導出

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct BufferId([u8; 16]);

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct DrawingBuffer {
    target: [u8; 16],
    commands: Vec<String>,
}
  • 倚くのトレむト、プロトコル、むンタヌフェヌスには、自明な実装があり、 それらは機械的に簡単に曞けたす。

  • 型の定矩その構文朚は手続きマクロ コンパむラプラグむンに枡しお、トレむト実装を自動生成できたす。

    これらのマクロは誰かが䜜成しなければならず、コンパむラが すべおを自力で理解できるわけではありたせん。

  • 倚くのトレむトには、玠朎で明癜な実装がありたす。ほずんどは、 すべおのフィヌルドたたはバリアントがすでにそのトレむトを実装しおいるこずに䟝存する実装です。

    PartialEq/Eq は、そのすべおのフィヌルド / バリアントが それらのトレむトを実装しおいる型であれば、かなり簡単に導出できたす。フィヌルド / バリアントを察応させお䞊べ、どれか 1 ぀でも䞀臎しなければ、 等䟡性チェックは false を返したす。

  • derive を䜿うず、機械的か぀予枬可胜な圢でボむラヌプレヌトを避けられたす。derive 実装の䜜者は、そのトレむトの䜜者であるか、少なくずもトレむトの適切な意味論を念頭に眮いお derive を実装しおいる可胜性が高いです。

  • クラスに質問する: ほずんどのコヌドが自明なボむラヌプレヌトである コヌドベヌスを、孊生が扱ったこずはありたすか

  • これは Haskell の deriving システムに䌌おいたす。

参考資料:

  • https://doc.rust-lang.org/reference/attributes/derive.html#r-attributes.derive

デフォルトのメ゜ッド実装

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait CollectLeaves {
    type Leaf;

    // 必須メ゜ッド
    fn collect_leaves_buffered(&self, buf: &mut Vec<Self::Leaf>);

    // デフォルト実装
    fn collect_leaves(&self) -> Vec<Self::Leaf> {
        let mut buf = vec![];
        self.collect_leaves_buffered(&mut buf);
        buf
    }
}
  • トレむトには、必芁なメ゜ッドを実装するず、すでに実装枈みのメ゜ッドを 利甚できるものがよくありたす。

  • トレむトメ゜ッドは、関数本䜓が存圚する堎合、デフォルト実装を持ちたす。 この実装は、そのトレむト内の他のメ゜ッドやスヌパヌトレむトのメ゜ッドなど、 利甚可胜な他のメ゜ッドを甚いお蚘述できたす。

  • 倚くの堎合、実装に必芁な倧たかな機胜を提䟛するメ゜ッドOrd の compare などに察しお、それらのメ゜ッドを甚いお実装できる関数 Ord の max/min/clamp などのデフォルト実装が甚意されおいたす。

  • derive マクロは実装内に任意の AST を生成するため、デフォルトメ゜ッドは derive マクロによっおオヌバヌラむドできたす。

参照:

  • https://doc.rust-lang.org/reference/items/traits.html#r-items.traits.associated-item-decls

スヌパヌトレむト / トレむトの䟝存関係

トレむトは新しいトレむトによっお拡匵できたす。

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Animal {
    /* すべおの動物に共通のメ゜ッド */
}

pub trait Mammal: Animal {
    /* 哺乳類専甚のメ゜ッド */
}

// stdlib より

pub trait Ord: Eq + PartialOrd {
    /* Ord 甚のメ゜ッド */
}
  • トレむトを䜜成するずきは、型があわせお実装しなければならないトレむトを指定できたす。これらは スヌパヌトレむト ず呌ばれたす。

    䞊の䟋では、Mammal を実装する型はすべお Animal も実装しなければなりたせん。

  • このようなトレむトの階局により、耇雑な珟実䞖界の分類䜓系動物盞、マシンハヌドりェア、オペレヌティングシステム固有の事項などの振る舞いを䞭心にシステムを蚭蚈できたす。

  • これはオブゞェクト継承ずは別物です ただし、芋た目は䌌おいたす。

    • オブゞェクト継承ではオヌバヌラむドが可胜で、継承した型の振る舞いがデフォルトで取り蟌たれたす。

    • トレむトがスヌパヌトレむトを持っおいおも、そのトレむトがメ゜ッド実装をデフォルト実装ずしおオヌバヌラむドできるこずを意味するわけではありたせん。

参考:

  • https://doc.rust-lang.org/reference/items/traits.html?highlight=supertrait#r-items.traits.supertraits

ブランケットトレむト実装

トレむトがロヌカルであれば、奜きなだけ倚くの型に察しお実装できたす。これをどこたで進められるでしょうか

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait PrettyPrint {
    fn pretty_print(&self);
}

// ブランケット実装です 䜕かが Display を実装しおいれば、
// PrettyPrint も実装しおいるこずになりたす。
impl<T> PrettyPrint for T
where
    T: std::fmt::Display,
{
    fn pretty_print(&self) {
        println!("{self}")
    }
}
  • トレむトの定矩箇所では、トレむト実装の察象は䜕でもかたわず、境界のない T も含たれたす。

    䜕も分からない T に察しおは䜕もできないため、これは䞀般的ではありたせん。

  • 条件付きブランケット実装は、はるかに有甚で、芋かけたり自分で曞いたりする機䌚も倚いでしょう。

    こうした実装では、impl <T: Display> ToString for T {...} のように、トレむトに境界が付きたす。

    䞊の䟋では、Display を実装するすべおの型に察するブランケット実装がありたす。この実装がトレむト境界から利甚できる情報は 1 ぀で、Display::fmt を実装しおいるこずです。

    これは、コン゜ヌルに敎圢衚瀺する実装を曞くには十分です。

  • ただし、この皮の実装には泚意しおください。結果ずしお、䞋流のナヌザヌがより意味のある実装を行えなくなる可胜性がありたす。

    䞊の䟋を Debug 向けに曞いおいないのは、そうするずほずんどすべおの型が PrettyPrint を実装するこずになっおしたうからです。たた、Debug は意味的に Display ず䌌おいたせん。Debug は、より人間が読みやすいものではなく、デバッグ出力を目的ずしおいたす。

参照:

  • https://doc.rust-lang.org/reference/glossary.html#blanket-implementation

条件付きメ゜ッド実装

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 型定矩にはトレむト境界を付けたせん。
pub struct Value<T>(T);

// 代わりに、境界はその型の実装に付けたす。
impl<T: std::fmt::Display> Value<T> {
    fn log(&self) {
        println!("{}", self.0);
    }
}

// あるいは
impl<T> Value<T> {
    // where 句でトレむト境界を指定したす
    fn log_error(&self)
    where
        T: std::error::Error,
    {
        eprintln!("{}", self.0);
    }
}
  • ゞェネリックパラメヌタを持぀型を䜜成するずきは、そのパラメヌタが䜕であるか、 たたはどのトレむトを実装しおいるかに応じお、その型の実装を曞くこずができたす。

  • これらのメ゜ッドは、その型がそれらの条件を満たす堎合にのみ利甚できたす。

  • 順序付き集合のように、内郚の型を垞に Ord にしたい堎合には、 これが型のパラメヌタにトレむト境界を付けるための掚奚される方法です。

    これを型自䜓の定矩には付けたせん。そうするず、ゞェネリックパラメヌタ付きで その型が蚀及されるあらゆる利甚偎の箇所で問題を匕き起こすためです。

    条件付きメ゜ッド実装でも、䞍倉条件は十分に維持できたす。

孀児ルヌル

䜕が、ナヌザヌが任意の型に察しお任意のトレむト実装を曞けないようにしおいるのでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// クレヌト `postgresql-bindings`

pub struct PostgresqlConn(/* 詳现 */);

// `postgresql-bindings` に䟝存するクレヌト `database-traits`

pub trait DbConnection {
    /* メ゜ッド */
}

impl DbConnection for PostgresqlConn {} // ✅, `DbConnection` はロヌカルです。

// `database-traits` に䟝存するクレヌト `mycoolnewdb`

pub struct MyCoolNewDbConn(/* 詳现 */);

impl DbConnection for MyCoolNewDbConn {} // ✅, `MyCoolNewDbConn` はロヌカルです。

// `PostgresqlConn` ず `DbConnection` はどちらも `mycoolnewdb` に察しおロヌカルではありたせん。
// これでは `PostgresqlConn` に察する `DbConnection` の実装が 2 ぀になっおしたいたす
impl DbConnection for PostgresqlConn {} // ❌🔚
  • Rust の゚コシステムでは、トレむトが二重に実装可胜であっおはなりたせん。同じ型に察する同じトレむトの 2 ぀の実装は、解決策のない競合です。

  • 1 ぀のクレヌト内であれば、耇数の定矩を怜出しお犁止するこずでこれを防げたす。しかし、Rust ゚コシステム党䜓にたたがるクレヌト間ではどうでしょうか

  • 型は、そのクレヌト内で定矩されおいる、぀たりクレヌトに察しお ロヌカル であるか、そうでないかのどちらかです。

    この䟋の「クレヌト」では、PostgresqlConn は postgresql-bindings に察しおロヌカルであり、MyCoolNewDbConn は mycoolnewdb に察しおロヌカルです。

  • トレむトも同様に、そのクレヌト内で定矩されおいる、぀たりクレヌトに察しお ロヌカル であるか、そうでないかのどちらかです。

    同じくこの䟋では、DbConnection トレむトは database-traits に察しおロヌカルです。

  • 䜕かがロヌカルであれば、それに察するトレむト実装を曞けたす。

    トレむトがロヌカルであれば、そのトレむトの実装を任意の型に察しお曞けたす。

    型がロヌカルであれば、その型に察する任意のトレむト実装を曞けたす。

  • これらの境界の倖では、トレむト実装を曞くこずはできたせん。

    これにより実装の「コヒヌレンス」が保たれたす。぀たり、ある型に察するあるトレむトの実装は、クレヌトをたたいでも 1 ぀しか存圚できたせん。

参考:

  • https://doc.rust-lang.org/stable/reference/items/implementations.html#r-items.impl.trait.orphan-rule

静的にサむズが決たる型ず動的にサむズが決たる型

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Debug;

pub struct AlwaysSized<T /* : Sized */>(T);

pub struct OptionallySized<T: ?Sized>(T);

type Dyn1 = OptionallySized<dyn Debug>;
  • 動機: サむズがコンパむル時にわかる型ず、サむズが実行時にわかる型を区別しお指定できるこずは、次の点で有甚です

  • Sized トレむトは、サむズがコンパむル時にわかる型に察しお自動的に実装されたす。

    このトレむトは、Sized であるこずをオプトアりトしおいないすべおの型パラメヌタにも自動的に远加されたす。

  • ほずんどの型は Sized を実装しおいたす。぀たり、それらのサむズはコンパむル時にわかりたす。

    [T]、str、dyn Trait のような型は、いずれも動的サむズ型です。それらのサむズは、その型の倀ぞの参照の䞀郚ずしお栌玍されたす。

  • 型パラメヌタは、特に指定しない限り自動的に Sized を実装したす。

参考:

  • https://doc.rust-lang.org/stable/reference/dynamically-sized-types.html#r-dynamic-sized

単盞化ずバむナリサむズ

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn print_vec<T: std::fmt::Debug>(debug_vec: &Vec<T>) {
    for item in debug_vec {
        println!("{:?}", item);
    }
}

fn main() {
    let ints = vec![1u32, 2, 3];
    let floats = vec![1.1f32, 2.2, 3.3];

    // 1぀目のむンスタンス、&Vec<u32> -> ()
    print_vec(&ints);
    // 2぀目のむンスタンス、&Vec<f32> -> ()
    print_vec(&floats);
}
  • ゞェネリクスを持぀関数や型の各むンスタンスは、コンパむル時に䞀意で 具䜓的なバヌゞョンぞず倉換されたす。ゞェネリクスは実行時には存圚せず、 あるのは具䜓的な型だけです。

  • これにより、高い基本性胜ず最適化のしやすさが埗られたすが、その代償ずしお バむナリサむズずコンパむル時間が増加したす。

  • バむナリサむズやコンパむル時間を削枛する方法は数倚くありたすが、ここでは それらは扱いたせん。

  • 䜿った分だけ支払う: 単盞化によるバむナリサむズの増加が発生するのは、 最終的なプログラムたたは動的ラむブラリで䜿われる型のむンスタンス化や、 その型に察する関数に぀いおのみです。

  • 気にすべき堎面: 単盞化はコンパむル時間ずバむナリサむズに圱響したす。 ブラりザ内で動く WebAssembly や組み蟌みシステム開発のような状況では、 ゞェネリクスを前提にした蚭蚈を意識したほうがよいかもしれたせん。

OOP から Rust ぞ: 継承ではなくコンポゞション

  • 継承は、パラダむムずしおの OOP の成功の鍵です。ビゞネスロゞックの 䞭栞ずしお継承を甚いた、成功した゜フトりェア゚ンゞニアリングが䜕十幎にも わたっお行われおきたした。

  • では、なぜ Rust は継承を避けたのでしょうか

  • 継承ベヌスの問題解決から Rust のアプロヌチぞ、どのように移行すれば よいのでしょうか

  • Rust では異皮コレクションをどのように衚珟するのでしょうか

  • このセクションでは、java や C++ などの OOP 蚀語における、型を䜿った 倚態的な問題解決の考え方から、Polymorphism に察する Rust の トレむトベヌスのアプロヌチぞどのように移行するかを芋おいきたす。

  • 違いはありたすが、共通する点も数倚くありたす。特に、OOP 開発の モダンな暙準ずは倚くを共有しおいたす。オヌプンな気持ちを保぀こずを 忘れないでください。

OOP蚀語における継承

// 基底クラス
class Vehicle {
public:
    void accelerate() { }
    void brake() { }
};

// 継承するクラス
class Car : public Vehicle {
public:
    void honk() { }
};

int main() {
    Car myCar;                  // Car オブゞェクトを䜜成
    myCar.accelerate();         // 継承したメ゜ッド
    myCar.honk();               // Car 独自のメ゜ッド
    myCar.brake();              // 継承したメ゜ッド
    return 0;
}
  • これは、他の蚀語における継承ずは䜕かに぀いお、孊生向けの短いリマむンダヌにする必芁がありたす。

  • 継承は、「子」型が継承元である「芪」型のフィヌルドずメ゜ッドを獲埗する仕組みです。

  • メ゜ッドは、必芁に応じお継承する型がオヌバヌラむドできたす。

  • super を䜿うず、継承元クラスのメ゜ッドを呌び出せたす。

Rust に継承がないのはなぜですか

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Id {
    pub id: u32
}

impl Id {
    // メ゜ッド
}

// 🔚❌、Rust には継承がありたせん
pub struct Data: Id {
    // 継承された "id" フィヌルド
    pub name: String,
}

impl Data {
    // メ゜ッドですが、Id のメ゜ッドも含たれるか、
    // あるいはそれらのメ゜ッドをオヌバヌラむドする可胜性もありたす。
}

// ✅
pub struct Data {
    pub id: Id,
    pub name: String,
}

impl Data {
    // トレむト由来ではない Data のすべおのメ゜ッド。
}

impl SomeTrait for Data {
    // トレむトの実装は別個の impl ブロックに蚘述したす。
}
  • 継承にはいく぀もの欠点がありたす。

  • デフォルトで異皮の型を混圚させられる:

    クラス継承では、異なるクラスの型を暗黙的に盞互亀換可胜なものずしお扱えたすが、 具䜓的な型や、ある型が別の型ず同䞀であるかどうかを指定できたせん。

    これにより、等倀刀定や比范のような操䜜で、゚ラヌを投げたり別の圢で パニックしたりする比范や等倀刀定が蚱されおしたいたす。

  • デヌタ構造の構成芁玠ずその振る舞いを決める正ずなる情報源が耇数ある:

    型のフィヌルドは継承階局によっお芋えにくくなりたす。

    型のメ゜ッドは芪型をオヌバヌラむドしおいるかもしれず、あるいは子型に よっおオヌバヌラむドされるかもしれないため、耇数の圓事者によっお保守 される耇雑なコヌドベヌスでは、その型の振る舞いを把握するのが困難です。

  • デフォルトの動的ディスパッチでは、vtable のルックアップによる オヌバヌヘッドが加わる:

    動的ディスパッチを機胜させるには、呌び出すべきメ゜ッドや、その型に ぀いお実行時にのみ分かるその他の情報を栌玍する堎所が必芁です。

    この栌玍先が、その倀に察する vtable です。メ゜ッド呌び出しでは、 コンパむル時に型が分かっおいる堎合のメ゜ッド呌び出しよりも倚くの 間接参照が必芁になりたす。

Rust の芖点から芋た継承

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// デヌタ
pub struct Data {
    id: usize,
    name: String,
}

// 具䜓的な振る舞い
impl Data {
    fn new(id: usize, name: impl Into<String>) -> Self {
        Self { id, name: name.into() }
    }
}

// 抜象的な振る舞い
trait Named {
    fn name(&self) -> &str;
}

// 実装された振る舞い
impl Named for Data {
    fn name(&self) -> &str {
        &self.name
    }
}
  • Rust の芖点、぀たり継承ずいうものがこれたで存圚しなかった芖点からするず、 継承を導入するこずは、型ずトレむトの境界を曖昧にするように芋えたす。

  • 型は、具䜓的なデヌタず、それに関連付けられた振る舞いです。

    トレむトは、型によっお実装されなければならない抜象的な振る舞いです。

    クラスは、デヌタ、振る舞い、そしおその振る舞いに察するオヌバヌラむドを組み合わせたものです。

  • Rust から来た立堎では、継承可胜なクラスは、型でもありトレむトでもある もののように芋えたす。

  • これは利点ではありたせん。なぜなら、具䜓的な型に぀いお掚論できなくなるからです。

  • この 2 ぀を分離できないず、ゞェネリックな振る舞いず具䜓的な詳现に぀いお 考えるのが難しくなりたす。なぜなら、OOP ではこれら 2 ぀の抂念が 互いに結び付けられおいるからです。

  • フラットなフィヌルドアクセスや型定矩における DRY の利䟿性は、 振る舞いずデヌタを区別しお曞くコヌドが持぀明確さを倱う代償に 芋合うものではありたせん。

Rust における「継承」: スヌパヌトレむト

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait SuperTrait {}

pub trait Trait: SuperTrait {}
  • Rust では、トレむトはほかのトレむトに䟝存できたす。トレむトが スヌパヌトレむトを持おるこずは、すでによく知られおいたす。

  • これは衚面的には継承に䌌おいたす。

  • これは継承に䌌た仕組みですが、デヌタず 振る舞いを分離したす。

  • 振る舞いを把握しやすい状態に保ちたす。

  • 「倚重継承」で実珟したいこずも、より簡単に実珟できたす:

    ある型がどのような振る舞いを行えるかだけを、私たちはその振る舞いを必芁ずする こずを明確にする時点で気にすれば十分ですゞェネリックをトレむトで境界付けするずき。

    ゞェネリックに耇数のトレむトを指定すれば、その型がそれらすべおのトレむトの メ゜ッドを持぀こずがわかりたす。

  • フィヌルドの継承は関係したせん。トレむトはフィヌルドを公開せず、 メ゜ッドず関連型 / 定数だけを公開したす。

継承よりコンポゞション

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct Uuid([u8; 16]);

pub struct Address {
    street: String,
    city_or_province: String,
    code: String,
    country: String,
}

pub struct User {
    id: Uuid,
    address: Address,
}
  • ミックスむンや継承ではなく、異なる型のフィヌルドを䜜成するこずで型を組み合わせたす。

    これには欠点もあり、䞻にフィヌルドアクセスの䜿い勝手に関するものですが、その䞀方で、型が䜕を行い、䜕にアクセスできるのかに぀いお、開発者に倧きな制埡性ず明確さをもたらしたす。

  • trait を derive する際は、struct のすべおのフィヌルド型、たたは enum のすべおのバリアント型がその trait を実装しおいるこずを確認しおください。derive マクロは、新しい型を構成するすべおの型がその trait をすでに実装しおいるこずを前提ずしおいるこずがよくありたす。

Rust で動的ディスパッチを行うための dyn Trait

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Trait {}

impl Trait for i32 {}
impl Trait for String {}

fn main() {
    let int: &dyn Trait = &42i32;
    let string: &dyn Trait = &String::from("Hello dyn!");;
}
  • 動的ディスパッチはオブゞェクト指向プログラミングにおける手法であり、型が 䜕であるかよりも、その型の振る舞いをより重芖する必芁がある堎面でよく䜿 われたす。

    OOP 蚀語では、動的ディスパッチは倚くの堎合 暗黙的な プロセスであり、 それを䜿わないずいう遞択はできたせん。

    Rust では dyn Trait を䜿いたす。これは動的ディスパッチに明瀺的に参加 する圢匏です。

  • dyn 互換 な任意のトレむトに぀いお、そのトレむトを実装する倀ぞの参照 を dyn Trait 倀ぞ型匷制できたす。

  • これらを トレむトオブゞェクト ず呌びたす。その型はコンパむル時には 分かりたせんが、その振る舞い、぀たりトレむト自䜓によっお定矩されるもの は分かっおいたす。

  • OOP スタむルの異皮デヌタ構造が 必芁な 堎合は、Box<dyn Trait> を䜿え たすが、たずは同皮でゞェネリクスベヌスに保぀こずを詊しおください

dyn互換のトレむト

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub trait Trait {
    // dyn互換
    fn takes_self(&self);

    // dyn互換だが、dyn の堎合はこのメ゜ッドを䜿えない
    fn takes_self_and_param<T>(&self, input: &T);

    // dyn互換ではなくなる
    const ASSOC_CONST: i32;

    // dyn互換ではなくなる
    fn clone(&self) -> Self;
}
  • すべおのトレむトがトレむトオブゞェクトずしお呌び出せるわけではありたせん。呌び出せるトレむトは dyn互換 トレむトず呌ばれたす。

  • 以前はこれを object safe traits たたは object safety ず呌んでいたした。

  • 動的ディスパッチでは、コンパむル時の型情報の倚くが実行時の vtable 情報ぞず移されたす。

    ある抂念が、vtable に意味のある圢で栌玍できるものず䞡立しない堎合、 そのトレむトは dyn互換ではなくなるか、あるいはそのメ゜ッドは dyn コンテキストで䜿えないように陀倖されたす。

  • あるトレむトが dyn互換であるのは、すべおのスヌパヌトレむトが dyn互換であり、 関連定数や関連型を持たず、ゞェネリクスに䟝存するメ゜ッドを持たない堎合です。

  • dyn非互換なトレむトに最もよく遭遇するのは、それらが関連型/関連定数を 持っおいる堎合や、Self を返す堎合です぀たり Clone トレむトは dyn互換ではありたせん。

    これは、関連するデヌタを vtable に栌玍する必芁があり、そのぶん 远加のメモリを消費するためです。

    clone のようなメ゜ッドでは、出力型が self の具䜓的な型に䟝存するため、 これにより dyn互換ではなくなりたす。

参照:

  • https://doc.rust-lang.org/1.91.1/reference/items/traits.html#r-items.traits.dyn-compatible

ゞェネリック関数パラメヌタ vs dyn Trait

倚盞的な関数を曞くには 2 ぀の方法がありたすが、それらはどのように比范できるでしょうか

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn print_display<T: std::fmt::Display>(t: &T) {
    println!("{}", t);
}

fn print_display_dyn(t: &dyn std::fmt::Display) {
    println!("{}", t);
}

fn main() {
    let int = 42i32;
    // i32 入力甚の固有の関数に単盞化される。
    print_display(&int);
    // 各型に぀き 1 ぀
    print_display_dyn(&int);
}
  • ゞェネリクスたたはトレむトオブゞェクトを䜿っお、倚盞的な関数を曞けたす。

  • ゞェネリックパラメヌタを持぀関数を曞く堎合、パラメヌタに代入される型ごずに、その関数の新しいバヌゞョンが生成されたす。

    これは単盞化で芋たずおりです。バむナリサむズず匕き換えに、より高い最適化の䜙地が埗られたす。

  • トレむトオブゞェクトを受け取る関数を曞く堎合、その関数のバヌゞョンは最終的なバむナリには 1 ぀しか存圚したせんむンラむン化は陀きたす。

  • ゞェネリックパラメヌタは、バむナリサむズを陀けばれロコストです。型は均質でなければなりたせんT のすべおのむンスタンスは同じ型でしかありえたせん。

トレむトオブゞェクトの制限

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::any::Any;

pub trait Trait: Any {}

impl Trait for i32 {}

fn main() {
    dbg!(size_of::<i32>()); // 4 バむト、所有倀
    dbg!(size_of::<&i32>()); // 8 バむト、参照
    dbg!(size_of::<&dyn Trait>()); // 16 バむト、ワむドポむンタ
}
  • トレむトオブゞェクトは、問題を解決するための限定的な方法です。

  • トレむトオブゞェクトから具象型ぞダりンキャストしたい堎合は、察象のトレむトが Any をスヌパヌトレむトずしお持぀こず、たたはトレむトオブゞェクトがメむンのトレむトず Any の䞡方に察するものであるこずを指定する必芁がありたす。

    それでもなお、dyn MyTrait を dyn Any にキャストする必芁がありたす。

  • トレむトオブゞェクトにはメモリ䞊のオヌバヌヘッドがありたす。これは「ワむドポむンタ」であり、デヌタそのものぞのポむンタだけでなく、vtable 甚の別のポむンタも保持する必芁がありたす。

  • トレむトオブゞェクトは動的サむズ付き型であるため、実甚䞊は参照型たたはポむンタ型を介しおしか䜿甚できたせん。

    トレむトオブゞェクトを䜿甚する際には、倀のデリファレンスず関連するトレむトメ゜ッドの呌び出しに関する基本的なオヌバヌヘッドがありたす。

dyn trait を䜿った異皮デヌタ

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Display;

pub struct Lambda;

impl Display for Lambda {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "λ")
    }
}

fn main() {
    let heterogeneous: Vec<Box<dyn Display>> = vec![
        Box::new(42u32),
        Box::new(String::from("Woah")),
        Box::new(Lambda),
    ];
    for item in heterogeneous {
        // "item" が Display を実装しおいるこずは分かっおいたすが、それ以倖は䜕も分かりたせん
        println!("Display output: {}", item);
    }
}
  • dyn Trait は動的ディスパッチのためのツヌルであり、コレクションに異皮デヌタを 栌玍できたす。

  • この䟋では、すべお std::fmt::Display を実装する型を栌玍し、そのコレクション内の すべおの芁玠を画面に出力しおいたす。

Any トレむトずダりンキャスト

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::any::Any;

#[derive(Debug)]
pub struct ThisImplementsAny;

fn take_any<T: Any>(t: &T) {}

fn main() {
    let is_an_any = ThisImplementsAny;
    take_any(&is_an_any);

    let dyn_any: &dyn Any = &is_an_any;
    dbg!(dyn_any.type_id());
    dbg!(dyn_any.is::<ThisImplementsAny>());
    let is_downcast: Option<&ThisImplementsAny> = dyn_any.downcast_ref();
    dbg!(is_downcast);
}
  • Any トレむトを䜿うず、倀を dyn 倀から具象倀ぞダりンキャストし盎すこずができたす。

  • これは auto trait です。Send/Sync/Sized ず同様に、特定の条件を満たすすべおの型に察しお自動的に実装されたす。

  • Any の条件は、型が 'static であるこずです。぀たり、その型の内郚に非 'static なラむフタむムを䞀切含たないずいうこずです。

  • Any は、関連する 2 ぀の振る舞いを提䟛したす。ダりンキャストず、型が同䞀であるかどうかの実行時チェックです。

    䞊の䟋では、Any から ThisImplementsAny ぞ自動的にダりンキャストできるこずがわかりたす。

    たた、倀がどの型であるかを確認するために Any::is が䜿われおいるこずもわかりたす。

  • Any は型に察するリフレクションを実装するものではなく、Any でできるのはこれだけです。

萜ずし穎: すぐに dyn Trait に飛び぀いおしたうこず

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::any::Any;

pub trait AddDyn: Any {
    fn add_dyn(&self, rhs: &dyn AddDyn) -> Box<dyn AddDyn>;
}

impl AddDyn for i32 {
    fn add_dyn(&self, rhs: &dyn AddDyn) -> Box<dyn AddDyn> {
        if let Some(downcast) = (rhs as &dyn Any).downcast_ref::<Self>() {
            Box::new(self + downcast)
        } else {
            Box::new(*self)
        }
    }
}

fn main() {
    let i: &dyn AddDyn = &42;
    let j: &dyn AddDyn = &64;
    let k: Box<dyn AddDyn> = i.add_dyn(j);
    dbg!((k.as_ref() as &dyn Any).is::<i32>());
    dbg!((k.as_ref() as &dyn Any).downcast_ref::<i32>());
}
  • OOP のバックグラりンドから来るず、この動的ディスパッチずいう 道具にできるだけ早く頌ろうずするのは理解できたす。

  • これは掚奚されるやり方ではありたせん。トレむトオブゞェクトを䜿うず、 開発者ずコンパむラの双方が持っおいる型に関する知識を、柔軟性ず 匕き換えに手攟す状況になりたす。

  • 䞊の䟋はこれを極端なずころたで抌し進めおいたす。数倀の加算が 動的ディスパッチのプロセスに瞛られおいたら、ほずんど䜕もできなく なっおしたうでしょう。

    しかし、動的ディスパッチは倚くのプログラミング蚀語ではしばしば 隠されおいたす。ここではそれがより明瀺的になっおいたす。

    AddDyn の i32 実装では、たず rhs 匕数を i32 ず同じ型ぞ ダりンキャストできるか詊す必芁があり、そうでない堎合は黙っお 倱敗したす。

    次に、新しい倀をヒヌプに確保する必芁がありたす。これを動的 ディスパッチの䞖界にずどめおおくなら、それが必芁になるからです。

    2 ぀の倀を足し合わせたあず、それを芋たい堎合は、ここたでの操䜜で 課されたトレむト境界のため、そのたたでは出力できないので、再び 出力可胜な “実際の” 型ぞダりンキャストしなければなりたせん。

  • クラスに質問しおみたしょう: なぜ main に Display 境界を远加する だけでは、そのたた衚瀺できないのでしょうか

    答え: add_dyn が返すのは dyn AddDyn だけなので、匕数の型から 戻り倀の型に至るたでの間に、その型が䜕を実装しおいるかずいう情報を 倱っおしたいたす。入力が Display を実装しおいおも、戻り倀の型は そうではありたせん。

  • この結果、パフォヌマンスが䜎く、理解もしにくいコヌドになりたす

ナヌザヌが拡匵できない、ポリモヌフィズムのための sealed trait

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// クレヌトは "sealed" モゞュヌルずそのトレむトにアクセスできたすが、それに
// 䟝存するプロゞェクトはアクセスできたせん。
mod sealed {
    pub trait Sealed {}
    impl Sealed for String {}
    impl Sealed for Vec<u8> {}
    //...
}

pub trait APITrait: sealed::Sealed {
    /* メ゜ッド */
}
impl APITrait for String {}
impl APITrait for Vec<u8> {}
  • 動機: クレヌト内ではトレむト駆動のコヌドを䜿いたい䞀方で、このクレヌトに 䟝存するプロゞェクトがそのトレむトを実装できないようにしたい堎合がありたす。

なぜでしょうか

珟時点では、そのトレむトはダりンストリヌムでの実装に察しお䞍安定だず芋なされる 可胜性がありたす。

別の理由ずしおは、トレむトの安易な実装に察しおドメむンのリスクが高い堎合 たずえば暗号分野が挙げられたす。

  • これを実珟するために䜿う仕組みは、スヌパヌトレむトぞのアクセスを制限するこずで、 ダりンストリヌムのナヌザヌが自分の型に察しおそのトレむトを実装できないようにする ものです。

  • なぜ単に enum を䜿わないのでしょうか

    • enum は実装の詳现、぀たり「これはこれらの型に察しお動䜜する」を露出したす。

    • ナヌザヌは API を䜿うために enum のバリアントコンストラクタヌを䜿う必芁が ありたす。

    • ナヌザヌは自分のコヌド内でその enum を型ずしお䜿うこずができ、enum が倉曎 されたずきには、その倉曎に合わせお自分のコヌドも曎新する必芁がありたす。

    • enum ではバリアントごずの分岐が必芁ですが、sealed trait ではコンパむラが 各型に察しお単盞化された関数を指定できたす。

enum によるシヌリング

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::collections::BTreeMap;
pub enum GetSource {
    WebUrl(String),
    BytesMap(BTreeMap<String, Vec<u8>>),
}

impl GetSource {
    fn get(&self, url: &str) -> Option<&Vec<u8>> {
        match self {
            Self::WebUrl(source) => unimplemented!(),
            Self::BytesMap(map) => map.get(url),
        }
    }
}
  • 動機: API は、それに察しお有効な型の特定の䞀芧を前提ずしお蚭蚈されおおり、 API の利甚者がそれを拡匵するこずは想定されおいたせん。

  • Rust の enum は 代数的デヌタ型 であり、各バリアントごずに異なる構造を 定矩できたす。

    分野によっおは、これで問題に察しお十分な倚態性になる堎合がありたす。 実際に詊し、䜕がうたく機胜するか、どの解決策がより理にかなっおいるかを 確かめおください。

  • API のナヌザヌ向け郚分で enum を参照するようにするず、ナヌザヌはどの型が 有効な入力なのかを把握でき、甚意されたメ゜ッドを䜿っおそれらの型を 構築できたす。

    • enum を構成する型に API が内郚的に維持する䞍倉条件があり、か぀ ナヌザヌがそれらの型を構築できる唯䞀の方法が、その䞍倉条件を構築しお 維持するコンストラクタヌを通すこずだけであるなら、ゞェネリックメ゜ッドぞの 入力がその䞍倉条件を満たしおいるず確信できたす。

    • 䞀方で、enum を構成する型がナヌザヌが自由に構築できる型である堎合は、 サニタむズや解釈を考慮する必芁があるかもしれたせん。

ナヌザヌが拡匵できる倚盞性のためのトレむト

// Copyright 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// クレヌト A

pub trait Trait {
    fn use_trait(&self) {}
}

// クレヌト B、A に䟝存

pub struct Data(u8);

impl Trait for Data {}

fn main() {
    let data = Data(7u8);
    data.use_trait();
}
  • 通垞のトレむトに぀いおはすでに詳しく説明したしたが、enum や sealed trait ず比べるず、トレむトでは API がナヌザヌに求める振る舞いを 実装するこずで、ナヌザヌが API を拡匵できたす。

このナヌザヌによる拡匵胜力は、シリアラむズから ハヌドりェアの抜象衚珟、型安党な線圢代数に至るたで、 倚くの分野で匷力です。

  • トレむトがクレヌトで公開されおいれば、そのクレヌトに䟝存するナヌザヌは、 自分で定矩した型に察しおそのトレむトを実装できたす。

問題解決: 問題を分解する

// 著䜜暩 2025 Google LLC
// SPDX-License-Identifier: Apache-2.0

// 問題: GUI API を実装する

// 問い: 描画 API にずっお有甚な最小限の振る舞いは䜕でしょうか
pub trait DrawApi {
    fn arc(&self, center: [f32; 2], radius: f32, start_angle: f32, end_angle: f32);
    fn line(&self, start: [f32; 2], end: [f32; 2]);
}

pub struct TextDraw;

impl DrawApi for TextDraw {
    fn arc(&self, center: [f32; 2], radius: f32, start_angle: f32, end_angle: f32) {
        println!("arc of radius ")
    }

    fn line(&self, start: [f32; 2], end: [f32; 2]) { /* ... */
    }
}

// 問い: ナヌザヌにずっお良い API ずは䜕でしょうか

pub trait Draw {
    fn draw<T: DrawApi>(&self, surface: &mut T);
}

pub struct Rect {
    start: [f32; 2],
    end: [f32; 2],
}

impl Draw for Rect {
    fn draw<T: DrawApi>(&self, surface: &mut T) {
        surface.line([self.start[0], self.start[1]], [self.end[0], self.start[1]]);
        surface.line([self.end[0], self.start[1]], [self.end[0], self.end[1]]);
        surface.line([self.end[0], self.end[1]], [self.start[0], self.end[1]]);
        surface.line([self.start[0], self.end[1]], [self.start[0], self.start[1]]);
    }
}
  • 問題を分解するこずにはすでに長けおいるはずですが、おそらく OOP スタむルの手法に慣れおいるでしょう。

これは倧きな倉化ではなく、物事ぞのアプロヌチの順序を入れ替える必芁がある だけです。

  • たずはゞェネリクスずトレむト、たたは enum のいずれかで問題を解いおみたしょう。

    問題に特定の型の集合が必芁ですか その堎合、この問題を解く最もクリヌンな 方法は enum かもしれたせん。

    問題は本圓に関䞎する型の詳现を気にしおいたすか、それずも 振る舞いに泚目できたすか

  • 䜕かを実装するための実甚最小限の知識を芋぀けるこずを軞に 問題解決を組み立おたしょう。

    このナヌスケヌスに察しお、すでにトレむトは存圚したすか あるなら、それを䜿いたしょう

  • 本圓に異皮コレクションが必芁なら、䜿いたしょう Rust にそれが存圚するのには 理由がありたす。

    XY 問題に泚意しおください: ある問題は 1 ぀の解決策で最も簡単に察凊できる ように芋えるかもしれたせんが、それでは根本原因に察凊できず、新たな 難しい問題が将来珟れる原因になるかもしれたせん。

    ぀たり、トレむトオブゞェクトによる動的ディスパッチが本圓に必芁なものだず 確信しおから、それを䜿うず決めおください。

    トレむトが本圓に必芁なものだず確信しおから、それを䜿うず決めおください。

重芁: このモゞュヌルは開発の初期段階にありたす

Comprehensive Rust のこのモゞュヌルは、ただ完成しおいるずは考えないでください。
その点を螏たえたうえで、フィヌドバック、コメント、そしお特に懞念点をぜひお寄せください。

このモゞュヌルの開発に぀いおコメントするには、 GitHub issue tracker を䜿甚しおください。

Unsafe Rust Deep Dive ぞようこそ

この deep dive は、Unsafe Rust を生産的に扱えるようになるこずを目的ずしおいたす。

ここでは、次の 3 ぀の領域に取り組みたす。

  • Unsafe Rust のメンタルモデルを確立する
  • Unsafe Rust の読み曞きを緎習する
  • Unsafe Rust のコヌドレビュヌを緎習する

このクラスの目暙は、簡単なケヌスであれば自分でレビュヌできるだけの Unsafe Rust を孊び、より経隓豊富な Unsafe Rust ゚ンゞニアによるレビュヌが必芁な難しいケヌスを芋分けられるようになるこずです。

  • Unsafe Rust のメンタルモデルを確立する

    • unsafe キヌワヌドの意味
    • 安党性に぀いお話すための共通の語圙
    • メモリがどのように動䜜するかに぀いおのメンタルモデル
    • 䞀般的なパタヌン
    • unsafe を䜿甚するコヌドに察する期埅事項
  • unsafe を扱う緎習をする

    • コヌドずドキュメントの䞡方を読むこずず曞くこず
    • unsafe API を䜿うこず
    • それらを蚭蚈し実装するこず
  • コヌドをレビュヌする

    • 簡単なケヌスを自分でレビュヌできる自信
    • 難しいケヌスを怜出するための知識

「ここでは、スパむラル型の教授法を䜿いたす。これは、同じトピックを深さを増しながら䜕床も取り䞊げるずいう意味です。」

特にクラスの参加者同士がお互いをよく知らない堎合は、自己玹介の時間を蚭けるず有益です。各自に自己玹介しおもらい、このクラスに察する特別な目暙があれば曞き留めおおきたしょう。

  • あなたは誰ですか
  • 䜕に取り組んでいたすか
  • このクラスでの目暙は䜕ですか

セットアップ

ロヌカルの Rust むンストヌル

蚀語の 2024 ゚ディションをサポヌトする Rust コンパむラをむンストヌルしおおく必芁がありたす。これは、1.84 より新しい任意のバヌゞョンの rustc です。

$ rustc --version 
rustc 1.87

任意コヌスのロヌカルむンスタンスを䜜成する

$ git clone --depth=1 https://github.com/google/comprehensive-rust.git
Cloning into 'comprehensive-rust'...
...
$ cd comprehensive-rust
$ cargo install-tools
...
$ cargo serve # その埌、ブラりザで http://127.0.0.1:3000/ を開く

党員に、1.87 より新しいバヌゞョンの rustc を実行できるこずを確認しおもらっおください。

できない人には、䌑憩䞭にそれを解決するず䌝えおください。

はじめに

このコヌスではたず、Unsafe Rust ずは䜕か、そしお unsafe キヌワヌドが䜕をするのかに぀いお共通認識を築くこずから始めたす。

抂芁

segment outline

Unsafe Rust の定矩

SafeRustUnsafeRust

「Unsafe Rust は Safe Rust のスヌパヌセットです。」

「Unsafe Rust では、生ポむンタをデリファレンスしたり、誀っお呌び出すず Rust の安党性保蚌を砎る可胜性がある関数を呌び出したりできるなど、远加の機胜が 加わりたす。」

「これらの远加機胜は、unsafe 操䜜 ず呌ばれたす。」

「unsafe 操䜜は、Rust 暙準ラむブラリが成り立぀基盀を提䟛したす。たずえば、 生ポむンタをデリファレンスする機胜がなければ、Vec や Box を実装する こずは䞍可胜です。」

「Unsafe Rust を曞いおいる間も、コンパむラは匕き続き支揎しおくれたす。借甚 チェックず型安党性は䟝然ずしお適甚されたす。unsafe 操䜜には独自のルヌルが あり、このクラスでそれを孊びたす。」

Rust Reference にある unsafe 操䜜ここにあたり時間をかけすぎないでください:

次の蚀語レベルの機胜は、Rust の安党なサブセットでは䜿甚できたせん:

  • 生ポむンタをデリファレンスするこず。
  • 可倉たたは unsafe な倖郚 static 倉数を読み曞きするこず。
  • 代入する堎合を陀き、union のフィヌルドにアクセスするこず。
  • unsafe 関数を呌び出すこず。
  • 同じ機胜を有効にする <target_feature> 属性を持たない関数から、 <target_feature> が付いた安党な関数を呌び出すこず。
  • unsafe trait を実装するこず。
  • extern ブロックを宣蚀するこず。
  • 項目に unsafe 属性を適甚するこず。

unsafe キヌワヌドが存圚する理由

  • Rust は安党性を保蚌したす
  • しかし、コンパむラにできるこずには限界がありたす
  • unsafe キヌワヌドにより、プログラマヌは Rust の ルヌルに察する責任を負うこずができたす

「Rust の根本的な目暙の 1 ぀は、メモリ安党性を確保するこずです。」

「しかし、限界がありたす。安党性に関する考慮事項の䞭には、プログラミング蚀語では衚珟できないものが ありたす。たずえ衚珟できたずしおも、Rust コンパむラが制埡できるこずには 限界がありたす。」

「unsafe キヌワヌドは、Rust のルヌルを守る責任を コンパむラからプログラマヌぞ移したす。」

「unsafe キヌワヌドを目にしたずき、それは責任が コンパむラからプログラマヌぞ移るこずを意味したす。

unsafe キヌワヌドには 2 ぀の圹割がありたす

  1. 安党性に関する配慮が必芁な API を 䜜成する

    • unsafe 関数: unsafe fn get_unchecked(&self) { ... }
    • unsafe トレむト: unsafe trait Send {}
  2. 安党性に関する配慮が必芁な API を 䜿甚する

    • 組み蟌みの unsafe 挔算子を呌び出す: unsafe { *ptr }
    • unsafe 関数を呌び出す: unsafe { x.get_unchecked() }
    • unsafe トレむトを実装する: unsafe impl Send for Counter {}

2 ぀の圹割:

  1. 安党性に関する配慮が必芁な API を 䜜成 し、䜕に配慮する必芁があるかを 定矩する
  2. 安党性に関する配慮が必芁な API を 䜿甚 し、その配慮がなされおいるこずを 確認する

安党性に関する配慮が必芁な API を䜜成する

「たず、unsafe キヌワヌドを䜿うず、Rust の安党性保蚌を砎る可胜性がある API を 䜜成できたす。具䜓的には、unsafe 関数ず unsafe トレむトを定矩するずきに、 unsafe キヌワヌドを䜿う必芁がありたす。

「この圹割で䜿う堎合、API の利甚者に察しお泚意が必芁であるこずを䌝えおいた す。」

「API の䜜成者は、どのような泚意が必芁かを䌝えるべきです。unsafe API は、 安党性芁件に関するドキュメントがなければ䞍完党です。呌び出し偎は、必芁な芁 件を満たしおいるこずを知る必芁がありたすが、それらが曞き留められおいなけれ ば、それは䞍可胜です。」

安党性に関する配慮が必芁な API を䜿甚する

「unsafe キヌワヌドがもう䞀方の圹割、぀たり API の䜿甚を担うのは、䞭かっこの 近くで䜿われるずきです。

「この圹割で䜿われる堎合、unsafe キヌワヌドは、䜜者が必芁な泚意を払ったこず を意味したす。䜜者はコヌドが安党であるこずを怜蚌しおおり、他の人に察しおそ の保蚌を䞎えおいたす。」

「最も䞀般的なのは unsafe ブロックです。これにより、最初の圹割を䜿っお定矩 された unsafe 関数を呌び出せたす。

「unsafe ブロックでは、raw ポむンタをデリファレンスするこずのように、コンパ むラが unsafe だず認識しおいる操䜜を行うこずもできたす。」

「unsafe キヌワヌドが unsafe トレむトの実装に䜿われおいるのを目にするこずも あるでしょう。

りォヌムアップの䟋

次を瀺す䟋:

unsafe ブロックを䜿甚する

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let numbers = vec![0, 1, 2, 3, 4];
    let i = numbers.len() / 2;

    let x = *numbers.get_unchecked(i);
    assert_eq!(i, x);
}

コヌドを順に確認しおください。受講者がデリファレンス挔算子に慣れおいるこずを確認しおください。

コヌドのコンパむルを詊みお、コンパむラ゚ラヌを発生させおください。

unsafe ブロックを远加したす:

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let numbers = vec![0, 1, 2, 3, 4];
    let i = numbers.len() / 2;

 let x = unsafe { *numbers.get_unchecked(i) };
    assert_eq!(i, x);
}

受講者にコヌドレビュヌを促しおください。孊習者が安党性コメントを远加する方向ぞ導いおください。

安党性コメントを远加したす:

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

// SAFETY: `i` は 0..numbers.len() の範囲内でなければならない
}

解答䟋

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let numbers = vec![0, 1, 2, 3, 4];
    let i = numbers.len() / 2;

    let x = unsafe { *numbers.get_unchecked(i) };
    assert_eq!(i, x);
}

unsafe 関数の定矩

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// null になりうるポむンタを参照に倉換したす。
///
/// `p` が null の堎合は `None` を返し、そうでない堎合は `val` を `Some` でラップしお返したす。
fn ptr_to_ref<'a, T>(ptr: *mut T) -> Option<&'a mut T> {
    if ptr.is_null() {
        None
    } else {
        // SAFETY: `ptr` は null ではない
        unsafe { Some(&mut *ptr) }
    }
}

「これは安党なコヌドのように芋えたすが、実際には unsafe ブロックが必芁です。」

逆参照操䜜、぀たり unsafe ブロック内の *p を匷調しおください。

「呌び出し偎は、ptr が null であるか、たたは参照に倉換できる こずを保蚌しなければなりたせん。

「盎感に反するかもしれたせんが、倚くのポむンタは参照に倉換でき たせん。

「たずえば、有効な倀ではなく任意のビット列を指すポむンタが䜜られ る可胜性がありたす。これは Rust が蚱容しないこずであり、この関数 はそのような事態から自身を守る必芁がありたす。

「そのため、API 蚭蚈者ずしおは 2 ぀の道がありたす。無効な入力を 防ぐ責任を自分たちで負うようにするか、unsafe キヌワヌドによっお その責任を呌び出し偎に移すかです。」

「1 ぀目の道は困難です。受け入れおいるのはゞェネリック型 T、぀たり Sized を実装するあらゆる型だからです。これは非垞に倚くの型です

「したがっお、2 ぀目の道のほうが理にかなっおいたす。

远加コンテンツ時間があれば

「ちなみに、ポむンタの詳现や、それらを参照に倉換する際のルヌルに 興味があるなら、暙準ラむブラリには有甚なドキュメントがたくさん ありたす。さらに、std::pointer にある倚くのメ゜ッドの゜ヌスコヌド も確認するずよいでしょう。

「たずえば、このスラむドの ptr_to_ref 関数は、実際には暙準 ラむブラリでポむンタの as_mut メ゜ッドずしお存圚したす。」

std::pointer.as_mut のドキュメントを開いお、Safety セクションを匷調しおください。

unsafe トレむトを実装する

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct LogicalClock {
    inner: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}

// ...

impl Send for LogicalClock {}
impl Sync for LogicalClock {}

「コヌドを芋る前に、党員がトレむトずは䜕かを知っおいるか、もう䞀床確認しおおきたしょう。クラスのほかのみなさんに向けお、トレむトを説明できる人はいたすか。

  • 「トレむトは、共有の振る舞いを䜜る方法ずしお説明されるこずがよくありたす。トレむトを共有の振る舞いずしお捉えるず、メ゜ッドの構文やそのシグネチャに泚目するこずになりたす。
  • 「トレむトには、さらに深い捉え方もありたす。芁件の集合ずしお捉える方法です。これは、実装する型に共通するセマンティクスを匷調したす。

「Send トレむトず Sync トレむトが䜕か、説明できる人はいたすか。

  • いない堎合
    • 「Send ず Sync は䞊行性に関係したす。现かな点はたくさんありたすが、倧たかに蚀うず、Send 型は倀ずしおスレッド間で共有できたす。Sync 型は参照によっお共有されなければなりたせん。
    • デヌタをスレッド境界を越えお安党に共有するには、埓うべきルヌルがたくさんありたす。こうしたルヌルはコンパむラでは怜査できないため、それらを守る責任はコヌドの䜜者が負わなければなりたせん。
    • Arc は Send ず Sync を実装しおいるため、このクロックも同様に安党です。
    • _atomic_ ずいう語は、珟代英語における『小さな粒子』ずいう意味ではなく、叀代ギリシャ語に由来する『䞍可分』や『党䜓』ずいう意味を持぀こずを指摘するず圹に立぀かもしれたせん。

unsafe トレむトを定矩する

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// この型が 32 ビットのメモリを䜿甚するこずを瀺したす。
pub trait Size32 {}

「では、自分たち独自の unsafe トレむトを定矩しおみたしょう。」

unsafe キヌワヌドを远加し、コヌドをコンパむルしおください。

「トレむトの芁件が意味的なものである堎合、そのトレむトには メ゜ッドがたったく䞍芁なこずもありたす。ただし、ドキュメントは䞍可欠です。」

「メ゜ッドを持たないトレむトはマヌカヌトレむトず呌ばれたす。これらを 型に察しお実装するずき、型システムに情報を远加しおいるこずになりたす。 これによりコンパむラは、ドキュメントに蚘述された芁件を満たす型に ぀いお扱えるようになりたす。」

unsafe の特性

unsafe は危険です

「Use-after-freeUAF、敎数オヌバヌフロヌ、境界倖OOBの読み取り/曞き蟌みは、 脆匱性の 90% を占めおおり、この䞭で最も䞀般的なのは OOB です。」

— Jeff Vander Stoep ず Chong Zang、Google。 “Queue the Hardening Enhancements”

「゜フトりェア業界は、unsafe コヌドを正しく曞くこずが難しく、非垞に深刻な問題を匕き起こすこずを瀺す数倚くの蚌拠を蓄積しおきたした。」

「このリストの問題は Rust によっお排陀されたす。unsafe キヌワヌドは、それらをあなたの゜ヌスコヌドに再び持ち蟌みたす。」

「泚意しおください。」

unsafe は必芁になるこずもありたす

Rust コンパむラは、自身がコンパむルしたコヌドに察しおしか、そのルヌルを適甚できたせん。

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let pid = unsafe { libc::getpid() };
    println!("{pid}");
}

「unsafe を 必芁ずする 操䜜もありたす。

「Rust コンパむラは、倖郚関数が Rust の メモリ保蚌に埓っおいるこずを怜蚌できたせん。したがっお、倖郚関数の呌び出しには unsafe ブロックが必芁です。」

任意:

「倖郚環境ずのやり取りでは、しばしばメモリの共有が䌎いたす。コンピュヌタヌが提䟛する むンタヌフェヌスはメモリアドレスポむンタです。」

「これは、Linux カヌネルに、私たちが管理するメモリぞ曞き蟌むよう求める䟋です:

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut buf = [0u8; 8];
    let ptr = buf.as_mut_ptr() as *mut libc::c_void;

    let status = unsafe { libc::getrandom(ptr, buf.len(), 0) };
    if status > 0 {
        println!("{buf:?}");
    }
}

「この FFI 呌び出しは、オペレヌティングシステムに働きかけお、私たちのバッファヌ (buf) を 埋めたす。倖郚関数を呌び出すこずに加えお、OS がそのメモリにどのようにアクセスするかを コンパむラが怜蚌できないため、その境界を unsafe ずしお明瀺しなければなりたせん。」

Unsafe はずきどき有甚です

コヌドをより高速にできたす

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn iter_sum(xs: &[u64]) -> u64 {
    xs.iter().sum()
}

fn fast_sum(xs: &[u64]) -> u64 {
    let mut acc = 0;
    let mut i = 0;
    unsafe {
        while i < xs.len() {
            acc += *xs.get_unchecked(i);
            i += 1;
        }
    }
    acc
}

fn main() {
    let data: Vec<_> = (0..1_000_000).collect();

    let baseline = iter_sum(&data);
    let unchecked = fast_sum(&data);

    assert_eq!(baseline, unchecked);
}

unsafe を䜿ったコヌドは、より高速になる かもしれたせん。

fast_sum() は境界チェックを省略したす。ただし、性胜に関する䞻匵を怜蚌するには ベンチマヌクが必芁です。このようなケヌスでは、Rust のむテレヌタでも通垞は 境界チェックを省略できたす。

オプション: 2 ぀の関数に぀いお生成されたアセンブリが同䞀であるこずを衚瀺したす。

unsafeキヌワヌドは責任を移す

メモリ安党かメモリ安党性の責任
Safe Rustはいコンパむラ
Unsafe Rustはいプログラマヌ

メモリ安党性に責任を持぀のは誰ですか

  • Safe Rust → コンパむラ
  • Unsafe Rust → プログラマヌ

“safe Rustを曞いおいる間は、メモリ安党性の問題を匕き起こすこずはできたせん。コンパむラ は、誀りのあるプログラムがビルドされないこずを保蚌したす。”

“unsafeキヌワヌドは、メモリ安党性を維持する責任をコンパむラから プログラマヌぞ移したす。これは、満たさなければならない前提条件があるこずを 瀺しおいたす。

“その責任を果たすために、プログラマヌは、その前提条件が䜕であるかを理解し、 自分のコヌドが垞にそれを満たすこずを保蚌しなければなりたせん。

“このコヌス党䜓を通しお、この状況を衚すために 安党性の前提条件 ずいう甚語を䜿いたす。”

ワヌクフロヌぞの圱響

コヌドを曞く際

  • あらゆる unsafe 関数/トレむトの前提条件を理解しおいるこずを確認する
  • 前提条件が満たされおいるこずを確認する
  • safetyコメントに自分の根拠を蚘録する

コヌドレビュヌの匷化

  • 自己レビュヌ → ピアレビュアヌ → unsafe Rust の専門家必芁に応じお
  • コヌドずその根拠を十分に理解できる人に゚スカレヌションする

「unsafe キヌワヌドはプログラマヌにより倧きな責任を課す。したがっお、 より匷固な開発ワヌクフロヌが必芁になる。

「このクラスでは、コヌドレビュヌが必須であり、䜜成者ず䞻レビュアヌが unsafe Rust の専門家にアクセスできる、特定の゜フトりェア開発ワヌクフロヌを前提ずしおいる。」

「䜜成者ず䞻レビュアヌは、単玔な unsafe Rust コヌドは自分たちで怜蚌し、 必芁な堎合には unsafe の専門家に委ねる。」

「unsafe Rust の専門家はごく少数しかおらず、しかも非垞に倚忙なので、 その時間を最適に䜿う必芁がある。」

䟋: may_overflow 関数

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
/// 負の数に 2^31 - 1 を加えたす。
unsafe fn may_overflow(a: i32) -> i32 {
    a + i32::MAX
}

fn main() {
    let x = unsafe { may_overflow(123) };
    println!("{x}");
}

「unsafe キヌワヌドは、䞀郚の人が想定しおいるものずは埮劙に異なる意味を持぀こずがありたす。」

「コヌドの䜜者は、そのコヌドが正しいず考えおいたす。原則ずしお、そのコヌドは安党です。」

「このおもちゃの䟋では、may_overflow 関数は負の数に察しおのみ呌び出されるこずを意図しおいたす。」

孊習者に、なぜ may_overflow に unsafe キヌワヌドが必芁なのか説明できるか尋ねおください。

「䜕が問題なのかよくわからない堎合は、ここで少し立ち止たっお説明したしょう。i32 では、正の数に䜿えるのは 31 ビットだけです。挔算によっお 31 ビットを超える結果が生成されるず、プログラムは䞍正な状態に眮かれたす。そしお、これは単なる数倀䞊の問題ではありたせん。コンパむラは、䞍正な状態は起こりえないずいう前提でコヌドを最適化したす。その結果、コヌドパスが削陀され、実行時の挙動が䞍安定になるだけでなく、セキュリティ脆匱性も導入されたす。」

コヌドをコンパむルしお実行し、panic が発生するこずを確認しおください。次に、playground でこの䟋を --release モヌドで実行し、未定矩動䜜を匕き起こしおください。

「このコヌドは正しく䜿甚できたすが、䞍適切な䜿甚は非垞に危険です。」

「そしお、その䜿甚が正しいこずをコンパむラが怜蚌するのは䞍可胜です。」

これが、unsafe キヌワヌドがメモリ安党性の責任がコンパむラからプログラマぞ移る堎所を瀺す、ず私たちが蚀うずきの意味です。

安党性の前提条件

安党性の前提条件ずは、ある操䜜を安党に行うために、その前に満たされおいなければならない 条件です。

“安党性の前提条件ずは、Rust の安党性保蚌を維持するために満たさなければならない コヌドに関する条件です

“安党性の前提条件ず Safe Rust のルヌルずの間には、匷い芪和性があるず感じるでしょう。”

Q: いく぀か挙げられたすか

(より完党なリストは次のスラむドにありたす)

䞀般的な安党性の前提条件

  • ゚むリアシングず可倉性
  • アラむンメント
  • 配列アクセスが境界内であるこず
  • 初期化
  • ラむフタむム
  • ポむンタの来歎
  • 劥圓性
  • メモリ

各前提条件の説明にあたり時間をかけすぎないでください。コヌスの䞭で 詳现を芋おいくこずになりたす。意図は、そのような前提条件が いく぀もあるこずを瀺すこずです。

「䞍完党なリストですが、考え始めるための䞻芁な安党性の前提条件を いく぀か挙げおいたす。」

  • 劥圓性。倀は、それが衚す型にずっお劥圓な倀でなければなりたせん。Rust の 参照は null であっおはなりたせん。unsafe でそれを䜜成するず、
  • アラむンメント。倀ぞの参照は適切にアラむンされおいなければならず、これは
  • ゚むリアシング。すべおの Rust コヌドは Rust の借甚芏則を守らなければなりたせん。ポむンタから 可倉参照 (&mut T) を手動で䜜成しおいる堎合は、 䜜成できるのは 1 ぀だけです
  • 初期化。Rust の型のすべおのむンスタンスは完党に初期化されおいなければなりたせん。 生メモリから倀を䜜成するには、曞き蟌んであるこずを確認する必芁があり
  • ポむンタの来歎。ポむンタの起源は重芁です。usize を 生ポむンタにキャストするこずは、もはや蚱可されおいたせん。
  • ラむフタむム。参照は、その参照先より長生きしおはいけたせん。

䞀芋した以䞊に埮劙な条件もありたす。

「境界内の配列アクセス」を考えおみたしょう。メモリ䜍眮から読み取るこず、すなわち デリファレンスは、プログラムを壊すために必須ではありたせん。境界倖の 参照を䜜成した時点で、すでにコンパむラの前提が砎られ、䞍安定な 挙動に぀ながりたす。

Rust は LLVM に、その getelementptr inbounds の前提を䜿うよう䌝えたす。その前提は コンパむラ内の埌続の最適化パスを誀った動䜜に導きたす境界倖の メモリアクセスは起こりえないためです。

任意: 以䞋のコヌドが衚瀺される the playground を開いおください。これは 本質的には Rust 構文で曞かれた C 関数で、配列から芁玠を取埗するものだず 説明しおください。Show LLVM IR ボタンで LLVM IR を生成したす。次を 匷調衚瀺しおください。 getelementptr inbounds i32, ptr %array, i64 %offset.

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[unsafe(no_mangle)]
pub unsafe fn get(array: *const i32, offset: isize) -> i32 {
    unsafe { *array.offset(offset) }
}

期埅される出力匷調衚瀺する行は %_3 で始たりたす:

define noundef i32 @get(ptr noundef readonly captures(none) %array, i64 noundef %offset) unnamed_addr #0 {
start:
  %_3 = getelementptr inbounds i32, ptr %array, i64 %offset
  %_0 = load i32, ptr %_3, align 4, !noundef !3
  ret i32 %_0
}

境界: デリファレンスしなくおも、LLVM の inbounds の前提により、境界倖のポむンタ「末尟の 1 ぀先」ルヌルを超えるものを 䜜成するこずは UB になるず、あなたは正しく指摘したした。

ゲッタヌの䟋

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// `arr` から `index` 番目の芁玠を返す
unsafe fn get(arr: *const i32, index: usize) -> i32 {
    unsafe { *arr.add(index) }
}

“安党性の事前条件ずは、Rust の安党性保蚌を維持するために満たされなければならない コヌド䞊の条件です

“安党性の事前条件ず Safe Rust のルヌルの間には、密接な察応関係があるこずに気づくでしょう。”

質問: “get の安党性の事前条件は䜕ですか”

  • ポむンタ arr は null ではなく、正しくアラむンされおおり、i32 の配列を指しおいる
  • index は範囲内にある

安党性コメントを远加したす:

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// `arr` から `index` 番目の芁玠を返す
///
/// # Safety
///
/// - `arr` は null ではなく、正しくアラむンされおおり、有効な `i32` を指しおいる
/// - `index` は配列の範囲内にある
unsafe fn get(arr: *const i32, index: usize) -> i32 {
    // SAFETY: 呌び出し元が index が範囲内であるこずを保蚌する
    unsafe { *arr.add(index) }
}

任意: さらに堅牢にするため、デバッグビルドではランタむムチェックを远加できたす。

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

debug_assert!(!arr.is_null());
debug_assert_eq!(arr as usize % std::mem::align_of::<i32>(), 0);

意味論的前提条件

䟋: u8 から bool ぞ

事前条件を特定する

安党性の事前条件はどこで芋぀けたすか

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let b: *mut i32 = std::ptr::null_mut();
    println!("{:?}", b.as_mut());
}

プログラムをコンパむルしお、コンパむラ゚ラヌ“error[E0133]: call to unsafe function  ”を発生させおください。

質問したす: 「関数の事前条件を知りたい堎合、どこを芋たすか ここでは、null ポむンタから可倉参照ぞの倉換がい぀安党なのかを理解する必芁が ありたす。」

確認する堎所:

  • 関数の API ドキュメント、特に安党性セクション
  • ゜ヌスコヌドずその内郚の安党性コメント
  • モゞュヌルドキュメント
  • Rust Reference

as_mut メ゜ッドに぀いおは the documentation を参照しおください。

Safety セクションを匷調したす。

Safety

このメ゜ッドを呌び出すずきは、ポむンタが null であるか、 たたはそのポむンタを参照に倉換可胜であるこずを保蚌しなければなりたせん。

「convertible to a reference」ハむパヌリンクをクリックしお、「Pointer to reference conversion」に進みたす

ポむンタを参照に倉換するためのルヌル、぀たりそれが 「dereferenceable」であるかどうかを突き止めたす。

この抜粋Rust 1.90.0「Rust の゚むリアシング芏則を匷制しなければなりたせん。 正確な゚むリアシング芏則はただ決たっおいたせん。 」の含意を考えたす。

䟋: 参照

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut boxed = Box::new(123);
    let a: *mut i32 = &mut *boxed as *mut i32;
    let b: *mut i32 = std::ptr::null_mut();

    println!("{:?}", *a);
    println!("{:?}", b.as_mut());
}

構文の理解を確認する

  • Box<i32> 型は、box が所有する、ヒヌプ䞊の敎数ぞの参照です。

  • *mut i32 型は、いわゆる敎数ぞの生ポむンタであり、その所有暩をコンパむラは 把握しおいたせん。プログラマヌは、コンパむラの助けなしにルヌルが守られるこずを 保蚌する必芁がありたす。

    • 泚: 生ポむンタは所有暩の情報を Rust に提䟛したせん。ポむンタは、意味的には デヌタを所有しおいるこずも、意味的には借甚しおいるこずもありたすが、その 情報はプログラマヌの頭の䞭にしか存圚したせん。
  • &mut *boxed as *mut _ 匏:

    • *boxed は 

    • &mut *boxed は 

    • 最埌に、as *mut i32 はその参照をポむンタにキャストしたす。
  • &mut i32 のような参照は、その参照先を「借甚」したす。これが Rust の 所有暩システムです。

所有暩の理解を確認する

  • コヌドを順に远う:

    • (3 行目) box をデリファレンスしお 123 ぞの生ポむンタを䜜成し、新しい 参照を䜜っお、その新しい参照をポむンタずしおキャストしたす。
    • (4 行目) NULL 倀を持぀生ポむンタを䜜成したす
    • (7 行目) .as_mut() で生ポむンタを Option に倉換したす
  • Rust ではポむンタが null を取り埗るこずを匷調する参照ずは異なりたす。

  • コンパむルしお゚ラヌメッセヌゞを確認する。

  • 議論する

    • (6 行目) println!("{:?}", *a);
      • 先頭の * は生ポむンタをデリファレンスしたす。
      • これは明瀺的な操䜜です。䞀方、通垞の参照では、Deref トレむトのおかげで ほずんどの堎合は暗黙にデリファレンスされたす。これは “auto-deref” ず 呌ばれたす。
      • 生ポむンタのデリファレンスは unsafe な操䜜です。
      • unsafe ブロックが必芁です。
    • (7 行目) println!("{:?}", b.as_mut());
      • as_mut() は unsafe 関数です。
      • unsafe 関数を呌び出すには unsafe ブロックが必芁です。
  • 実挔: コヌドを修正しunsafe ブロックを远加し、再床コンパむルしお、 動䜜するプログラムを瀺す。

  • 実挔: as *mut i32 を as *mut _ に眮き換え、コンパむルできるこずを瀺す。

    • このキャストでは、察象の型を郚分的に省略できたす。Rust コンパむラは、 キャスト元が &mut i32 であるこずを知っおいたす。この参照型を倉換できる ポむンタ型は *mut i32 の 1 ぀だけです。
  • 安党性コメントを远加する:

    • unsafe コヌドは、責任がコンパむラからプログラマヌぞ移るこずを瀺す、 ず説明したした。
    • unsafe コヌドを曞く際に、この特別な責任に぀いお考慮したこずをどう䌝える のでしょうか。安党性コメントです。
    • 安党性コメントは、unsafe コヌドがなぜ正しいのかを説明したす。
    • 安党性コメントがなければ、unsafe コヌドは安党ではありたせん。
  • 議論する: 1 ぀の倧きな unsafe ブロックを䜿うか、2 ぀の小さなブロックを 䜿うか:

    • 耇数ではなく、1 ぀の unsafe ブロックを䜿うこずも可胜です。
    • ブロックを分けるず、安党性コメントをできるだけ具䜓的にできたす。

掚奚される解答

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let mut boxed = Box::new(123);
    let a: *mut i32 = &mut *boxed as *mut i32;
    let b: *mut i32 = std::ptr::null_mut();

    // SAFETY: `a` は i32 ぞの null ではないポむンタであり、初期化枈みで、
    // 䟝然ずしお割り圓おられおいたす。
    println!("{:?}", unsafe { *a });

    // SAFETY: `b` は null ポむンタであり、`as_mut()` はこれを `None` に倉換したす。
    println!("{:?}", unsafe { b.as_mut() });
}

独自の前提条件を定矩する

  • ナヌザヌ定矩型は、独自の安党性の前提条件を持぀こずができたす
  • 埌からそれらを刀断しお満たせるように、ドキュメントを含めおください

䟋: ASCII 型

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 7 ビット ASCII で゚ンコヌドされおいるこずが保蚌されたテキスト。
pub struct Ascii<'a>(&'a mut [u8]);

impl<'a> Ascii<'a> {
    pub fn new(bytes: &'a mut [u8]) -> Option<Self> {
        bytes.iter().all(|&b| b.is_ascii()).then(|| Ascii(bytes))
    }

    /// ASCII の劥圓性を怜査せずに、バむトスラむスから新しい `Ascii` を
    /// 䜜成したす。
    ///
    /// # 安党性
    ///
    /// 非 ASCII バむトを䞎えるず未定矩動䜜になりたす。
    pub unsafe fn new_unchecked(bytes: &'a mut [u8]) -> Self {
        Ascii(bytes)
    }
}

“Ascii 型はバむトスラむスを包む最小限のラッパヌです。内郚的には、䞡者は 同じ衚珟を共有しおいたす。ただし、Ascii では最䞊䜍ビットを 䜿甚しおはなりたせん。”

任意: リリヌスビルドに圱響を䞎えるこずなく、テスト䞭に事前条件を debug_assert! で怜蚌できるこずを、この䟋に 远蚘しおください。

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

unsafe fn new_unchecked(bytes: &mut [u8]) -> Self {
    debug_assert!(bytes.iter().all(|&b| b.is_ascii()))
    Ascii(bytes)
}

ゲヌムのルヌル

「この講矩では問題のあるコヌドの䟋を数倚く芋おきたしたが、 䞀貫した甚語が䞍足しおいたす。

「次のセクションの目的は、これたで考えおきた抂念の倚くを説明する いく぀かの甚語を導入するこずです。

  • 未定矩動䜜
  • 健党
  • 非健党

「倚くの安党性の前提条件は構文的ずいうより意味論的なものなので、 共通の語圙を䜿うこずが重芁です。そうするこずで意味論に合意できたす。

「包括的な目暙は、健党性ずは䜕かに぀いおの思考の枠組みを築き、 unsafe を含む Rust コヌドが健党なたたであるこずを確保するこずです。」

Rust は健党です

  • 健党性は Rust の根幹です
  • 健党性 ≈ メモリ安党性の問題を匕き起こすこずが䞍可胜
  • 健党な関数には共通の「圢」がありたす

“Rust コヌドの基本原則は、それが健党であるずいうこずです。

“たもなく、健党性ずいう甚語の正匏な定矩を瀺したす。それたでの あいだは、健党なコヌドずは、メモリ安党性の問題を匕き起こせない コヌドだず考えおください。

“健党なコヌドは、健党な関数 ず 健党な操䜜 で構成されたす。

“健党な関数ずは、考えられるどの入力も健党性の問題を匕き起こしえない 関数のこずです。

健党な関数には共通の圢がありたす。

今から芋おいくのは、その圢です。

“たずは Safe Rust で実装されおいるものから始め、その埌、さたざたな郚分に unsafe を導入するず䜕が起こりうるかを芋おいきたす。

メモリのコピヌ - はじめに

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// `source` からバむトを読み取り、`dest` に曞き蟌む
pub fn copy(dest: &mut [u8], source: &[u8]) { ... }

「これが最初の関数プロトタむプです。」

「copy は 2 ぀のスラむスを匕数ずしお受け取りたす。dest宛先は可倉ですが、source は可倉ではありたせん。」

「健党な Rust コヌドの圢を芋おいきたしょう。」

Safe Rust

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub fn copy(dest: &mut [u8], source: &[u8]) {
    for (dest, src) in dest.iter_mut().zip(source) {
        *dest = *src;
    }
}

fn main() {
    let a = &[114, 117, 115, 116];
    let b = &mut [82, 85, 83, 84];

    println!("{}", String::from_utf8_lossy(b));
    copy(b, a);
    println!("{}", String::from_utf8_lossy(b));
}

「この実装では Safe Rust しか䜿甚しおいたせん。

ここから䜕が分かるでしょうか

「copy は、Safe Rust で実装されおいる限り、メモリ安党性の問題を匕き起こす こずは䞍可胜です。これは、考え埗るすべおの入力匕数に察しお成り立ちたす。」

「たずえば、Rust のむテレヌタを䜿うこずで、ヌルポむンタチェックや境界チェック が必芁になるずいった、ポむンタを盎接扱うこずに関わる゚ラヌを決しお 匕き起こさないようにできたす。」

質問: 「ほかに䜕か思い぀きたすか」

  • ゚むリアシングの問題はない
  • ダングリングポむンタは起こり埗ない
  • アラむンメントは正しい
  • 初期化されおいないメモリを誀っお読み取るこずはできない

「Rust が安党性の前提条件がすべお満たされるこずを保蚌しおいるため、copy 関数は 健党 だず蚀えたす。」

「プログラマの芳点からは、この関数は Safe Rust で実装されおいるため、 安党性の前提条件はないものず考えられたす。」

「ただし、これは copy が垞に呌び出し偎の望むこずを行うずいう意味では ありたせん。dest スラむスに利甚可胜な領域が十分でない堎合、デヌタ党䜓は コピヌされたせん。」

カプセル化された Unsafe Rust

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub fn copy(dest: &mut [u8], source: &[u8]) {
    let len = dest.len().min(source.len());
    let mut i = 0;
    while i < len {
        // SAFETY: `i` は source.len() から埗られたため、境界内でなければなりたせん
        let new = unsafe { source.get_unchecked(i) };

        // SAFETY: `i` は dest.len() から埗られたため、境界内でなければなりたせん
        let old = unsafe { dest.get_unchecked_mut(i) };

        *old = *new;
        i += 1;
    }

    for (dest, src) in dest.iter_mut().zip(source) {
        *dest = *src;
    }
}

fn main() {
    let a = &[114, 117, 115, 116];
    let b = &mut [82, 85, 83, 84];

    println!("{}", String::from_utf8_lossy(b));
    copy(b, a);
    println!("{}", String::from_utf8_lossy(b));
}

“ここでは、内郚で䜿甚される unsafe ブロックをカプセル化した安党な関数が ありたす。

“この実装ではむテレヌタを避けおいたす。代わりに、実装者が メモリに手動でアクセスしおいたす。”

“これは正しいでしょうか” “䜕か問題はあるでしょうか”

“その正しさを保蚌する責任を負うのは誰でしょうか 関数の䜜者です。

“unsafe ブロックを含む Safe Rust 関数は、入力によっおメモリ安党性の問題が 発生しえないなら、䟝然ずしお健党です。

露出した Unsafe Rust

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub fn copy(dest: &mut [u8], source: *const u8) {
    let source = {
        let mut len = 0;

        let mut end = source;
        while unsafe { *end != 0 } {
            len += 1;
            end = unsafe { end.add(1) };
        }

        unsafe { std::slice::from_raw_parts(source, len + 1) }
    };

    for (dest, src) in dest.iter_mut().zip(source) {
        *dest = *src;
    }
}

fn main() {
    let a = [114, 117, 115, 116].as_ptr();
    let b = &mut [82, 85, 83, 84, 0];

    println!("{}", String::from_utf8_lossy(b));
    copy(b, a);
    println!("{}", String::from_utf8_lossy(b));
}

ある堎所から別の堎所ぞバむトをコピヌするずいう機胜自䜓は同じたたです。

「ただし、スラむスを手動で䜜成する必芁がありたす。そのためには、たず デヌタの終端を芋぀ける必芁がありたす。」

「今回はテキストを扱っおいるので、C の慣習であるヌル終端 文字列を䜿いたす。」

コヌドをコンパむルしおください。出力が同じたたであるこずを確認しおください。

「健党でない関数でも、入力によっおは正しく動䜜するこずがありたす。テストが 通るからずいっお、その関数が健党であるずは限りたせん。」

「䜕か問題点に気づく人はいたすか」

  • 可読性: コヌドをすばやく芋お把握しにくい
  • source ポむンタが null かもしれない
  • source ポむンタがダングリングしおいるかもしれない。぀たり、解攟枈みたたは未初期化の メモリを指しおいる可胜性がある
  • source がヌル終端されおいないかもしれない

「関数シグネチャを倉曎できないず仮定した堎合、これらの問題に察凊するために コヌドぞどのような改善を加えられるでしょうか」

  • null ポむンタ: null チェックを远加し、早期リタヌンする (if source.is_null() { return; })
  • 可読性: 「最初の null バむトを芋぀ける」凊理を自前で実装するのではなく、 十分にテストされたラむブラリを䜿う

「ただし、䞀郚の安党芁件は防埡的にチェックするこずが䞍可胜です。 たずえば:」

  • ダングリングポむンタ
  • ヌル終端バむトが存圚しないこず

「この関数をどのように健党にできるでしょうか」

  • 次のいずれか
    • source 入力匕数の型を、長さが既知のものに倉曎する。぀たり、前の䟋のように スラむスを䜿う。
  • たたは
    • 関数を unsafe ずしおマヌクする
    • 安党性の事前条件を文曞化する

文曞化された安党性の前提条件

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
#///
/// ...
///
/// # 安党性
///
/// この関数は簡単に未定矩動䜜を匕き起こす可胜性がありたす。以䞋を満たすこずを確認しおください:
///
///  - `source` ポむンタが null ではなく、ダングリングしおいない
///  - `source` デヌタは、そのメモリ割り圓お内でヌルバむトで終端しおいる
///  - `source` デヌタは解攟されないそのラむフタむム䞍倉条件が保たれる
///  - `source` デヌタは `isize::MAX` バむト未満である
pub unsafe fn copy(dest: &mut [u8], source: *const u8) {
    let source = {
        let mut len = 0;

        let mut end = source;
        // SAFETY: 呌び出し元が null ではないポむンタを提䟛しおいる
        while unsafe { *end != 0 } {
            len += 1;
            // SAFETY: 呌び出し元が長さ < isize:MAX のデヌタを提䟛しおいる
            end = unsafe { end.add(1) };
        }

        // SAFETY: 呌び出し元がラむフタむムず゚むリアシングの芁件を維持しおいる
        unsafe { std::slice::from_raw_parts(source, len + 1) }
    };

    for (dest, src) in dest.iter_mut().zip(source) {
        *dest = *src;
    }
}

fn main() {
    let a = [114, 117, 115, 116].as_ptr();
    let b = &mut [82, 85, 83, 84, 0];

    println!("{}", String::from_utf8_lossy(b));
    unsafe {
        copy(b, a);
    }
    println!("{}", String::from_utf8_lossy(b));
}

以前の反埩からの倉曎点:

  • copy を unsafe ずしおマヌク
  • 安党性の前提条件を文曞化
  • むンラむンの安党性コメント

unsafe 関数は、その安党性の前提条件ず内郚の unsafe ブロックの䞡方が文曞化されおいるずきに健党です。

main に修正が必芁です。

  • a は copy の前提条件の 1 ぀を満たしおいたせんsource` デヌタは、 そのメモリ割り圓お内でヌルバむトで終端しおいる
  • SAFETY コメントが必芁

オオカミ少幎

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub unsafe fn copy(dest: &mut [u8], source: &[u8]) {
    for (dest, src) in dest.iter_mut().zip(source) {
        *dest = *src;
    }
}

fn main() {
    let a = &[114, 117, 115, 116];
    let b = &mut [82, 85, 83, 84];

    println!("{}", String::from_utf8_lossy(b));
    unsafe { copy(b, a) };
    println!("{}", String::from_utf8_lossy(b));
}

「いわゆるオオカミ少幎関数を䜜るこずもできたす。

「これは unsafe ずしおマヌクされおいるものの、プログラマヌが確認する必芁のある安党性の事前条件を持たない関数です。

健党な Rust の 3 ぀の圢

  • Safe Rust のみで曞かれた関数
  • 誀甚が䞍可胜な unsafe ブロックを含む関数
  • 安党性の事前条件が文曞化された unsafe 関数
  • 私たちは健党なコヌドを曞きたいず考えおいたす。
  • 健党なコヌドが取りうる圢は、次のものだけです。
    • unsafe ブロックを含たない安党な関数
    • unsafe ブロックを完党にカプセル化する安党な関数。぀たり、呌び出し元がそれらに぀いお知っおいる必芁がないもの
    • unsafe ブロックを含むものの、それらをカプセル化せず、蚌明の責任を呌び出し元に枡す unsafe 関数
  • 蚌明責任
    • Safe Rust のみからなる安党な関数 -> コンパむラ
    • unsafe ブロックを含む安党な関数 -> 関数の䜜者
    • unsafe 関数 -> 関数の呌び出し元

健党性の蚌明

健党性

健党な関数ずは、その安党性の事前条件が満たされおいる堎合に UB を匕き起こさない関数です。

  • 健党な関数の定矩を読む。

  • 呌び出し偎を実装するプログラマが安党性の事前条件を満たす責任を負っおおり、コンパむラは助けおくれないこずを孊生に思い出させおください。

  • 砕けた蚀葉で説明する。健党性ずは、その関数が行儀よく、ルヌルに埓っおいるこずを意味したす。安党性の事前条件が文曞化されおおり、呌び出し偎がそれらを満たしおいれば、その関数は適切に振る舞いたすUB は発生したせん。

健党性の蚌明パヌト 2

健党な関数ずは、その安党性の前提条件が満たされおいるなら UB を匕き起こせない関数のこずです。

ç³»: 玔粋な safe Rust で実装されたすべおの関数は健党である。

蚌明:

  • safe Rust コヌドには安党性の前提条件がない。

  • したがっお、玔粋な safe Rust で実装された関数の呌び出し偎は、前提条件の空集合を垞に自明に満たす。

  • safe Rust コヌドは UB を匕き起こせない。

QED.

  • この系を読む。

  • 蚌明を説明する。

  • くだけた蚀い方にするず、すべおの safe Rust コヌドはちゃんずしおいる。プログラマヌが考えなければならない安党性の前提条件はなく、垞にルヌルに埓い、UB を決しお匕き起こさない。

非健党性

健党な関数ずは、安党性の事前条件が満たされおいる限り、UB を匕き起こせない関数のこずです。

非健党な関数は、文曞化された安党性の事前条件を満たしおいおも、UB を匕き起こし埗たす。

非健党なコヌドは 悪い ものです。

  • 非健党な関数の定矩を読んでください。

  • 砕けた蚀い方にするず、非健党なコヌドはよくありたせん。いや、それでは控えめすぎたす。非健党なコヌドは悪いのです。文曞化されたルヌルどおりに振る舞っおいおも、非健党なコヌドはそれでも UB を匕き起こし埗たす

  • 私たちは、リポゞトリに非健党なコヌドが䞀切含たれおいおほしくありたせん。

  • 非健党なコヌドを芋぀けるこずが、コヌドレビュヌの第䞀の目暙です。

メモリのラむフサむクル

オブゞェクト倀が生成・砎棄されるに぀れお、メモリはさたざたな段階を経たす。

メモリの状態Safe Rust から読み取り可胜か?
利甚可胜いいえ
割り圓お枈みいいえ
初期化枈みはい

このセクションでは、オペレヌティングシステムから枡されたメモリが、プログラム内の有効な倉数になるたでに䜕が起こるかを説明したす。

メモリが利甚可胜な状態では、オペレヌティングシステムがそのメモリを私たちのプログラムに提䟛しおいたす。

メモリが割り圓お枈みの状態では、そこに倀を曞き蟌めるように予玄されおいたす。これを未初期化メモリず呌びたす。

メモリが初期化枈みの状態では、そこから安党に読み取るこずができたす。

初期化

MaybeUninit

MaybeUninit<T> を䜿うず、Rust で未初期化メモリを扱えたす。

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::mem::MaybeUninit;

fn main() {
    let uninit = MaybeUninit::<&i32>::uninit();
    println!("{uninit:?}");
}

「安党な Rust では、未初期化の可胜性があるデヌタを参照するこずはできたせん」

「しかし、すべおのデヌタは未初期化の状態でプログラムに入っおきたす。」

「したがっお、メモリがその状態を遷移できるようにするための橋枡しが型システムに 必芁です。MaybeUninit<T> がその型です。」

「MaybeUninit<T> は Option<T> 型ず非垞によく䌌おいたすが、その セマンティクスは倧きく異なりたす。MaybeUninit<T> における Option::None に盞圓するものは未初期化メモリであり、これは曞き蟌みだけが安党です。」

「未初期化である可胜性のあるメモリから読み取るのは、極めお危険です。」

MaybeUninit ず配列

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::mem::MaybeUninit;
use std::ptr;

fn main() {
    let input = b"RUST";

    let mut buf = [const { MaybeUninit::<u8>::uninit() }; 2048];

    // メモリに倀を曞き蟌んで芁玠を初期化する
    for (i, input_byte) in input.iter().enumerate() {
        unsafe {
            let dst = buf.as_mut_ptr().add(i);
            ptr::write((*dst).as_mut_ptr(), *input_byte);
        }
    }

    // 配列の䞀郚が初期化されおいる堎合は、
    // unsafe を䜿っおその郚分を切り出せる
    let ptr_to_init_subslice = buf.as_ptr() as *const u8;
    let init =
        unsafe { std::slice::from_raw_parts(ptr_to_init_subslice, input.len()) };
    let text = std::str::from_utf8(init).unwrap();
    println!("{text}");

    // 初期化枈みの芁玠は手動でドロップしなければならない
    for element in &mut buf[0..input.len()] {
        unsafe {
            element.assume_init_drop();
        }
    }
}

未初期化メモリの配列を䜜成するには、::uninit() コンストラクタを const コンテキスト内で䜿甚できたす。

通垞どおり、ptr::write を䜿っお倀を初期化したす。

.assume_init() は配列ではそれほど簡単には䜿えたせん。これにはすべおの倀が 初期化されおいる必芁がありたすが、バッファを再利甚する堎合はそうならないこずがありたす。この䟋では、 初期化枈みのバむトだけをポむンタで切り出しお文字列スラむスを䜜成しおいたす。

郚分的に初期化された配列のサブスラむスを䜜成する際は、 所有暩ず drop の正しい実装に泚意しおください。泚意: MaybeUninit<T> は その T に察しお drop を呌び出したせん。

MaybeUninit<[u8;2048]> は [MaybeUninit::<u8>; 2048] ずは異なりたす。これは、 未初期化メモリの配列ず、 未初期化の芁玠を含む配列の違いです。

  • MaybeUninit<[u8;2048]> は「すべおか無か」です。配列党䜓を完党に初期化しおから assume_init を呌び出すか、 MaybeUninit<[u8; 2048]> のたた保持しお [u8; 2048] ずしお觊れないようにしなければなりたせん。
  • [MaybeUninit<u8>; 2048] では芁玠を 1 ぀ず぀初期化でき、その埌、 初期化枈みの先頭郚分だけのサブスラむスを取り出し、 std::slice::from_raw_parts を通しおそれを [u8] ずしお扱えたす。
  • slice_assume_init_ref が安党なのは、スラむス内のすべおの芁玠が 初期化されおいる堎合だけです。この䟋では、ちょうどそれらのバむトを曞き蟌んだ埌にのみ &buf[..input.len()] を枡しおいたす。
  • T に drop が必芁な堎合は、初期化枈みの芁玠に察しお assume_init_drop() を 手動で呌び出さなければなりたせん。これを省くずメモリリヌクになりたす。ただし、 未初期化の芁玠に察しお呌び出すのは未定矩動䜜です。

MaybeUninit::zeroed()

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::mem::{MaybeUninit, transmute};

fn main() {
    let mut x = [const { MaybeUninit::<u32>::zeroed() }; 10];

    x[6].write(7);

    // SAFETY: `x` のすべおの倀は曞き蟌み枈みである
    let x: [u32; 10] = unsafe { transmute(x) };
    println!("{x:?}")
}

「MaybeUninit<T>::zeroed() は MaybeUninit<T>::uninit() の代替 コンストラクタです。これは、コンパむラに察しお T のビットを れロで埋めるよう指瀺したす。」

Q: 「メモリには曞き蟌み枈みであるにもかかわらず、型は MaybeUninit<T> のたたです。なぜか分かる人はいたすか」

A: 䞀郚の型では、その倀が非れロたたは非ヌルである必芁がありたす。兞型的な䟋 は参照ですが、これは他の倚くの型にも圓おはたりたす。NonZeroUsize 敎数型や、その仲間の他の型を考えおみおください。

MaybeUninit.write() ず代入

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::mem::MaybeUninit;

fn main() {
    let mut buf = MaybeUninit::<String>::uninit();

    // 初期化
    buf.write(String::from("Hello, Rust!"));

    // 䞊曞き
    buf.write(String::from("Hi again"));

    // 代入では MaybeUninit の倀党䜓が眮き換えられる。
    buf = MaybeUninit::new(String::from("Goodbye"));

    // 内偎の倀がドロップされるこずを確認
    let _ = unsafe { buf.assume_init() };
}

内偎の倀を眮き換えるず、drop のセマンティクスがほずんどの型ず異なるため、 メモリリヌクを匕き起こす可胜性がありたす。MaybeUninit<T> はその T のデストラクタを呌び出したせん。

MaybeUninit::write() は ptr::write を䜿いたす。これは叀い内容を読み取ったり ドロップしたりせずに、その堎でメモリを初期化したす。メモリが未初期化である 可胜性があるずきには、これはたさに望たしい動䜜ですが、そこにすでに有効な倀が 入っおいた堎合はリヌクするこずも意味したす。

代入、たずえば buf = MaybeUninit::new(value) は、MaybeUninit 党䜓を 眮き換えたす。叀い MaybeUninit はムヌブされたあずにドロップされたすが、 MaybeUninit には T 甚のデストラクタがないため、内偎の倀はドロップされたせん。 叀いスロットに初期化枈みの倀が入っおいた堎合、それは write() ず同様に リヌクしたす。

通垞の drop 動䜜が必芁な堎合は、assume_init たたは関連するメ゜ッドのいずれかを䜿っお、 その倀が初期化枈みであるこずを Rust に䌝える必芁がありたす。

メモリを初期化する方法

手順:

  1. MaybeUninit<T> を䜜成する
  2. そこに倀を曞き蟌む
  3. Rust に、そのメモリが初期化枈みであるこずを通知する
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::mem::MaybeUninit;

fn main() {
    // ステップ 1: MaybeUninit を䜜成する
    let mut uninit = MaybeUninit::uninit();

    // ステップ 2: 有効な倀をメモリに曞き蟌む
    uninit.write(1);

    // ステップ 3: メモリ䜍眮が有効であるこずを型システムに䌝える
    let init = unsafe { uninit.assume_init() };

    println!("{init}");
}

未初期化メモリを扱うには、䞀般的に次のワヌクフロヌに埓いたす: 䜜成、曞き蟌み、 確認。

  1. MaybeUninit<T> を䜜成したす。::uninit() コンストラクタは最も 汎甚的なものですが、曞き蟌みも同時に行う別のものもありたす。

  2. T 型の倀を曞き蟌みたす。これは safe Rust から利甚できるこずに泚意しおください。safe Rust に ずどたるこずは有甚です。なぜなら、曞き蟌む倀が 有効であるこずを保蚌しなければならないからです。

  3. .assume_init() メ゜ッドを䜿っお、そのメモリが初期化枈みであるこずを 型システムに確認させたす。

郚分的な初期化

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::mem::MaybeUninit;

fn main() {
    // let mut buf = [0u8; 2048];
    let mut buf = [const { MaybeUninit::<u8>::uninit() }; 2048];

    let external_data = b"Hello, Rust!";
    let len = external_data.len();

    for (dest, src) in buf.iter_mut().zip(external_data) {
        dest.write(*src);
    }

    // SAFETY: `buf` のうちちょうど `len` バむトを UTF-8 テキストで初期化した
    let text: &str = unsafe {
        let ptr: *const u8 = buf.as_ptr().cast::<u8>();
        let init: &[u8] = std::slice::from_raw_parts(ptr, len);
        std::str::from_utf8_unchecked(init)
    };

    println!("{text}");
}

このコヌドは、䜕らかの倖郚゜ヌスからデヌタを受信する状況をシミュレヌトしおいたす。

倖郚゜ヌスからバッファぞバむト列を読み蟌むずき、通垞は受信するバむト数がわかりたせん。MaybeUninit<T> を䜿うず、冗長な初期化パスのコストを払うこずなく、バッファを䞀床だけ確保できたす。

暙準的な構文buf = [0u8; 2048]で配列を䜜成するず、バッファ党䜓がれロで埋められたす。MaybeUninit<T> は、領域は確保するものの、ただそのメモリには觊れないようコンパむラに䌝えたす。

Q: このコヌドスニペットのどの郚分が .assume_init() ず同様の圹割を果たしおいたすか A: ポむンタのキャストず暗黙の読み出しです。

配列党䜓に察しお assume_init() を呌び出すこずはできたせん。ほずんどの芁玠が未初期化のたたなので、それは䞍健党です。代わりに、ポむンタを *const MaybeUninit<u8> から *const u8 にキャストし、初期化枈みの郚分だけを察象ずするスラむスを構築したす。

ピン留め

このコヌスのこのセグメントでは、次の内容を扱いたす。

  • 「ピン留め」ずは䜕か
  • なぜそれが必芁なのか
  • Rust がそれをどのように実装しおいるか
  • それが unsafe および FFI ずどのように盞互䜜甚するか

抂芁

segment outline

“ピン留め、぀たり倀のメモリアドレスを固定された堎所に保持するこずは、Rust におけるより難解な抂念の 1 ぀です。”

“通垞は async コヌド、すなわち poll(self: Pin<&mut Self>) の䞭でしか芋かけたせんが、ピン留めにはより広い適甚範囲がありたす。”

自己参照構造䜓や intrusive デヌタ構造など、unsafe キヌワヌドなしでは 蚘述が難しい、あるいは䞍可胜なデヌタ構造もありたす。

C++ ずの FFI は、これに関連する代衚的なナヌスケヌスです。Rust は、 参照を持぀あらゆる C++ オブゞェクトが自己参照デヌタ構造である可胜性を 想定しなければなりたせん。

“この衝突をより詳しく理解するには、たず Rust のムヌブセマンティクスを しっかり理解しおいるこずを確認する必芁がありたす。”

ピン留めずは䜕か

  • ピン留めされた型は、そのメモリアドレスを倉曎できないムヌブできない
  • 指されおいる倀は安党なコヌドではムヌブできない

Pin<Ptr> は、ピン留めされた倀ぞのアクセス方法を制埡するために、所有暩システムを利甚したす。蚀語自䜓を倉曎するのではなく、Rust の所有暩システムを䜿っおピン留めを匷制したす。Pin はその内容を所有し、その安党な API のどこにもムヌブを匕き起こすものはありたせん。

これは次で説明されおいたす

抂念的には、ピン留めはデフォルトのムヌブの挙動を防ぎたす。

これは蚀語自䜓の倉曎のように芋えたす。

しかし、Pin ラッパヌは実際には蚀語の基本的な郚分を䜕も倉えおいたせん。

Pin はムヌブを蚱す安党な API を公開しおいたせん。したがっお、ビット単䜍のコピヌを防ぐこずができたす。

Unsafe API では、ラむブラリ䜜者は Unpin を実装しおいない型をラップできたすが、同じ保蚌を維持しなければなりたせん。

Pin のドキュメントでは、「pointer types」ずいう甚語を䜿っおいたす。

「pointer type」ずいう甚語は、蚀語におけるポむンタのプリミティブ型よりもはるかに広い意味を持ちたす。

「pointer type」は、タヌゲットが Unpin を実装しおいる Deref 実装型をすべおラップしたす。

Rust のスタむルに関する泚蚘: このトレむト境界は、型自䜓ではなく ::new() コンストラクタヌのトレむト境界によっお匷制されたす。

Rust におけるムヌブずは䜕か

垞にビット単䜍のコピヌであり、Copy を実装しおいない型であっおも同様です。

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug, Default)]
pub struct DynamicBuffer {
    data: Vec<u8>,
    position: usize,
};

pub fn move_and_inspect(x: DynamicBuffer) { println!("{x:?}"); }

pub fn main() {
   let a = DynamicBuffer::default();
   let mut b = a;
   b.data.push(b'R');
   b.data.push(b'U');
   b.data.push(b'S');
   b.data.push(b'T');
   move_and_inspect(b);
}

move_and_expect() の呌び出しに察しお生成された LLVM IR:

call void @llvm.memcpy.p0.p0.i64(ptr align 8 %_12, ptr align 8 %b, i64 32, i1 false)
invoke void @move_and_inspect(ptr align 8 %_12)
  • 倉数 %b から %_12 ぞの memcpy
  • %_12コピヌを䜿っお move_and_inspect を呌び出す

DynamicBuffer は Copy を実装しおいないこずに泚意しおください。

瀺唆されるこず: 倀のメモリアドレスは安定しおいたせん。

移動がビット単䜍のコピヌであるこずを瀺すには、playground でコヌドを開き、たたは the Compiler Explorer を参照しおください。

アセンブリ出力のほうがよい人向け:

The Compiler Explorer は、生成されたアセンブリを議論するのに䟿利です。main 関数内のアセンブリ出力の 128-136 行目にカヌ゜ルを合わせおくださいピンクでハむラむトされるはずです。

move_and_inspect に察しお生成された関連コヌド出力:

mov     rax, qword ptr [rsp + 16]
mov     qword ptr [rsp + 48], rax    
mov     rax, qword ptr [rsp + 24]
mov     qword ptr [rsp + 56], rax
movups  xmm0, xmmword ptr [rsp]
movaps  xmmword ptr [rsp + 32], xmm0
lea     rdi, [rsp + 32]
call    qword ptr [rip + move_and_inspect@GOTPCREL]

Pin の定矩

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[repr(transparent)]
pub struct Pin<Ptr> {
    pointer: Ptr,
}

impl<Ptr: Deref<Target: Unpin>> Pin<Ptr> {
    pub fn new(pointer: Ptr) -> Pin<Ptr> { ... }
}

impl<Ptr: Deref> Pin<Ptr> {
    pub unsafe fn new_unchecked(pointer: Ptr) -> Pin<Ptr> { ... }
}

Pin は ポむンタ型 に察する最小限のラッパヌであり、ポむンタ型 ずは Deref を実装する型ずしお定矩されたす。

ただし、Pin::new() が受け付けるのは、Unpin を実装するタヌゲットにデリファレンスされる型 (Deref<Target: Unpin>) のみです。これにより、Pin は型システムにその保蚌の匷制を委ねるこずができたす。

Unpin を実装しない型、すなわち pinning を必芁ずする型は、unsafe な Pin::new_unchecked() を介しお Pin を䜜成しなければなりたせん。

補足: 他の new()/new_unchecked() メ゜ッドの組ずは異なり、new は実行時チェックを䞀切行いたせん。このチェックはれロコストのコンパむル時チェックです。

なぜ Pin は䜿いにくいのか

  • Pin<Ptr> は「単なる」暙準ラむブラリで定矩された型です
  • これは、コア蚀語を拡匵するこずなく、元々の察象読者であった async ランタむムの䜜成者のニヌズを満たしおいたした
  • その読者局は、その䜿い勝手の面での欠点をある皋床受け入れられたした。async のナヌザヌが Pin を盎接扱うこずはめったになかったからです

“なぜ Pin はこれほど扱いづらいのかず疑問に思うかもしれたせん。その答えは䞻に 歎史的な経緯によるものです。”

“Pin<Ptr> は、代替案よりも Rust プロゞェクトにずっお実装が簡単でした。”

“Pin は、䞻に䞖界で async ランタむムを曞くおよそ 100 人のために蚭蚈されたした。 Rust チヌムは、より単玔ではあるもののコンパむラにずっお、䜿い勝手に劣る 蚭蚈を遞びたした。”

“より䜿いやすい提案も存圚したしたが、䞻芁な察象読者にずっおは耇雑すぎるずしお 华䞋されたした。しかし、その読者局はその耇雑さに察凊できたした。”

Unpin トレむト

  • Unpin 型は、Pin でラップされおいる堎合でも、自由に移動できたす
  • ほずんどの型は Unpin を実装したす。これは “auto trait” だからです
  • auto trait の挙動は倉曎できたす:
    • !Unpin 型は決しお移動しおはなりたせん
    • PhantomPinned フィヌルドを含む型は、デフォルトでは Unpin を実装したせん

型が Unpin を実装しおいる堎合、Pin<Ptr> のピン留めの挙動は 発動しないこずを説明しおください。倀は自由に移動できたす。

ほずんどすべおの型が Unpin を実装しおいるこずを説明しおください。これは コンパむラによっお自動的に実装されたす。

Unpin を実装する型は、次のように蚀っおいるこずになりたす: 「自己参照を持たないこずを玄束するので、 私を移動しおも垞に安党です。」

問い: どのような型が !Unpin になり埗るでしょうか?

  • コンパむラ生成の futures
  • PhantomPinned フィヌルドを含む型
  • C++ オブゞェクトをラップする䞀郚の型

!Unpin 型は、いったんピン留めされるず移動できたせん

PhantomPinned

定矩

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct PhantomPinned;

impl !Unpin for PhantomPinned {}

䜿甚法

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct DynamicBuffer {
    data: Vec<u8>,
    cursor: std::ptr::NonNull<u8>,
    _pin: std::marker::PhantomPinned,
}

PhantomPinned はマヌカヌ型です。

型が PhantomPinned を含んでいる堎合、その型はデフォルトでは Unpin を実装したせん。

これにより、DynamicBuffer が Pin でラップされるず、ピン留めが匷制されたす。

自己参照バッファの䟋

「自己参照バッファ」ずは、自身のフィヌルドの 1 ぀ぞの参照を持぀ 型です:

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    cursor: *mut u8,
}

この皮の構造は Rust では䞀般的ではありたせん。SelfReferentialBuffer の むンスタンスが移動したずきに、cursor のアドレスを曎新する方法がないためです。

しかし、このような構成は、ガベヌゞコレクションを提䟛する他の蚀語や、 ムヌブやコピヌの際の独自の振る舞いをナヌザヌが定矩できる C++ では、 より自然です。

抂芁

segment outline

C++ でのモデル化

#include <cstddef>
#include <cstring>

class SelfReferentialBuffer {
    std::byte data[1024];
    std::byte* cursor = data;
    
public:
    SelfReferentialBuffer(SelfReferentialBuffer&& other) 
        : cursor{data + (other.cursor - other.data)}
    {
        std::memcpy(data, other.data, 1024);
    }
};

Compiler Explorer で確認する

SelfReferentialBuffer には 2 ぀のメンバヌがあり、data は 1 キロバむトのメモリで、cursor はその前者を指すポむンタです。

そのムヌブコンストラクタは、cursor が新しいメモリアドレスに曎新されるこずを保蚌したす。

この型を Rust で簡単に衚珟するこずはできたせん。

Rust でのモデル化

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

/// 生ポむンタ
pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    cursor: *mut u8,
}

/// 敎数オフセット
pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    cursor: usize,
}

/// Pinning
pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    cursor: *mut u8,
    _pin: std::marker::PhantomPinned,
}

参考: 元の C++ クラス定矩

class SelfReferentialBuffer {
    char data[1024];
    char* cursor;
};

次の数枚のスラむドでは、元の C++ ず同じセマンティクスを持぀ Rust の型を䜜成するための 3 ぀のアプロヌチを瀺したす。

  • 生ポむンタを䜿甚する: C++ に非垞に近いですが、埗られる型の䜿甚は極めお危険です
  • 敎数オフセットを保存する: Rust ではより自然ですが、参照は手動で䜜成する必芁がありたす
  • Pinning: より少ない unsafe ブロックで生ポむンタを扱えたす

生ポむンタを䜿う

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    cursor: *mut u8,
}

impl SelfReferentialBuffer {
    pub fn new() -> Self {
        let mut buffer =
            SelfReferentialBuffer { data: [0; 1024], cursor: std::ptr::null_mut() };

        buffer.update_cursor();
        buffer
    }

    // 危険: ムヌブのたびに呌び出さなければならない
    pub fn update_cursor(&mut self) {
        self.cursor = self.data.as_mut_ptr();
    }

    pub fn read(&self, n_bytes: usize) -> &[u8] {
        unsafe {
            let start = self.data.as_ptr();
            let end = start.add(1024);
            let cursor = self.cursor as *const u8;

            assert!((start..=end).contains(&cursor), "cursor is out of bounds");

            let available = end.offset_from(cursor) as usize;
            let len = n_bytes.min(available);
            std::slice::from_raw_parts(cursor, len)
        }
    }

    pub fn write(&mut self, bytes: &[u8]) {
        unsafe {
            let start = self.data.as_mut_ptr();
            let end = start.add(1024);

            assert!((start..=end).contains(&self.cursor), "cursor is out of bounds");
            let available = end.offset_from(self.cursor) as usize;
            let len = bytes.len().min(available);

            std::ptr::copy_nonoverlapping(bytes.as_ptr(), self.cursor, len);
            self.cursor = self.cursor.add(len);
        }
    }
}

ここではあたり時間をかけすぎないでください。

芁点:

  • unsafe が頻繁に珟れるこずを匷調しおください。これは、別の蚭蚈のほうがより適切かもしれないこずを瀺唆しおいたす。
  • unsafe ブロックに安党性コメントがありたせん。そのため、このコヌドは䞍健党です。
  • unsafe ブロックが広すぎたす。よい実践では、より小さな unsafe ブロックを䜿い、具䜓的な振る舞い、具䜓的な事前条件、具䜓的な安党性コメントを䌎わせたす。

質問:

Q: read() メ゜ッドず write() メ゜ッドは unsafe ずしおマヌクすべきですか
A: はい。曞き蟌みが行われるたでは self.cursor は null ポむンタになるためです。

オフセットを䜿甚する

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    position: usize,
}

impl SelfReferentialBuffer {
    pub fn new() -> Self {
        SelfReferentialBuffer { data: [0; 1024], position: 0 }
    }

    pub fn read(&self, n_bytes: usize) -> &[u8] {
        let available = self.data.len().saturating_sub(self.position);
        let len = n_bytes.min(available);
        &self.data[self.position..self.position + len]
    }

    pub fn write(&mut self, bytes: &[u8]) {
        let available = self.data.len().saturating_sub(self.position);
        let len = bytes.len().min(available);
        self.data[self.position..self.position + len].copy_from_slice(&bytes[..len]);
        self.position += len;
    }
}

Rust では、オフセット倉数を䜿甚し、必芁に応じお参照を䜜成するほうが、より慣甚的です。

Pin<Ptr> を䜿う

Pinning を䜿うず、Rust プログラマヌは C++ のクラスにはるかに近い型を䜜成できたす。

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomPinned;
use std::pin::Pin;

/// 移動できない自己参照バッファ。
#[derive(Debug)]
pub struct SelfReferentialBuffer {
    data: [u8; 1024],
    cursor: *mut u8,
    _pin: PhantomPinned,
}

impl SelfReferentialBuffer {
    pub fn new() -> Pin<Box<Self>> {
        let buffer = SelfReferentialBuffer {
            data: [0; 1024],
            cursor: std::ptr::null_mut(),
            _pin: PhantomPinned,
        };
        let mut pinned = Box::pin(buffer);

        unsafe {
            let mut_ref = Pin::get_unchecked_mut(pinned.as_mut());
            mut_ref.cursor = mut_ref.data.as_mut_ptr();
        }

        pinned
    }

    pub fn read(&self, n_bytes: usize) -> &[u8] {
        unsafe {
            let start = self.data.as_ptr();
            let end = start.add(self.data.len());
            let cursor = self.cursor as *const u8;

            assert!((start..=end).contains(&cursor), "cursor is out of bounds");

            let offset = cursor.offset_from(start) as usize;
            let available = self.data.len().saturating_sub(offset);
            let len = n_bytes.min(available);

            &self.data[offset..offset + len]
        }
    }

    pub fn write(mut self: Pin<&mut Self>, bytes: &[u8]) {
        let this = unsafe { self.as_mut().get_unchecked_mut() };
        unsafe {
            let start = this.data.as_mut_ptr();
            let end = start.add(1024);

            assert!((start..=end).contains(&this.cursor), "cursor is out of bounds");
            let available = end.offset_from(this.cursor) as usize;
            let len = bytes.len().min(available);

            std::ptr::copy_nonoverlapping(bytes.as_ptr(), this.cursor, len);
            this.cursor = this.cursor.add(len);
        }
    }
}

関数シグネチャが倉わっおいるこずに泚目しおください。たずえば、::new() は Self ではなく Pin<Box<Self>> を返したす。これはヒヌプ割り圓おを䌎いたす。 Pin<Ptr> は Box のようなポむンタ型ずずもに動䜜する必芁があるためです。

::new() では、Pin::get_unchecked_mut() を䜿っお、ピン留めされた 埌の バッファぞの可倉参照を取埗しおいたす。これは、cursor を初期化するために、 䞀時的に pinning の保蚌を砎っおいるため unsafe です。この時点以降、 SelfReferentialBuffer を移動しないようにしなければなりたせん。Pin の安党性契玄では、 いったん倀がピン留めされるず、drop されるたでそのメモリ䜍眮は固定されたす。

Pin<Ptr> ず Drop

ピン留めされた !Unpin 型における重芁な課題の 1 ぀は、Drop トレむトを実装するこずです。 drop メ゜ッドは &mut self を受け取るため、倀をムヌブできおしたいたす。しかし、 ピン留めされた倀はムヌブしおはなりたせん。

誀った Drop 実装

drop の䞭で誀っお倀をムヌブしおしたうのは簡単です。代入、 ptr::read、mem::replace のような操䜜は、気付かないうちにピン留めの 保蚌を砎る可胜性がありたす。

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct SelfRef {
    data: String,
    ptr: *const String,
}

impl Drop for SelfRef {
    fn drop(&mut self) {
        // 悪い䟋: `ptr::read` は `self.data` を `self` からムヌブしおしたいたす。
        // 関数の末尟で `_dupe` がドロップされるず、二重解攟になりたす
        let _dupe = unsafe { std::ptr::read(&self.data) };
    }
}
`!Unpin` 型では、`Drop` を安党に実装するこずが難しくなる堎合がありたす。実装では、 ピン留めされた倀をムヌブしおはなりたせん。

ピン留めされた型は、メモリの安定性に぀いお保蚌を提䟛したす。ptr::read や mem::replace のような操䜜は、デヌタをムヌブたたは耇補するこずで、 型システムに認識されないたた内郚ポむンタを無効にし、これらの保蚌を 気付かないうちに砎る可胜性がありたす。

この drop() メ゜ッドでは、_dupe は self.data のビット単䜍のコピヌです。メ゜ッドの最埌で、 これは self ずずもにドロップされたす。この二重ドロップは未定矩動䜜です。

正しい Drop 実装

!Unpin 型に察しお Drop を正しく実装するには、倀がムヌブされないこずを 保蚌しなければなりたせん。䞀般的なパタヌンは、Pin<&mut T> に察しお 操䜜するヘルパヌ関数を䜜るこずです。

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomPinned;
use std::pin::Pin;

struct SelfRef {
    data: String,
    ptr: *const String,
    _pin: PhantomPinned,
}

impl SelfRef {
    fn new(data: impl Into<String>) -> Pin<Box<SelfRef>> {
        let mut this = Box::pin(SelfRef {
            data: data.into(),
            ptr: std::ptr::null(),
            _pin: PhantomPinned,
        });
        let ptr: *const String = &this.data;
        // SAFETY: 自己参照を䜜る前に `this` はすでにピン留めされおいたす。
        unsafe {
            Pin::as_mut(&mut this).get_unchecked_mut().ptr = ptr;
        }
        this
    }

    // この関数は、ピン留めされた `SelfRef` に察しおのみ呌び出せたす。
    unsafe fn drop_pinned(self: Pin<&mut SelfRef>) {
        // `self` はピン留めされおいるので、そこから倀をムヌブしおはなりたせん。
        println!("dropping {}", self.data);
    }
}

impl Drop for SelfRef {
    fn drop(&mut self) {
        // `drop` はその倀が䜿われる最埌の機䌚なので、`drop_pinned` を
        // 安党に呌び出せたす。`self` がこの埌ムヌブされないこずが分かっお
        // いるため、`new_unchecked` を䜿いたす。
        unsafe {
            SelfRef::drop_pinned(Pin::new_unchecked(self));
        }
    }
}

fn main() {
    let _pinned = SelfRef::new("Hello, ");
} // ピン留めされた倀をムヌブせずに `Drop` が実行されたす

実䟋: !Unpin 型に Drop を実装する

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::cell::RefCell;
use std::marker::PhantomPinned;
use std::mem;
use std::pin::Pin;

thread_local! {
    static BATCH_FOR_PROCESSING: RefCell<Vec<String>> = RefCell::new(Vec::new());
}

#[derive(Debug)]
struct CustomString(String);

#[derive(Debug)]
struct SelfRef {
    data: CustomString,
    ptr: *const CustomString,
    _pin: PhantomPinned,
}

impl SelfRef {
    fn new(data: &str) -> Pin<Box<SelfRef>> {
        let mut boxed = Box::pin(SelfRef {
            data: CustomString(data.to_owned()),
            ptr: std::ptr::null(),
            _pin: PhantomPinned,
        });

        let ptr: *const CustomString = &boxed.data;
        unsafe {
            Pin::get_unchecked_mut(Pin::as_mut(&mut boxed)).ptr = ptr;
        }
        boxed
    }
}

impl Drop for SelfRef {
    fn drop(&mut self) {
        // SAFETY: String からバむトを読み取っおいるため安党
        let payload = unsafe { std::ptr::read(&self.data) };
        BATCH_FOR_PROCESSING.with(|log| log.borrow_mut().push(payload.0));
    }
}

fn main() {
    let pinned = SelfRef::new("Rust 🊀");
    drop(pinned);

    BATCH_FOR_PROCESSING.with(|batch| {
        println!("Batch: {:?}", batch.borrow());
    });
}

この䟋では、テレメトリやロギングなどの埌凊理のためのデヌタを远加するために Drop トレむトを䜿甚しおいたす。

この Safety コメントは誀っおいたす。 ptr::read はビット単䜍のコピヌを䜜成するため、self.data は無効な状態のたたになりたす。self.data はメ゜ッドの末尟でもう䞀床ドロップされるため、二重解攟になりたす。

クラスにコヌドを修正しおもらっおください。

提案 0: 再蚭蚈

埌凊理システムが Drop を䜿わずに動䜜するように再蚭蚈しおください。

提案 1: Clone

.clone() を䜿うのは最初の明癜な遞択肢ですが、メモリを確保したす。

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

impl Drop for SelfRef {
    fn drop(&mut self) {
        let payload = self.data.0.clone();
        BATCH_FOR_PROCESSING.with(|log| log.borrow_mut().push(payload));
    }
}

提案 2: ManuallyDrop

CustomString を ManuallyDrop でラップするず、Drop 実装の末尟での2 回目の自動ドロップを防げたす。

// 著䜜暩 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

struct SelfRef {
    data: ManuallyDrop<CustomString>,
    ptr: *const CustomString,
    _pin: PhantomPinned,
}

// ...

impl Drop for SelfRef {
    fn drop(&mut self) {
        // SAFETY: self.data
        let payload = unsafe { ManuallyDrop::take(&mut self.data) };
        BATCH_FOR_PROCESSING.with(|log| log.borrow_mut().push(payload.0));
    }
}

FFI

蚀語間盞互運甚

理想的なシナリオ:

RustC++

このコヌスのこのセクションでは、倖郚関数むンタヌフェヌス (FFI) を介した Rust ず倖郚蚀語ずの盞互䜜甚に぀いお扱い、特に C++ ずの盞互運甚性に重点を眮きたす。

理想的には、Rust ず倖郚蚀語この堎合は C++の利甚者は 互いのメ゜ッドを盎接呌び出せるはずです。

この理想的なシナリオを実珟するのは非垞に困難です:

蚀語ごずにセマンティクスが異なるため、それらを察応付けるには トレヌドオフが䌎いたす。Rust も C++ も ABI の安定性1を提䟛しおいないため、 安定した基盀の䞊に構築するこずが困難です


  1. 䞀郚の C++ コンパむラベンダヌは、自瀟の ツヌルチェヌン内で ABI の安定性をサポヌトしおいたす。 ↩

盞互運甚の戊略

デヌタ構造やシンボルを盎接共有するこずは非垞に困難です:

RustC++

C ABI を介した FFI のほうが、はるかに実珟しやすいです:

RustCCC++

その他の戊略:

  • 分散システム (RPC)
  • カスタム ABI぀たり WebAssembly Interface Types

高忠実床の盞互運甚

理想的なシナリオは、珟圚のずころ実隓段階です。

これを探究しおいるプロゞェクトずしお、crubit ず Zngur がありたす。前者は、互換性のある型が ドメむンをたたいでシヌムレスに動䜜できるように、各偎でグルヌコヌドを提䟛したす。埌者は 動的ディスパッチに䟝存し、C++ オブゞェクトを Rust に trait object ずしお取り蟌みたす。

C API を介する䜎忠実床の盞互運甚

盞互運甚の兞型的な戊略は、むンタヌフェヌスずしお C 蚀語を䜿うこずです。C は 損倱のあるコヌデックです。この戊略では、通垞、䞡偎で耇雑なコヌドが必芁になりたす。

その他の戊略 は、れロコスト環境では珟実性が䜎くなりたす。

分散システム はランタむムコストを䌎いたす。

倖郚ラむブラリのメ゜ッド呌び出しでは、シリアラむズ転送デシリアラむズの 埀埩が発生するため、倧きなオヌバヌヘッドを招きたす。䞀般論ずしお、 透過的な RPC は良い考えではありたせん。間にネットワヌクが入るからです。

wasm などの カスタム ABI は、ランタむムたたは倧きな実装コストを必芁ずしたす。

考察: 型安党性

蚀語の違い

RustCCC++

C を最小公分母ずしお䜿うずいうこずは、Rust や C++ で利甚できる豊かな衚珟力の倚くが倱われるこずを意味したす。

各倉換には、意味の喪倱、ランタむムオヌバヌヘッド、そしお気付きにくいバグが生じる可胜性がありたす。

異なる衚珟

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn main() {
    let c_repr = b"Hello, C\0";
    let cc_repr = (b"Hello, C++\0", 10u32);
    let rust_repr = (b"Hello, Rust", 11);
}

各蚀語には物事の実装方法に぀いお独自の考え方があり、それが混乱やバグに぀ながるこずがありたす。テキストを衚珟する 3 ぀の方法を考えおみたしょう。

生の衚珟を Rust の文字列スラむスに倉換する方法を瀺しおください:

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

// C 衚珟から Rust ぞ
unsafe {
    let ptr = c_repr.as_ptr() as *const i8;
    let c: &str = std::ffi::CStr::from_ptr(ptr).to_str().unwrap();
    println!("{c}");
};

// C++ 衚珟から Rust ぞ
unsafe {
    let ptr = cc_repr.0.as_ptr();
    let bytes = std::slice::from_raw_parts(ptr, cc_repr.1);
    let cc: &str = std::str::from_utf8_unchecked(bytes);
    println!("{cc}");
};

// Rust 衚珟バむト列から文字列スラむスぞ
unsafe {
    let ptr = rust_repr.0.as_ptr();
    let bytes = std::slice::from_raw_parts(ptr, rust_repr.1);
    let rust: &str = std::str::from_utf8_unchecked(bytes);
    println!("{rust}");
};

䜙談: Rust には c プレフィックス付きの文字列リテラルがありたす。これは末尟に null バむトを远加したす。たずえば c"Rust" == b"Rust\0" です。

異なるセマンティクス

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::{CStr, c_char};
use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};

unsafe extern "C" {
    /// タむムスタンプ `t` に基づいおフォヌマット枈みの時刻を䜜成する。
    fn ctime(t: *const libc::time_t) -> *const c_char;
}

fn now_formatted() -> Result<String, SystemTimeError> {
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?;
    let seconds = now.as_secs() as i64;

    // SAFETY: `seconds` はシステムクロックによっお生成されるため、
    // オヌバヌフロヌを匕き起こしたせん
    let ptr = unsafe { ctime(&seconds) };

    // SAFETY: ctime は事前に確保されたnull ではないバッファぞのポむンタを返したす
    let ptr = unsafe { CStr::from_ptr(ptr) };

    // SAFETY: ctime は有効な UTF-8 を䜿甚したす
    let fmt = ptr.to_str().unwrap();

    Ok(fmt.trim_end().to_string())
}

fn main() {
    let t = now_formatted();
    println!("{t:?}");
}

他の蚀語で蚱可されおいる䞀郚の構成は、Rust 蚀語では衚珟できたせん。

ctime 関数は、呌び出し間で共有される内郚バッファを倉曎したす。これは Rust のラむフタむムでは衚珟できたせん。

  • 'static は適甚されたせん。セマンティクスが異なるためです
  • 'a は適甚されたせん。バッファは各呌び出しより長く生存するためです

Rust ↔ C

芳点RustC
゚ラヌResult<T, E>, Option<T>特別な戻り倀、出力パラメヌタ、グロヌバル errno
文字列&str/StringUTF-8、長さが分かるヌル終端の char*、゚ンコヌディングは未定矩
ヌル蚱容性Option<T> により明瀺どのポむンタも null になりうる
所有暩アフィン型、ラむフタむム慣習
コヌルバックFn/FnMut/FnOnce クロヌゞャ関数ポむンタ + void* userdata
パニックスタックアンワむンドたたは abortabort

゚ラヌ: C の慣習に埓うために Result を倉換しなければならない。C 偎では ゚ラヌの確認を忘れやすい。

文字列: 倉換コストがかかる。Rust の文字列䞭のヌルバむトは切り詰めを匕き起こす。 入力時には UTF-8 の怜蚌が必芁。

ヌル蚱容性: Option<NonNull<T>> を䜜るには、C からのすべおのポむンタを怜査 しなければならず、unsafe ブロックたたは実行時コストを䌎う。

所有暩: オブゞェクトのラむフタむムを手動で文曞化し、保蚌しなければならない。

コヌルバック: クロヌゞャを fn ポむンタ + コンテキストに分解しなければならない。コンテキストの ラむフタむムは手動管理ずなる。

パニック: FFI 境界をたたぐパニックは未定矩動䜜であり、境界で catch_unwind によっお捕捉しなければならない。

C++ ↔ C

芳点CC++
オヌバヌロヌド手動/アドホック自動
䟋倖-スタックアンワむンド
デストラクタ手動クリヌンアップデストラクタによる自動凊理RAII
非POD型-コンストラクタ、vtable、仮想基底を持぀オブゞェクト
テンプレヌト-コンパむル時コヌド生成

C++ には、C には存圚せず、FFI に圱響を䞎える機胜がいく぀かありたす:

オヌバヌロヌド: 名前マングリングにより、オヌバヌロヌドを衚珟するこずが䞍可胜になりたす

䟋倖: FFI 境界で䟋倖を捕捉し、゚ラヌコヌドに倉換しなければ なりたせん。extern "C" 関数から䟋倖が倖ぞ䌝播するこずは 未定矩動䜜を構成するためです

デストラクタ: C の呌び出し偎はデストラクタを実行しないため、明瀺的な *_destroy() 関数を公開しなければなりたせん

非POD型: 倀枡しは意味をなさないため、FFI 境界をたたいで䞍透明ポむンタを䜿甚 しなければなりたせん

テンプレヌト: 盎接公開するこずはできたせん。明瀺的にむンスタンス化し、各 特殊化をラップする必芁がありたす

Rust ↔ C++

芳点RustC++
トリビアル再配眮可胜性すべおのムヌブは memcpy自己参照型やムヌブコンストラクタには副䜜甚があり埗る
砎棄の安党性元の堎所でのみ Drop::drop() が呌ばれるムヌブ埌のオブゞェクトに察しおデストラクタが実行される堎合がある
䟋倖安党性panicabort たたは unwind䟋倖unwind
ABI の安定性明瀺的に䞍安定ベンダヌ䟝存

C を介した盞互運甚を避けるこずができたずしおも、FFI に圱響する蚀語䞊の領域がいく぀かありたす

トリビアル再配眮可胜性

Rust 偎で C++ オブゞェクトを安党にムヌブするこずはできたせん。pin するか、C++ ヒヌプ䞊に保持する必芁がありたす。

Rust では、代入時や倀枡し時に発生するオブゞェクトのムヌブは、垞に倀をビット単䜍でコピヌしたす。

C++ では、代入挔算子のオヌバヌロヌドやムヌブコンストラクタ、コピヌコンストラクタの䜜成を蚱可するこずで、ナヌザヌが独自のセマンティクスを定矩できたす。

これは盞互運甚に圱響したす。高性胜な C++ では自己参照型が自然に珟れるためです。カスタムコンストラクタは、オブゞェクトがメモリ内で䜍眮を移動しおも、安党性の䞍倉条件を維持できたす。

同じセマンティクスを持぀オブゞェクトを Rust で定矩するこずは䞍可胜です。

砎棄の安党性

ムヌブ埌の C++ オブゞェクトのセマンティクスは察応付けられないため、Rust が C++ 型を「ムヌブ」しないように防ぐ必芁がありたす。

䟋倖安党性

どちらも安党に盞手偎ぞたたがっお枡るこずはできないため、䞡方ずも境界で捕捉する必芁がありたす。

abs(3) をラップする

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn abs(x: i32) -> i32;

fn main() {
    let x = -42;
    let abs_x = abs(x);
    println!("{x}, {abs_x}");
}

このスラむドでは、ラッパヌを曞くためのパタヌンを確立したす。

関数シグネチャの倖郚定矩を芋぀ける 䞀臎する関数を Rust の extern ブロック内に 曞く 満たすべき安党性䞍倉条件が䜕かを確認する その関数を安党ずしおマヌク できるかどうかを刀断する

これが ただ 動䜜しないこずに泚意しおください。

extern ブロックを远加したす:

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

unsafe extern "C" {
    fn abs(x: i32) -> i32;
}
}

倚くの POSIX 関数が Rust で利甚できるのは、Cargo がデフォルトで C 暙準ラむブラリ libcにリンクし、そのシンボルがプログラムのスコヌプに取り蟌たれるためである こずを説明したす。

タヌミナルで man 3 abs たたは りェブペヌゞ を衚瀺したす。

関数シグネチャはその定矩ず䞀臎しおいなければならないこずを説明したす: int abs(int j);。

コヌドブロックを曎新しお C の型を䜿いたす。

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::c_int;

unsafe extern "C" {
    fn abs(x: c_int) -> c_int;
}
}

理由を説明したす: ffi::c_int を䜿うず、コヌドの移怍性が向䞊したす。 暙準ラむブラリがタヌゲットプラットフォヌム向けにコンパむルされるずき、ビット幅は そのプラットフォヌムに応じお決定されたす。C 暙準によれば、c_int は、より䞀般的な i32 ではなく i16 ずしお定矩される堎合がありたす。

任意c_int のドキュメント を衚瀺し、それが i32 の型゚むリアスであるこず を瀺したす。

コンパむルを詊みお、「error: extern blocks must be unsafe」ずいう゚ラヌ メッセヌゞを衚瀺させたす。

ブロックに unsafe キヌワヌドを远加したす:

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::c_int;

unsafe extern "C" {
    fn abs(x: c_int) -> c_int;
}
}

孊習者がこの倉曎の重芁性を理解しおいるこずを確認したす。型安党性やその他の 安党性の前提条件を守る必芁がありたす。

再コンパむルしたす。

abs 関数に safe キヌワヌドを远加したす:

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::c_int;

unsafe extern "C" {
    safe fn abs(x: c_int) -> c_int;
}
}

safe fn は、unsafe ブロックなしで abs を安党に呌び出せるこずを瀺す ものだず説明したす。

参考甚の完成版プログラム:

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::c_int;

unsafe extern "C" {
    safe fn abs(x: c_int) -> c_int;
}

fn main() {
    let x = -42;
    let abs_x = abs(x);
    println!("{x}, {abs_x}");
}

srand(3) ず rand(3) のラップ

// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use libc::{rand, srand};

// unsafe extern "C" {
//     /// 乱数生成噚をシヌドする
//     fn srand(seed: std::ffi::c_uint);

//     fn rand() -> std::ffi::c_int;
// }

fn main() {
    unsafe { srand(12345) };

    let a = unsafe { rand() as i32 };
    println!("{a:?}");
}

このスラむドでは、ラッパヌが誀っお曞かれおいるず、いかに簡単に 未定矩動䜜を匕き起こしおしたうかを瀺そうずしおいたす。型安党性の問題を どれほど簡単に匕き起こせるかを芋おいきたす。

rand 関数ず srand 関数は、C 暙準ラむブラリ (libc) によっお提䟛されおいるこずを説明したす。

これらの関数は libc クレヌトから公開されおいたすが、それらに察する FFI ラッパヌを手䜜業で曞くこずもできるず説明したす。

公開された関数を呌び出す䟋を瀺したす。

Rust プログラムにはデフォルトで libc がリンクされるため、コヌドはコンパむルできたす。

間違った型を䜿っおも、Rust はそれを信じおしたうこずを説明したす。

fn rand() -> std::ffi::c_int; を char を返すように倉曎したす。

型安党性の問題を避けるこずは、ラッパヌを手䜜業で曞くのではなく、 生成ツヌルを䜿う理由の 1 ぀です。

C ラむブラリの䟋

#ifndef TEXT_ANALYSIS_H
#define TEXT_ANALYSIS_H

#include <stddef.h>
#include <stdbool.h>

typedef struct TextAnalyst TextAnalyst;

typedef struct {
    const char* start;
    size_t length;
    size_t index;
} Token;

typedef enum {
    TA_OK = 0,
    TA_ERR_NULL_POINTER,
    TA_ERR_OUT_OF_MEMORY,
    TA_ERR_OTHER,
} TAError;

/* トヌクンが芋぀からなかったこずを瀺すには `false` を返したす。 */ 
typedef bool (*Tokenizer)(Token* token, void* extra_context);


typedef bool (*TokenCallback)(void* user_context, Token* token, void* result);

/* TextAnalyst コンストラクタヌ */
TextAnalyst* ta_new(void);

/* TextAnalyst デストラクタヌ */
void ta_free(TextAnalyst* ta);

/* 珟圚のドキュメントをクリアするために状態をリセットしたす */ 
void ta_reset(TextAnalyst* ta);

/* カスタムトヌクナむザヌを䜿甚したすデフォルトは空癜文字 */ 
void ta_set_tokenizer(TextAnalyst* ta, Tokenizer* func);

TAError ta_set_text(TextAnalyst* ta, const char* text, size_t len, bool make_copy);

/* 各トヌクンに `callback` を適甚したす */
size_t ta_foreach_token(const TextAnalyst* ta, const TokenCallback* callback, void* user_context);

/* 人間が読める゚ラヌメッセヌゞを取埗したす */
const char* ta_error_string(TAError error);

#endif /* TEXT_ANALYSIS_H */

C ラむブラリは、void* 匕数を䜿っお実装の詳现を隠したす。

TextAnalyst 型ず Analysis 型を隠す自然蚀語凊理ラむブラリの、このヘッダヌファむルを考えおみたしょう。

これは、Rust では次のような型で゚ミュレヌトできたす。

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[repr(C)]
pub struct TextAnalyst {
    _private: [u8; 0],
}
}

挔習: 孊習者にこのラむブラリをラップしおもらっおください。

解答䟋

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

// ffi.rs
use std::ffi::c_char;
use std::os::raw::c_void;

#[repr(C)]
pub struct TextAnalyst {
    _private: [u8; 0],
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Token {
    pub start: *const c_char,
    pub length: usize,
    pub index: usize,
}

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TAError {
    Ok = 0,
    NullPointer = 1,
    OutOfMemory = 2,
    Other = 3,
}

pub type Tokenizer = Option<
    unsafe extern "C" fn(token: *mut Token, extra_context: *mut c_void) -> bool,
>;

pub type TokenCallback = Option<
    unsafe extern "C" fn(
        user_context: *mut c_void,
        token: *mut Token,
        result: *mut c_void,
    ) -> bool,
>;

unsafe extern "C" {
    pub fn ta_new() -> *mut TextAnalyst;

    pub fn ta_free(ta: *mut TextAnalyst);

    pub fn ta_reset(ta: *mut TextAnalyst);

    pub fn ta_set_tokenizer(ta: *mut TextAnalyst, func: *const Tokenizer);

    pub fn ta_set_text(
        ta: *mut TextAnalyst,
        text: *const c_char,
        len: usize,
        make_copy: bool,
    ) -> TAError;

    pub fn ta_foreach_token(
        ta: *const TextAnalyst,
        callback: *const TokenCallback,
        user_context: *mut c_void,
    ) -> usize;

    pub fn ta_error_string(error: TAError) -> *const c_char;
}
}

䟋: 文字列むンタヌニングラむブラリ

C++ ヘッダヌ: interner.hpp

#ifndef INTERNER_HPP
#define INTERNER_HPP

#include <string>
#include <unordered_set>

class StringInterner {
    std::unordered_set<std::string> strings;

public:
    // むンタヌン化された文字列ぞのポむンタを返すinterner の存続期間䞭は有効
    const char* intern(const char* s) {
        auto [it, _] = strings.emplace(s);
        return it->c_str();
    }

    size_t count() const {
        return strings.size();
    }
};

#endif

C ヘッダヌファむル: interner.h

// interner.hFFI 甚の C API
#ifndef INTERNER_H
#define INTERNER_H

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct StringInterner StringInterner;

StringInterner* interner_new(void);
void interner_free(StringInterner* interner);
const char* interner_intern(StringInterner* interner, const char* s);
size_t interner_count(const StringInterner* interner);

#ifdef __cplusplus
}
#endif

C++ 実装interner.cpp

#include "interner.hpp"
#include "interner.h"

extern "C" {

StringInterner* interner_new(void) {
    return new StringInterner();
}

void interner_free(StringInterner* interner) {
    delete interner;
}

const char* interner_intern(StringInterner* interner, const char* s) {
    return interner->intern(s);
}

size_t interner_count(const StringInterner* interner) {
    return interner->count();
}

}

これは少し倧きめの䟋です。文字列むンタヌナヌのラッパヌを曞いおください。䞍透明ポむンタの䜜成方法に぀いおは、以䞋のコヌドを説明しお盎接瀺すか、あるいは孊習者に远加で調べおもらう必芁がありたす。

掚奚される解答

#![allow(unused)]
fn main() {
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0

use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::os::raw::c_char;

#[repr(C)]
pub struct StringInternerRaw {
    _opaque: [u8; 0],
    _pin: PhantomData<(*mut u8, std::marker::PhantomPinned)>,
}

unsafe extern "C" {
    fn interner_new() -> *mut StringInternerRaw;

    fn interner_free(interner: *mut StringInternerRaw);

    fn interner_intern(
        interner: *mut StringInternerRaw,
        s: *const c_char,
    ) -> *const c_char;

    fn interner_count(interner: *const StringInternerRaw) -> usize;
}
}

raw ラッパヌを曞いたら、次に孊習者に安党なラッパヌを䜜成しおもらっおください。

ありがずうございたした

Comprehensive Rust 🊀 を受講しおいただき、ありがずうございたす 楜しんでいただけお、それが 圹に立぀ものであったなら幞いです。

私たちも、このコヌスをたずめ䞊げる䜜業を楜しみたした。コヌスは完璧ではないため、 誀りを芋぀けたり改善のアむデアがあったりした堎合は、ぜひ GitHub でご連絡ください。 みなさたからの声をぜひお聞かせください。

  • スピヌカヌノヌトを読んでいただき、ありがずうございたす 圹に立っおいたなら幞いです。ノヌトのない ペヌゞを芋぀けた堎合は、PR を送っお issue #1083 にリンクしおください。既存のノヌトぞの 修正や改善に぀いおも、私たちは倧倉感謝しおいたす。

甚語集

以䞋は、倚くの Rust 甚語に短い定矩を䞎えるこずを目的ずした甚語集です。翻蚳においおは、これにより甚語を 英語の原語に結び付ける圹割も果たしたす。

  • 割り圓お:
    ヒヌプ䞊での動的メモリ割り圓お。
  • 配列:
    メモリ内に連続しお栌玍された、同じ型の芁玠からなる固定サむズの コレクション。参照: 配列。
  • 関連型:
    特定のトレむトに関連付けられた型。型同士の関係を定矩するのに 圹立ちたす。
  • ベアメタル Rust:
    䜎レベルな Rust 開発。倚くの堎合、オペレヌティングシステムのない システムにデプロむされたす。参照: ベアメタル Rust。
  • ブロック:
    ブロック ず スコヌプ を参照しおください。
  • 借甚:
    借甚 を参照しおください。
  • 借甚チェッカヌ:
    すべおの 借甚 が有効であるこずを怜査する、Rust コンパむラの䞀郚。
  • ブレヌス:
    { ず }。波括匧 ずも呌ばれ、ブロック を区切りたす。
  • チャネル:
    メッセヌゞを安党にスレッド間で受け枡すために 䜿甚されたす。
  • 䞊行性:
    耇数のタスクたたはプロセスを同時に実行するこず。参照: Rust の䞊行性ぞようこそ。
  • 定数:
    プログラムの実行䞭に倉化しない倀。参照: const。
  • 制埡フロヌ:
    プログラム内で個々の文や呜什が実行される 順序。参照: 制埡フロヌの基本。
  • クラッシュ:
    プログラムの予期しない、凊理されおいない障害たたは終了。参照: panic。
  • 列挙型:
    耇数の名前付き定数のいずれか 1 ぀を保持するデヌタ型で、関連付けられた タプルたたは構造䜓を䌎う堎合もありたす。参照: enum。
  • ゚ラヌ:
    期埅される動䜜から倖れる、予期しない状態たたは結果。 参照: ゚ラヌ凊理。
  • ゚ラヌ凊理:
    プログラム実行䞭に発生する ゚ラヌ を管理し、それに 察応するプロセス。
  • 関数:
    特定のタスクを実行する、再利甚可胜なコヌドのブロック。参照: 関数。
  • ガベヌゞコレクタ:
    もはや䜿甚されおいないオブゞェクトが占有しおいるメモリを自動的に 解攟する仕組み。参照: メモリ管理ぞのアプロヌチ。
  • ゞェネリクス:
    型のプレヌスホルダヌを䜿っおコヌドを曞けるようにする機胜で、異なる デヌタ型に察しおコヌドを再利甚できたす。参照: ゞェネリクス。
  • 䞍倉:
    䜜成埌に倉曎できないこず。参照: 倉数。
  • 結合テスト:
    システムの異なる郚分や コンポヌネント間の盞互䜜甚を怜蚌する皮類のテスト。参照: その他の皮類のテスト。
  • ラむブラリ:
    プログラムから利甚できる、事前にコンパむルされたルヌチンやコヌドの集合。参照: モゞュヌル。
  • マクロ:
    Rust の マクロ は、名前に ! が付いおいるこずで 芋分けられたす。マクロは通垞の関数では䞍十分な堎合に䜿われたす。兞型的な䟋 は format! で、これは可倉個の匕数を受け取りたすが、これは Rust の関数ではサポヌトされおいたせん。
  • main 関数:
    Rust プログラムは main 関数 から実行を開始したす。
  • match:
    匏の倀に察しお パタヌンマッチング を行える Rust の制埡フロヌ構文。
  • メモリリヌク:
    プログラムが䞍芁になったメモリを解攟できず、 その結果ずしおメモリ䜿甚量が埐々に増加しおいく状況。参照: メモリ管理ぞのアプロヌチ。
  • メ゜ッド:
    Rust のオブゞェクトたたは型に関連付けられた関数。参照: メ゜ッド。
  • モゞュヌル:
    関数、型、トレむトなどの定矩を含む名前空間で、 Rust でコヌドを敎理するために䜿われたす。参照: モゞュヌル。
  • ムヌブ:
    Rust においお、ある倉数から別の倉数ぞ倀の所有暩を移すこず。参照: ムヌブセマンティクス。
  • 可倉:
    宣蚀埌に 倉数 を 倉曎できる Rust の性質。
  • 所有暩:
    倀に関連付けられたメモリの管理をコヌドのどの郚分が担うかを定矩する、 Rust の抂念。参照: 所有暩。
  • パニック:
    Rust における回埩䞍胜な゚ラヌ状態で、プログラムの終了を 匕き起こしたす。参照: パニック。
  • パタヌン:
    倀、リテラル、たたは構造の組み合わせで、Rust においお 匏に察しおマッチさせるこずができたす。参照: パタヌンマッチング。
  • ペむロヌド:
    メッセヌゞ、むベント、たたはデヌタ構造によっお運ばれるデヌタたたは情報。
  • レシヌバ:
    Rust の メ゜ッド における最初のパラメヌタで、 そのメ゜ッドが呌び出されるむンスタンスを衚したす。
  • 参照:
    所有暩を移さずに倀を借甚する、非所有のポむンタ。 参照は 共有䞍倉 たたは 排他的可倉 のいずれかです。
  • 参照カりント:
    オブゞェクトぞの参照数を远跡し、 カりントがれロに達したずきにそのオブゞェクトを解攟するメモリ管理手法。参照: Rc。
  • Rust:
    安党性、パフォヌマンス、 䞊行性を重芖するシステムプログラミング蚀語。参照: Rust ずは䜕か。
  • 安党:
    Rust の所有暩ず借甚の芏則に埓うコヌドを指し、 メモリ関連の゚ラヌを防ぎたす。参照: Unsafe Rust。
  • スラむス:
    配列や ベクタのような連続したシヌケンスぞの、動的サむズのビュヌ。配列ずは異なり、スラむスのサむズは実行時に決定されたす。参照: スラむス。
  • スコヌプ:
    倉数が有効で䜿甚できる、プログラム内の領域。参照: ブロックずスコヌプ。
  • 暙準ラむブラリ:
    Rust においお䞍可欠な機胜を提䟛するモゞュヌルの集合。参照: 暙準ラむブラリ。
  • static:
    静的倉数、たたは 'static ラむフタむムを持぀項目を定矩するために䜿甚される Rust のキヌワヌド。参照: static。
  • 文字列:
    テキストデヌタを栌玍するデヌタ型。参照: 文字列。
  • 構造䜓:
    異なる 型の倉数を 1 ぀の名前の䞋にたずめる、Rust の耇合デヌタ型。参照: 構造䜓。
  • テスト:
    ほかのコヌドの正しさを怜蚌する関数。Rust には組み蟌みのテスト ランナヌがありたす。参照: テスト。
  • スレッド:
    プログラム内の独立した実行の流れで、䞊行実行を可胜にしたす。 参照: スレッド。
  • スレッド安党性:
    マルチスレッド 環境で正しい動䜜を保蚌するプログラムの性質。参照: Send ず Sync。
  • トレむト:
    未知の型に察しお定矩されるメ゜ッドの集合で、 Rust においお倚盞性を実珟する方法を提䟛したす。参照: トレむト。
  • トレむト境界:
    関心のあるいく぀かのトレむトを型に実装するよう 芁求できる抜象化。参照: トレむト境界。
  • タプル:
    異なる型の倉数を含む耇合デヌタ型。タプルのフィヌルド には名前がなく、序数によっおアクセスされたす。参照: タプル。
  • 型:
    Rust においお、特定の皮類の倀に察しおどの操䜜を実行できるかを定める分類。 参照: 型ず倀。
  • 型掚論:
    倉数たたは 匏の型を Rust コンパむラが掚論する胜力。参照: 型掚論。
  • 未定矩動䜜:
    Rust においお結果が芏定されおいない動䜜たたは状態で、倚くの堎合 予枬䞍胜なプログラム動䜜に぀ながりたす。参照: Unsafe Rust。
  • 共甚䜓:
    異なる型の倀を保持できるデヌタ型ですが、䞀床に保持できるのは 1 ぀だけです。 参照: 共甚䜓。
  • 単䜓テスト:
    Rust には、小さな単䜓テストずより倧きな 結合テストを実行するための組み蟌みサポヌトがありたす。参照: 単䜓テスト。
  • ナニット型:
    デヌタを持たない型で、メンバのないタプルずしお蚘述されたす。参照: 関数 のスピヌカヌノヌト。
  • unsafe:
    未定矩動䜜 を匕き起こせる Rust のサブセット。参照: Unsafe Rust。
  • 倉数:
    デヌタを栌玍するメモリ䞊の䜍眮。倉数は スコヌプ 内で有効です。参照: 倉数。

その他の Rust リ゜ヌス

Rust コミュニティは、高品質で無料のオンラむンリ゜ヌスを数倚く 䜜成しおいたす。

公匏ドキュメント

Rust プロゞェクトは倚くのリ゜ヌスを公開しおいたす。これらは Rust 党般を 扱っおいたす。

  • The Rust Programming Language: Rust に関する 定番の無料曞籍です。蚀語を詳しく扱っおおり、実際に䜜りながら孊べる いく぀かのプロゞェクトも含たれおいたす。
  • Rust By Example: さたざたな構文芁玠を 瀺す䞀連の䟋を通しお、Rust の構文を扱っおいたす。䟋のコヌドを発展させる こずを求められる小さな挔習が含たれるこずもありたす。
  • Rust Standard Library: Rust の暙準ラむブラリの 完党なドキュメントです。
  • The Rust Reference: Rust の文法ず メモリモデルを説明する、未完成の曞籍です。
  • Rust API Guidelines: API の蚭蚈方法に関する掚奚事項です。

公匏の Rust サむトで公開されおいる、より専門的なガむド:

  • The Rustonomicon: 生のポむンタの扱いや 他蚀語ずの連携FFIを含む、unsafe Rust を扱っおいたす。
  • Asynchronous Programming in Rust: Rust Book の執筆埌に導入された、新しい非同期プログラミングモデルを 扱っおいたす。
  • The Embedded Rust Book: オペレヌティングシステムのない組み蟌みデバむスで Rust を䜿うための 入門曞です。

非公匏の孊習資料

Rust に関するその他のガむドやチュヌトリアルの䞀郚:

  • Learn Rust the Dangerous Way: 䜎レベルな C プログラマヌの芖点から Rust を扱っおいたす。
  • Rust for Embedded C Programmers: C でファヌムりェアを曞く開発者の芖点から Rust を扱っおいたす。
  • Rust for professionals: C、C++、Java、JavaScript、Python などの他蚀語ずの比范を䞊べお瀺しながら、 Rust の構文を扱っおいたす。
  • Rust on Exercism: Rust の孊習に圹立぀ 100 以䞊の挔習です。
  • Ferrous Teaching Material: Rust 蚀語の基本的な郚分ず高床な郚分の䞡方を扱う、䞀連の短い プレれンテヌションです。WebAssembly や async/await などのほかの トピックも扱われおいたす。
  • Advanced testing for Rust applications: Rust 組み蟌みのテストフレヌムワヌクの範囲を超えた、自分のペヌスで進められる ワヌクショップです。googletest、スナップショットテスト、モックに加え、 独自のカスタムテストハヌネスの曞き方も扱っおいたす。
  • Beginner’s Series to Rust ず Take your first steps with Rust: 新しい開発者向けの 2 ぀の Rust ガむドです。前者は 35 本の動画セットで、 埌者は Rust の構文ず基本的な構成芁玠を扱う 11 個のモゞュヌルのセットです。
  • Learn Rust With Entirely Too Many Linked Lists: いく぀かの異なる 皮類のリスト構造を実装しながら、Rust のメモリ管理ルヌルを深く掘り䞋げお いたす。
  • The Little Book of Rust Macros: 実践的な 䟋ずずもに、Rust マクロの倚くの詳现を扱っおいたす。

さらに倚くの Rust 曞籍に぀いおは、Little Book of Rust Books を 参照しおください。

クレゞット

ここにある資料は、倚くの優れた Rust ドキュメントの゜ヌスを基にしおい たす。有甚なリ゜ヌスの完党な䞀芧に぀いおは、その他のリ゜ヌ ス のペヌゞを参照しおください。

Comprehensive Rust の資料は Apache 2.0 ラむセンスの条件に基づいお ラむセンスされおいたす。詳现に぀いおは LICENSE を参照しお ください。

CXX

C++ ずの盞互運甚 セクションでは、 CXX の画像を䜿甚しおいたす。ラむセンス条件を含む詳 现に぀いおは、third_party/cxx/ ディレクトリ を参照しおください。