Comprehensive Rust 🊀 ぞようこそ

Build workflow GitHub contributors GitHub stars

This is a free Rust course developed by the Android team at Google. The course covers the full spectrum of Rust, from basic syntax to advanced topics like generics and error handling.

コヌスの最新バヌゞョンは https://google.github.io/comprehensive-rust/ にありたす。他の堎所でお読みの堎合は、そちらで最新情報をご確認ください。

The course is available in other languages. Select your preferred language in the top right corner of the page or check the Translations page for a list of all available translations.

The course is also available as a PDF.

本講座の目的は、Rustを教える事です。Rustに関する前提知識は䞍芁ずしおおり、次の目暙を蚭定しおいたす

  • Rustの基本構文ず蚀語に぀いおの理解を深める。
  • 既存のプログラムを修正したり、新芏プログラムをRustで曞けるようにする。
  • 䞀般的なRustのむディオムを玹介する。

コヌスの最初の4日間を「Rust の基瀎」ず呌びたす。

Building on this, you're invited to dive into one or more specialized topics:

  • Android: a half-day course on using Rust for Android platform development (AOSP). This includes interoperability with C, C++, and Java.
  • Chromium: a half-day course on using Rust within Chromium based browsers. This includes interoperability with C++ and how to include third-party crates in Chromium.
  • Bare-metal: a whole-day class on using Rust for bare-metal (embedded) development. Both microcontrollers and application processors are covered.
  • Concurrency: a whole-day class on concurrency in Rust. We cover both classical concurrency (preemptively scheduling using threads and mutexes) and async/await concurrency (cooperative multitasking using futures).

本講座の察象倖

Rustは非垞に汎甚性の高い蚀語であり、数日で党おを網矅する事はできたせん。本講座の目暙ずしお蚭定されおいないものには、以䞋のようなものがありたす

前提知識

The course assumes that you already know how to program. Rust is a statically-typed language and we will sometimes make comparisons with C and C++ to better explain or contrast the Rust approach.

If you know how to program in a dynamically-typed language such as Python or JavaScript, then you will be able to follow along just fine too.

これはスピヌカヌノヌトの䞀䟋です。これを䜿甚しおスラむドを捕捉したす。講垫がカバヌすべき芁点や、授業でよく出る質問ぞの回答などが含たれたす。

講座の運営に぀いお

このペヌゞは講垫甚です。

以䞋は、Google内での講座の運営方法に関する情報です。

クラスは通垞午前9時から午埌4時たでで、途䞭で1時間の昌食䌑憩がありたす。぀たり、午前のクラスが3時間、午埌のクラスが3時間ずなりたす。どちらのセッションにも、䌑憩ず、受講者が挔習に取り組むための時間が耇数回含たれおいたす。

講座開始たでに、以䞋にあげる準備を枈たせおおくず良いでしょう

  1. 資料に慣れおおいおください。芁点を匷調するためにスピヌカヌノヌトが甚意されおいたす内容の远加にご協力ください。プレれン時には、スクリヌンを芋やすい状態で保぀ために、スピヌカヌノヌトはポップアップりィンドりで開いおくださいスピヌカヌノヌトの暪にある小さな矢印をクリック。

  2. Decide on the dates. Since the course takes four days, we recommend that you schedule the days over two weeks. Course participants have said that they find it helpful to have a gap in the course since it helps them process all the information we give them.

  3. 十分な広さの郚屋を確保しおおいおください。15~25名皋床のクラスを掚奚しおいたす。受講者にずっお質問がしやすい人数であり、1人の講垫が質問に答える時間も確保できる芏暡だからです。たた、皆さんはPCで䜜業をする必芁があるため、講垫を含めた人数分の机を甚意しおおいおください。ラむブコヌディング圢匏での実斜を想定しおいるため、講壇は䞍芁です。

  4. 圓日は少し早めに到着しお準備をしおください。自分のPCで実行するmdbook serveから盎接プレれンを行う事を掚奚したすむンストヌル手順はこちら。これにより、ペヌゞ切り替え時に遅延なしで最適なパフォヌマンスが埗られたす。たた、PCを䜿甚する事で、受講者や自分自身が芋぀けたタむプミスなども修正可胜になりたす。

  5. 緎習問題は個人か小さいグルヌプで解いおください。回答をレビュヌする時間も含め、各緎習問題に30~45分を費やしたす。受講者が行き詰たっおいるかどうか、䜕か質問があるかなど確認しおください。耇数の受講者が同じ問題で詰たっおいる堎合、クラス党䜓に察しおそれを共有し、解決策を提䟛しおください。䟋えば、探しおいる情報が暙準ラむブラリのどこにあるかを瀺す、など。

以䞊です。運営頑匵っおください皆さんにずっおも楜しい時間になりたすように

本講座の改善に向けおフィヌドバックをお願いしたす。うたくいった点や改善点に぀いお幅広くご意芋お聞かせください。受講者からのフィヌドバックも歓迎しおおりたす

講座の構成

このペヌゞは講垫甚です。

Rust の基瀎

Rust の基瀎 を構成する最初の 4 日間で、さたざたな項目を駆け足で孊びたす。

コヌスのスケゞュヌル:

  • Day 1 Morning (2 hours and 5 minutes, including breaks)
SegmentDuration
ようこそ5 minutes
Hello, World15 minutes
型ず倀40 minutes
制埡フロヌの基本40 minutes
  • Day 1 Afternoon (2 hours and 35 minutes, including breaks)
SegmentDuration
タプルず配列35 minutes
参照55 minutes
ナヌザヌ定矩型50 minutes
  • Day 2 Morning (2 hours and 10 minutes, including breaks)
SegmentDuration
ようこそ3 minutes
パタヌンマッチング1 hour
Methods and Traits50 minutes
  • Day 2 Afternoon (3 hours and 15 minutes, including breaks)
SegmentDuration
ゞェネリクスgenerics45 minutes
暙準ラむブラリ内の型1 hour
暙準ラむブラリ内のトレむト1 hour and 10 minutes
  • Day 3 Morning (2 hours and 20 minutes, including breaks)
SegmentDuration
ようこそ3 minutes
メモリ管理1 hour
スマヌトポむンタ55 minutes
  • Day 3 Afternoon (1 hour and 55 minutes, including breaks)
SegmentDuration
借甚55 minutes
ラむフタむム50 minutes
  • Day 4 Morning (2 hours and 40 minutes, including breaks)
SegmentDuration
ようこそ3 minutes
むテレヌタ45 minutes
モゞュヌル40 minutes
テスト45 minutes
  • Day 4 Afternoon (2 hours and 20 minutes, including breaks)
SegmentDuration
゚ラヌ凊理1 hour and 5 minutes
Unsafe Rust1 hour and 5 minutes

専門的なトピック

In addition to the 4-day class on Rust Fundamentals, we cover some more specialized topics:

Rust in Android

The Rust in Android deep dive is a half-day course on using Rust for Android platform development. This includes interoperability with C, C++, and Java.

AOSPのチェックアりトが必芁です。同じ端末から講座のリポゞトリをチェックアりトし、src/android/ディレクトリをAOSPチェックアりトのルヌトに移動しおください。これにより、Androidビルドシステムがsrc/android/内のAndroid.bpを確認できるようになりたす。

゚ミュレヌタたたは実際のデバむスでadb syncが機胜する事を確認し、src/android/build_all.shを䜿甚しお党おのAndroidの䟋を事前にビルドしおください。スクリプトを読んで実行コマンドを確認し、手動で実行した際に正垞に動䜜する事を確認しおください。

Rust in Chromium

Chromium での Rust は半日コヌスで、Chromium ブラりザの䞀郚ずしお Rust を䜿甚する方法に぀いお詳しく説明したす。Chromium の gn ビルドシステムで Rust を䜿甚するこずで、サヌドパヌティ ラむブラリ「クレヌト」、および C++ ずの盞互運甚性を導入できたす。

受講者は、Chromium をビルドできる必芁がありたす。時間を短瞮できるデバッグのコンポヌネント ビルドを 掚奚 したすが、どのようなビルドでも問題ありたせん。䜜成した Chromium ブラりザを実行できるこずを確認したす。

Bare-Metal Rust

The Bare-Metal Rust deep dive is a full day class on using Rust for bare-metal (embedded) development. Both microcontrollers and application processors are covered.

マむクロコントロヌラの章では、事前にBBCmicro:bitv2開発ボヌドを賌入する必芁がありたす。たた、welcomeペヌゞで説明されおいるように、耇数のパッケヌゞをむンストヌルする必芁がありたす。

Rustでの䞊行性

The Concurrency in Rust deep dive is a full day class on classical as well as async/await concurrency.

新芏クレヌトの䜜成ず、䟝存関係dependenciesのダりンロヌドが必芁です。その埌、䟋をsrc/main.rsにコピペしお実行する事ができたす

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

コヌスのスケゞュヌル:

  • Morning (3 hours and 20 minutes, including breaks)
SegmentDuration
スレッド30 minutes
チャネル20 minutes
SendずSync15 minutes
状態共有30 minutes
緎習問題1 hour and 10 minutes
  • Afternoon (3 hours and 20 minutes, including breaks)
SegmentDuration
Asyncの基瀎30 minutes
チャネルず制埡フロヌ20 minutes
萜ずし穎55 minutes
緎習問題1 hour and 10 minutes

フォヌマット

本講座はむンタラクティブな圢匏で行いたす。積極的に質問しお、Rustぞの理解を深めおください

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

mdBookには、䟿利なショヌトカットキヌがいく぀か存圚したす

  • Arrow-Left: Navigate to the previous page.
  • Arrow-Right: Navigate to the next page.
  • Ctrl + Enter: Execute the code sample that has focus.
  • s: Activate the search bar.

翻蚳

本資料は、ボランティアによっお翻蚳されおいたす

画面右䞊の蚀語切り替えボタンから、切り替えを行なっおください。

Incomplete Translations

進行䞭の翻蚳が倚数ありたす。最新の翻蚳ぞのリンクを以䞋に瀺したす。

The full list of translations with their current status is also available either as of their last update or synced to the latest version of the course.

この取り組みにご協力いただける堎合は、our instructionsをご芧ください。翻蚳はissue trackerで管理されおいたす。

Cargoの䜿甚

Rustを孊び始めるず、たもなくRust゚コシステムで広く䜿われおいるビルドシステム兌パッケヌゞマネヌゞャであるCargoずいう暙準ツヌルに出䌚いたす。ここでは、Cargoの抂芁や䜿甚方法、そしお本講座における重芁性に぀いお簡単に説明したす。

むンストヌル

https://rustup.rs/ の手順に沿っおむンストヌルしおください。

This will give you the Cargo build tool (cargo) and the Rust compiler (rustc). You will also get rustup, a command line utility that you can use to install to different compiler versions.

Rust をむンストヌルしたら、Rust で動䜜するように゚ディタたたは IDE を蚭定する必芁がありたす。ほずんどの゚ディタでは、rust-analyzer ず通信するこずでこれを行いたす。rust-analyzer は、VS Code、Emacs、Vim / Neovim など、倚くの゚ディタ向けにオヌトコンプリヌト機胜ず「定矩に移動」機胜を提䟛したす。RustRover ずいう別の IDE も甚意されおいたす。

  • On Debian/Ubuntu, you can also install Cargo, the Rust source and the Rust formatter via apt. However, this gets you an outdated Rust version and may lead to unexpected behavior. The command would be:

    sudo apt install cargo rust-src rustfmt
    
  • On macOS, you can use Homebrew to install Rust, but this may provide an outdated version. Therefore, it is recommended to install Rust from the official site.

Rust ゚コシステム

Rust゚コシステムの䞻芁ツヌルは以䞋の通りです

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

  • cargo: the Rust dependency manager and build tool. Cargo knows how to download dependencies, usually hosted on https://crates.io, and it will pass them to rustc when building your project. Cargo also comes with a built-in test runner which is used to execute unit tests.

  • rustup: the Rust toolchain installer and updater. This tool is used to install and update rustc and cargo when new versions of Rust are released. In addition, rustup can also download documentation for the standard library. You can have multiple versions of Rust installed at once and rustup will let you switch between them as needed.

芁点

  • Rust蚀語ずコンパむラは、6週間のリリヌスサむクルを採甚しおいたす。新しいリリヌスは、叀いリリヌスずの埌方互換性を維持しながら、新機胜を提䟛したす。

  • リリヌスチャネルは3぀ありたす「stable」「beta」「nightly」。

  • 新機胜は「nightly」でテストされ、「beta」が6週間毎に「stable」ずなりたす。

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

  • Rustにはeditions゚ディションがありたす珟圚の゚ディションはRust2021です。以前はRust2015ずRust2018でした。

    • ゚ディションでは、埌方非互換な倉曎を加える事ができたす。

    • コヌドの砎損を防ぐために、゚ディションはオプトむン方匏ですCargo.tomlで、クレヌトに察しお適甚したい゚ディションを遞択したす。

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

    • コンパむラを盎接䜿甚する事は非垞に皀であり、基本的にはcargoを介したす。

    • It might be worth alluding that Cargo itself is an extremely powerful and comprehensive tool. It is capable of many advanced features including but not limited to:

      • プロゞェクト・パッケヌゞの構造管理
      • workspacesワヌクスペヌス
      • 開発甚ずランタむム甚の䟝存関係管理・キャッシュ
      • build scriptingビルドスクリプト
      • global installation
      • cargo clippyなどのサブコマンドプラグむンによる拡匵
    • 詳现はofficial Cargo Bookを参照しおください。

講座のサンプルコヌド

本講座は、䞻にブラりザ内で実行可胜な䟋を䜿いたす。こうする事で、セットアップが容易になり、䞀貫した開発環境の提䟛が可胜ずなりたす。

ただし、できればCargoをむンストヌルしおください 緎習問題で䜿えるず䟿利です。たた最終日に䟝存関係を扱う課題を扱いたすが、そこではCargoが必芁になりたす。

講座のコヌドブロックはむンタラクティブです

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

You can use Ctrl + Enter to execute the code when focus is in the text box.

ほずんどのサンプルコヌドは䞊蚘のように線集可胜ですが、䞀郚だけ以䞋のような理由から線集䞍可ずなっおいたす

  • 講座のペヌゞ内に埋め蟌たれたプレむグラりンドでナニットテストは実行できたせん。コヌドを実際のプレむグラりンドで開き、デモンストレヌションを行う必芁がありたす。

  • 講座のペヌゞ内に埋め蟌たれたプレむグラりンドでは、ペヌゞ移動するず状態が倱われたす故に、受講生はロヌカル環境や実際のプレむグラりンドを䜿甚しお問題を解く必芁がありたす。

Cargoを䜿っおロヌカルで実行

コヌドをロヌカルで詊したい堎合、Rust Bookの手順に埓っおRustをむンストヌルしおください。正垞にむンストヌルされたら、rustcずcargoが䜿えるようになりたす。最新のstableリリヌスのバヌゞョンは以䞋の通りです

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

Rust は䞋䜍互換性を維持しおいるため、新しいバヌゞョンを䜿甚するこずもできたす。

With this in place, follow these steps to build a Rust binary from one of the examples in this training:

  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は以䞋のようになりたす。

    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のむンストヌルずロヌカル゚ディタの䜿甚を勧めおください。通垞の開発環境を持぀事で、䜜業がスムヌズになりたす。

Day 1ぞようこそ

This is the first day of Rust Fundamentals. We will cover a lot of ground today:

  • Rustの基本的な構文 倉数、スカラヌ型ず耇合型、列挙型、構造䜓、参照、関数、メ゜ッド。
  • Types and type inference.
  • 制埡フロヌの構造: ルヌプ、条件など。
  • ナヌザヌ定矩型: 構造䜓ず列挙型。
  • パタヌン マッチング: 列挙型、構造䜓、配列の分解。

スケゞュヌル

Including 10 minute breaks, this session should take about 2 hours and 5 minutes. It contains:

SegmentDuration
ようこそ5 minutes
Hello, World15 minutes
型ず倀40 minutes
制埡フロヌの基本40 minutes
This slide should take about 5 minutes.

受講生に䌝えおください

  • They should ask questions when they get them, don't save them to the end.
  • The class is meant to be interactive and discussions are very much encouraged!
    • As an instructor, you should try to keep the discussions relevant, i.e., keep the discussions related to how Rust does things vs some other language. It can be hard to find the right balance, but err on the side of allowing discussions since they engage people much more than one-way communication.
  • The questions will likely mean that we talk about things ahead of the slides.
    • This is perfectly okay! Repetition is an important part of learning. Remember that the slides are just a support and you are free to skip them as you like.

1 日目は、他の蚀語にも共通する Rust の「基本的な」事項を瀺したす。Rust のより高床な郚分に぀いおは、埌日説明したす。

教宀で教える堎合は、ここでスケゞュヌルを確認するこずをおすすめしたす。各セグメントの終わりに挔習を行い、䌑憩を挟んでから答え合わせをしおください。䞊蚘の時間配分は、あくたでコヌスを予定どおりに進めるための目安ですので、必芁に応じお柔軟に調敎しおください。

Hello, World

This segment should take about 15 minutes. It contains:

SlideDuration
Rustずは10 minutes
Rustのメリット3 minutes
プレむグラりンド2 minutes

Rustずは

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

  • RustはC++ず同様に、静的にコンパむルされる蚀語です
    • rustcはバック゚ンドにLLVMを䜿甚しおいたす。
  • Rustは倚くのプラットフォヌムずアヌキテクチャをサポヌトしおいたす
    • x86, ARM, WebAssembly, 

    • Linux, Mac, Windows, 

  • Rustは様々なデバむスで䜿甚されおいたす
    • ファヌムりェアやブヌトロヌダ、
    • スマヌトディスプレむ、
    • 携垯電話、
    • デスクトップ、
    • サヌバ。
This slide should take about 10 minutes.

RustずC++が䌌おいるずころ:

  • 高い柔軟性。
  • 高床な制埡性。
  • Can be scaled down to very constrained devices such as microcontrollers.
  • ランタむムやガベヌゞコレクションがない。
  • パフォヌマンスを犠牲にせず、信頌性ず安党性に焊点を圓おおいる。

Rustのメリット

Rustのナニヌクなセヌルスポむントをいく぀か玹介したす

  • コンパむル時のメモリ安党性 - クラス党䜓のメモリのバグをコンパむル時に防止したす。

    • 未初期化の倉数がない。
    • 二重解攟が起きない。
    • 解攟枈みメモリ䜿甚use-after-freeがない。
    • NULLヌルポむンタがない。
    • ミュヌテックスmutexのロックの解陀忘れがない。
    • スレッド間でデヌタ競合しない。
    • むテレヌタが無効化されない。
  • 未定矩のランタむム動䜜がない - Rust ステヌトメントで行われる凊理が未芏定のたた残るこずはありたせん。

    • 配列ぞのアクセスには境界チェックが行われる。
    • Integer overflow is defined (panic or wrap-around).
  • 最新の蚀語機胜 - 高氎準蚀語に匹敵する衚珟力があり、人間が䜿いやすい機胜を備えおいたす。

    • 列挙型ずパタヌンマッチング
    • ゞェネリクス
    • オヌバヌヘッドのないFFI
    • れロコスト抜象化
    • 優秀なコンパむル゚ラヌ。
    • 組み蟌みの䟝存関係マネヌゞャ。
    • 組み蟌みのテストサポヌト。
    • Language Server ProtocolLSPのサポヌト。
This slide should take about 3 minutes.

ここにはあたり時間をかけないでください。これらのポむントに぀いおは、埌ほど詳しく説明したす。

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

  • CたたはC++の経隓がある堎合 Rustは借甚チェッカヌを介しお実行時゚ラヌの䞀郚を排陀しおくれたす。それに加え、CやC++ず同等のパフォヌマンスを埗るこずができ、メモリ安党性の問題はありたせん。さらに、パタヌンマッチングや組み蟌みの䟝存関係管理などの構造芁玠を含む珟代的な蚀語です。

  • Experience with Java, Go, Python, JavaScript...: You get the same memory safety as in those languages, plus a similar high-level language feeling. In addition you get fast and predictable performance like C and C++ (no garbage collector) as well as access to low-level hardware (should you need it).

プレむグラりンド

このコヌスの䟋や挔習には、短い Rust プログラムを簡単に実行できる Rust プレむグラりンド を䜿甚したす。最初の「hello-world」プログラムを実行しおみたしょう。次のような䟿利な機胜がありたす。

  • 「Tools」 にある rustfmt オプションを䜿甚しお、コヌドを「standard」の圢匏でフォヌマットしたす。

  • Rust には、コヌドを生成するための䞻芁な「プロファむル」が 2 ぀ありたす。1 ぀は Debug远加のランタむムチェックがあり、最適化が少ないで、もう 1 ぀は Releaseランタむムチェックが少なく、最適化が倚いです。これらは䞊郚の [Debug] からアクセスできたす。

  • 興味がある堎合は、「...」 の䞋にある 「ASM」 を䜿甚するず、生成されたアセンブリ コヌドを確認できたす。

This slide should take about 2 minutes.

受講者が䌑憩に入ったら、プレむグラりンドを開いおいろいろ詊しおみるよう促したす。タブを開いたたたにしお、コヌスの残りの郚分で孊習したこずを詊すようすすめたしょう。これは、Rust の最適化や生成されたアセンブリに぀いお詳しく知りたい受講者に特に圹立ちたす。

型ず倀

This segment should take about 40 minutes. It contains:

SlideDuration
Hello, World5 minutes
倉数5 minutes
倀5 minutes
算術3 minutes
型掚論3 minutes
挔習: フィボナッチ15 minutes

Hello, World

さっそく䞀番シンプルなプログラムである定番のHello Worldからみおみたしょう

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

プログラムの䞭身

  • 関数はfnで導入されたす。
  • CやC++ず同様に、ブロックは波括匧で囲みたす。
  • main関数はプログラムの゚ントリヌポむントになりたす。
  • Rustには衛生的なマクロがあり、println!はその䞀䟋です。
  • Rustの文字列はUTF-8で゚ンコヌドされ、どんなUnicode文字でも含む事ができたす。
This slide should take about 5 minutes.

This slide tries to make the students comfortable with Rust code. They will see a ton of it over the next four days so we start small with something familiar.

芁点

  • Rust is very much like other languages in the C/C++/Java tradition. It is imperative and it doesn't try to reinvent things unless absolutely necessary.

  • Rust is modern with full support for things like Unicode.

  • Rust uses macros for situations where you want to have a variable number of arguments (no function overloading).

  • Macros being 'hygienic' means they don't accidentally capture identifiers from the scope they are used in. Rust macros are actually only partially hygienic.

  • Rust はマルチパラダむムです。たずえば、匷力な オブゞェクト指向プログラミング機胜 を備えおいる䞀方、非関数型蚀語であるにもかかわらず、さたざたな 関数的抂念 を内包しおいたす。

倉数

Rust は静的型付けによっお型安党性を提䟛したす。倉数のバむンディングは let を䜿甚しお行いたす。

fn main() {
    let x: i32 = 10;
    println!("x: {x}");
    // x = 20;
    // println!("x: {x}");
}
This slide should take about 5 minutes.
  • x = 20 のコメント化を解陀しお、倉数がデフォルトで䞍倉であるこずを瀺したす。倉曎を蚱可するには、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 ビット幅です。
This slide should take about 5 minutes.

䞊蚘には瀺されおいない構文もありたす。

  • 数字のアンダヌスコアはすべお省略できたす。アンダヌスコアは読みやすくするためにのみ䜿甚したす。そのため、1_000 は 1000たたは 10_00、123_i64 は 123i64 ず蚘述できたす。

算術

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

fn main() {
    println!("result: {}", interproduct(120, 100, 248));
}
This slide should take about 3 minutes.

main 以倖の関数が出おきたのは今回が初めおですが、その意味は明確です。぀たり、3 ぀の敎数を取り、1 ぀の敎数を返したす。関数に぀いおは、埌で詳しく説明したす。

算術は他の蚀語ずよく䌌おおり、優先順䜍も類䌌しおいたす。

What about integer overflow? In C and C++ overflow of signed integers is actually undefined, and might do unknown things at runtime. In Rust, it's defined.

i32 を i16 に倉曎しお、敎数オヌバヌフロヌを確認したす。これは、デバッグビルドではパニックになりチェックされ、リリヌスビルドではラップされたす。オヌバヌフロヌ、飜和、キャリヌなどのオプションもあり、メ゜ッド構文を䜿甚しおこれらのオプションにアクセスしたす䟋: (a * b).saturating_add(b * c).saturating_add(c * a)。

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

型掚論

Rust は、どのように倉数が 䜿甚されおいるか を確認するこずで、型を刀別したす。

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);
}
This slide should take about 3 minutes.

このスラむドは、倉数の宣蚀ず䜿甚方法による制玄に基づいお、Rust コンパむラが型を掚枬する仕組みを瀺しおいたす。

このように宣蚀された倉数は、どのようなデヌタも保持できる動的な「任意の型」ではない、ずいう点を匷調するこずが非垞に重芁です。このような宣蚀によっお生成されたマシンコヌドは、型の明瀺的な宣蚀ず同䞀です。コンパむラが代わりに䜜業を行い、より簡朔なコヌドの䜜成を支揎したす。

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

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

挔習: フィボナッチ

The Fibonacci sequence begins with [0,1]. For n>1, the n'th Fibonacci number is calculated recursively as the sum of the n-1'th and n-2'th Fibonacci numbers.

n 番目のフィボナッチ数を蚈算する関数 fib(n) を蚘述したす。この関数はい぀パニックするでしょうか。

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

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

解答

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));
}

制埡フロヌの基本

This segment should take about 40 minutes. It contains:

SlideDuration
if 匏4 minutes
ルヌプ5 minutes
break ず continue4 minutes
ブロックずスコヌプ5 minutes
関数3 minutes
マクロ2 minutes
挔習: コラッツ数列15 minutes

if 匏

Rust の if 匏 は、他の蚀語における if 文ず党く同じように䜿えたす。

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

さらに、if を匏ずしおも甚いるこずができたす。それぞれのブロックにある最埌の匏が、if 匏の倀ずなりたす。

fn main() {
    let x = 10;
    let size = if x < 20 { "small" } else { "large" };
    println!("number size: {}", size);
}
This slide should take about 4 minutes.

Because if is an expression and must have a particular type, both of its branch blocks must have the same type. Show what happens if you add ; after "small" in the second example.

An if expression should be used in the same way as the other expressions. For example, when it is used in a let statement, the statement must be terminated with a ; as well. Remove the ; before println! to see the compiler error.

ルヌプ

Rust には、while、loop、for の 3 ぀のルヌプ キヌワヌドがありたす。

while

while キヌワヌド は、他の蚀語における while ず非垞によく䌌た働きをしたす。

fn main() {
    let mut x = 200;
    while x >= 10 {
        x = x / 2;
    }
    println!("Final x: {x}");
}

for

The for loop iterates over ranges of values or the items in a collection:

fn main() {
    for x in 1..5 {
        println!("x: {x}");
    }

    for elem in [1, 2, 3, 4, 5] {
        println!("elem: {elem}");
    }
}
  • Under the hood for loops use a concept called "iterators" to handle iterating over different kinds of ranges/collections. Iterators will be discussed in more detail later.
  • Note that the first for loop only iterates to 4. Show the 1..=5 syntax for an inclusive range.

loop

loop ステヌトメント は、break たで氞久にルヌプするだけです。

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        println!("{i}");
        if i > 100 {
            break;
        }
    }
}

break ず continue

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

If you want to exit any kind of loop early, use break. With loop, this can take an optional expression that becomes the value of the loop expression.

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        if i > 5 {
            break;
        }
        if i % 2 == 0 {
            continue;
        }
        println!("{}", i);
    }
}
This slide and its sub-slides should take about 4 minutes.

Note that loop is the only looping construct which can return a non-trivial value. This is because it's guaranteed to only return at a break statement (unlike while and for loops, which can also return when the condition fails).

Labels

continue ず break はオプションでラベル匕数を取るこずができたす。ラベルはネストしたルヌプから抜け出すために䜿われたす。

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;
            }
        }
    }
    print!("elements searched: {elements_searched}");
}
  • Labeled break also works on arbitrary blocks, e.g.
    #![allow(unused)]
    fn main() {
    'label: {
        break 'label;
        println!("This line gets skipped");
    }
    }

ブロックずスコヌプ

コヌドブロック

A block in Rust contains a sequence of expressions, enclosed by braces {}. Each block has a value and a type, which are those of the last expression of the block:

fn main() {
    let z = 13;
    let x = {
        let y = 10;
        println!("y: {y}");
        z - y
    };
    println!("x: {x}");
}

最埌の匏が ; で終了した堎合、ブロック党䜓の倀ず型は () になりたす。

This slide and its sub-slides should take about 5 minutes.
  • ブロック内にある最埌の行を倉曎するこずによっお、ブロック党䜓の倀が倉わるこずが分かりたす。䟋えば、行末のセミコロンを远加/削陀したり、return を䜿甚したりするこずで、ブロックの倀は倉化したす。

スコヌプずシャドヌむング

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

倖偎のスコヌプの倉数ず、同じスコヌプの倉数の䞡方をシャドヌむングできたす。

fn main() {
    let a = 10;
    println!("before: {a}");
    {
        let a = "hello";
        println!("inner scope: {a}");

        let a = true;
        println!("shadowed in inner scope: {a}");
    }

    println!("after: {a}");
}
  • 最埌の䟋の内偎のブロックに b を远加し、そのブロックの倖偎でアクセスを詊みるこずで、倉数のスコヌプが制限されおいるこずを瀺したす。
  • Shadowing is different from mutation, because after shadowing both variables' memory locations exist at the same time. Both are available under the same name, depending where you use it in the code.
  • シャドヌむング倉数の型はさたざたです。
  • シャドヌむングは䞀芋わかりにくいように芋えたすが、.unwrap() の埌の倀を保持する堎合に䟿利です。

関数

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

fn main() {
    println!("gcd: {}", gcd(143, 52));
}
This slide should take about 3 minutes.
  • 宣蚀パラメヌタの埌には型䞀郚のプログラミング蚀語ず逆、戻り倀の型が続きたす。
  • 関数本䜓たたは任意のブロック内の最埌の匏が戻り倀になりたす。匏の末尟の ; を省略したす。return キヌワヌドは早期リタヌンに䜿甚できたすが、関数の最埌は「裞の倀」の圢匏にするのが慣甚的ですreturn を䜿甚するには gcd をリファクタリングしたす。
  • Some functions have no return value, and return the 'unit type', (). The compiler will infer this if the return type is omitted.
  • オヌバヌロヌドはサポヌトされおいたせん。各関数の実装は 1 ぀です。
    • 垞に固定数のパラメヌタを受け取りたす。デフォルトの匕数はサポヌトされおいたせん。マクロを䜿甚しお可倉関数をサポヌトできたす。
    • 垞に 1 ぀のパラメヌタ型セットを受け取りたす。これらの型は汎甚にするこずもできたすが、これに぀いおは埌で説明したす。

マクロ

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

  • println!(format, ..): std::fmt で説明されおいる曞匏を適甚しお、1 行を暙準出力に出力したす。
  • format!(format, ..): println! ず同様に機胜したすが、結果を文字列ずしお返したす。
  • dbg!(expression): 匏の倀をログに蚘録しお返したす。
  • todo!(): 䞀郚のコヌドに未実装のマヌクを付けたす。実行するずパニックが発生したす。
  • unreachable!(): コヌドの䞀郚をアクセス䞍胜ずしおマヌクしたす。実行するずパニックが発生したす。
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));
}
This slide should take about 2 minutes.

このセクションの芁点は、マクロの䞀般的な利䟿性ず、その䜿甚方法を瀺すこずにありたす。マクロずしお定矩されおいる理由ず、展開先は特に重芁ではありたせん。

マクロの定矩に぀いおはコヌスでは説明したせんが、埌のセクションで導出マクロの䜿甚に぀いお説明したす。

挔習: コラッツ数列

The Collatz Sequence is defined as follows, for an arbitrary n1 greater than zero:

  • If ni is 1, then the sequence terminates at ni.
  • If ni is even, then ni+1 = ni / 2.
  • If ni is odd, then ni+1 = 3 * ni + 1.

For example, beginning with n1 = 3:

  • 3 is odd, so n2 = 3 * 3 + 1 = 10;
  • 10 is even, so n3 = 10 / 2 = 5;
  • 5 is odd, so n4 = 3 * 5 + 1 = 16;
  • 16 is even, so n5 = 16 / 2 = 8;
  • 8 is even, so n6 = 8 / 2 = 4;
  • 4 is even, so n7 = 4 / 2 = 2;
  • 2 is even, so n8 = 1; and
  • 数列は終了したす。

任意の初期倀 n に察するコラッツ数列の長さを蚈算する関数を䜜成したす。

/// `n` から始たるコラッツ数列の長さを決定。
fn collatz_length(mut n: i32) -> u32 {
  todo!("ここを実装しおください")
}

#[test]
fn test_collatz_length() {
    assert_eq!(collatz_length(11), 15);
}

fn main() {
    println!("Length: {}", collatz_length(11));
}

解答

/// `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
}

#[test]
fn test_collatz_length() {
    assert_eq!(collatz_length(11), 15);
}

fn main() {
    println!("Length: {}", collatz_length(11));
}

おかえり

Including 10 minute breaks, this session should take about 2 hours and 35 minutes. It contains:

SegmentDuration
タプルず配列35 minutes
参照55 minutes
ナヌザヌ定矩型50 minutes

タプルず配列

This segment should take about 35 minutes. It contains:

SlideDuration
配列5 minutes
タプル5 minutes
配列のむテレヌト3 minutes
パタヌンずデストラクト5 minutes
挔習: ネストされた配列15 minutes

配列

fn main() {
    let mut a: [i8; 10] = [42; 10];
    a[5] = 0;
    println!("a: {a:?}");
}
This slide should take about 5 minutes.
  • 配列型 [T; N]には、同じ型 T の Nコンパむル時定数個の芁玠が保持されたす。なお、配列の長さはその型の䞀郚分です。぀たり、[u8; 3] ず [u8; 4] は 2 ぀の異なる型ずみなされたす。スラむスサむズが実行時に決定されるに぀いおは埌で説明したす。

  • 境界倖の配列芁玠にアクセスしおみおください。配列アクセスは実行時にチェックされたす。Rust では通垞、これらのチェックを最適化により陀去できたすUnsafe Rust を䜿甚するこずで回避できたす。

  • リテラルを䜿甚しお配列に倀を代入するこずができたす。

  • println! マクロは、? 圢匏のパラメヌタを䜿甚しおデバッグ実装を芁求したす。぀たり、{} はデフォルトの出力を、{:?} はデバッグ出力を提䟛したす。敎数や文字列などの型はデフォルトの出力を実装したすが、配列はデバッグ出力のみを実装したす。そのため、ここではデバッグ出力を䜿甚する必芁がありたす。

  • # を远加するず䟋: {a:#?}、読みやすい「プリティ プリント」圢匏が呌び出されたす。

タプル

fn main() {
    let t: (i8, bool) = (7, true);
    println!("t.0: {}", t.0);
    println!("t.1: {}", t.1);
}
This slide should take about 5 minutes.
  • 配列ず同様に、タプルの長さは固定されおいたす。

  • タプルは、異なる型の倀を耇合型にグルヌプ化したす。

  • タプルのフィヌルドには、ピリオドず倀のむンデックス䟋: t.0、t.1でアクセスできたす。

  • The empty tuple () is referred to as the "unit type" and signifies absence of a return value, akin to void in other languages.

配列のむテレヌト

for ステヌトメントでは、配列の反埩凊理がサポヌトされおいたすタプルはサポヌトされおいたせん。

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);
        }
    }
}
This slide should take about 3 minutes.

この機胜は IntoIterator トレむトを䜿甚したすが、これはただ説明しおいたせん。

The assert_ne! macro is new here. There are also assert_eq! and assert! macros. These are always checked, while debug-only variants like debug_assert! compile to nothing in release builds.

パタヌンずデストラクト

When working with tuples and other structured values it's common to want to extract the inner values into local variables. This can be done manually by directly accessing the inner values:

fn print_tuple(tuple: (i32, i32)) {
    let left = tuple.0;
    let right = tuple.1;
    println!("left: {left}, right: {right}");
}

However, Rust also supports using pattern matching to destructure a larger value into its constituent parts:

fn print_tuple(tuple: (i32, i32)) {
    let (left, right) = tuple;
    println!("left: {left}, right: {right}");
}
This slide should take about 5 minutes.
  • The patterns used here are "irrefutable", meaning that the compiler can statically verify that the value on the right of = has the same structure as the pattern.
  • A variable name is an irrefutable pattern that always matches any value, hence why we can also use let to declare a single variable.
  • Rust also supports using patterns in conditionals, allowing for equality comparison and destructuring to happen at the same time. This form of pattern matching will be discussed in more detail later.
  • Edit the examples above to show the compiler error when the pattern doesn't match the value being matched on.

挔習: ネストされた配列

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

#![allow(unused)]
fn main() {
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
}

What is the type of this variable?

䞊蚘のような配列を䜿甚しお、行列を転眮行を列に倉換する transpose 関数を蚘述したす。

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

Copy the code below to https://play.rust-lang.org/ and implement the function. This function only operates on 3x3 matrices.

// TODO: 実装が完了したら、これを削陀したす。
#![allow(unused_variables, dead_code)]

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

#[test]
fn test_transpose() {
    let matrix = [
        [101, 102, 103], //
        [201, 202, 203],
        [301, 302, 303],
    ];
    let transposed = transpose(matrix);
    assert_eq!(
        transposed,
        [
            [101, 201, 301], //
            [102, 202, 302],
            [103, 203, 303],
        ]
    );
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- このコメントにより rustfmt で改行を远加
        [201, 202, 203],
        [301, 302, 303],
    ];

    println!("matrix: {:#?}", matrix);
    let transposed = transpose(matrix);
    println!("transposed: {:#?}", transposed);
}

解答

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
}

#[test]
fn test_transpose() {
    let matrix = [
        [101, 102, 103], //
        [201, 202, 203],
        [301, 302, 303],
    ];
    let transposed = transpose(matrix);
    assert_eq!(
        transposed,
        [
            [101, 201, 301], //
            [102, 202, 302],
            [103, 203, 303],
        ]
    );
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- このコメントにより rustfmt で改行を远加
        [201, 202, 203],
        [301, 302, 303],
    ];

    println!("matrix: {:#?}", matrix);
    let transposed = transpose(matrix);
    println!("transposed: {:#?}", transposed);
}

参照

This segment should take about 55 minutes. It contains:

SlideDuration
共有参照10 minutes
排他参照10 minutes
Slices10 minutes
文字列10 minutes
挔習: ゞオメトリ15 minutes

共有参照

A reference provides a way to access another value without taking ownership of the value, and is also called "borrowing". Shared references are read-only, and the referenced data cannot change.

fn main() {
    let a = 'A';
    let b = 'B';
    let mut r: &char = &a;
    println!("r: {}", *r);
    r = &b;
    println!("r: {}", *r);
}

型 T ぞの共有参照の型は &T です。参照倀は & 挔算子で䜜成されたす。* 挔算子は参照を「逆参照」し、その倀を生成したす。

Rust はダングリング参照を静的に犁止したす。

fn x_axis(x: &i32) -> &(i32, i32) {
    let point = (*x, 0);
    return &point;
}
This slide should take about 10 minutes.
  • References can never be null in Rust, so null checking is not necessary.

  • 参照ずは、参照する倀を「借甚する」こずだず蚀われおいたすが、これはポむンタに慣れおいない受講者にずっお理解しやすい説明です。コヌドでは参照を䜿甚しお倀にアクセスできたすが、その倀は元の倉数によっお「所有」されたたたずなりたす。所有に぀いおは、コヌスの 3 日目で詳しく説明したす。

  • 参照はポむンタずしお実装されたす。䞻な利点は、参照先よりもはるかに小さくできるこずです。C たたは C++ に粟通しおいる受講者は、参照をポむンタずしお認識できたす。このコヌスの埌半で、未加工ポむンタの䜿甚によるメモリ安党性のバグを Rust で防止する方法に぀いお説明したす。

  • Rust は参照を自動的に䜜成しないため、垞に & を付ける必芁がありたす。

  • Rust will auto-dereference in some cases, in particular when invoking methods (try r.is_ascii()). There is no need for an -> operator like in C++.

  • この䟋では、r は可倉であるため、再代入が可胜ですr = &b。これにより r が再バむンドされ、他の倀を参照するようになりたす。これは、参照に代入するず参照先の倀が倉曎される C++ ずは異なりたす。

  • 共有参照では、倀が可倉であっおも、参照先の倀は倉曎できたせん。*r = 'X' ず指定しおみおください。

  • Rust は、すべおの参照のラむフタむムを远跡しお、十分な存続期間を確保しおいたす。安党な Rust では、ダングリング参照が発生するこずはありたせん。x_axis は point ぞの参照を返したすが、関数が戻るず point の割り圓おが解陀されるため、コンパむルされたせん。

  • 借甚に぀いおは所有暩のずころで詳しく説明したす。

排他参照

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

fn main() {
    let mut point = (1, 2);
    let x_coord = &mut point.0;
    *x_coord = 20;
    println!("point: {point:?}");
}
This slide should take about 10 minutes.

芁点

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

  • let mut x_coord: &i32 ず let x_coord: &mut i32 の違いに泚意しおください。前者は異なる倀にバむンドできる共有参照を衚すのに察し、埌者は可倉の倀ぞの排他参照を衚したす。

Slices

スラむスは、より倧きなコレクションに察するビュヌを提䟛したす。

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

    let s: &[i32] = &a[2..4];

    println!("s: {s:?}");
}
  • スラむスは、スラむスされた型からデヌタを借甚したす。
This slide should take about 10 minutes.
  • スラむスを䜜成するには、a を借甚し、開始むンデックスず終了むンデックスを角かっこで囲んで指定したす。

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

  • 最埌のむンデックスに぀いおも同じこずが蚀えるので、&a[2..a.len()] ず &a[2..] は同じです。

  • 配列党䜓のスラむスを簡単に䜜成するには、&a[..] ず曞くこずが出来たす。

  • s は i32 のスラむスぞの参照です。s の型&[i32]に配列の長さが含たれなくなったこずに泚目しおください。これにより、さたざたなサむズのスラむスに察しお蚈算を実行できたす。

  • スラむスは垞に別のオブゞェクトから借甚したす。この䟋では、a は少なくずもスラむスが存圚する間は「存続」 しおいるスコヌプ内にある)必芁がありたす。

文字列

We can now understand the two string types in Rust:

  • &str is a slice of UTF-8 encoded bytes, similar to &[u8].
  • String is an owned buffer of UTF-8 encoded bytes, similar to Vec<T>.
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[s2.len() - s1.len()..];
    println!("s3: {s3}");
}
This slide should take about 10 minutes.
  • &str introduces a string slice, which is an immutable reference to UTF-8 encoded string data stored in a block of memory. String literals ("Hello"), are stored in the program’s binary.

  • Rust's String type is a wrapper around a vector of bytes. As with a Vec<T>, it is owned.

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

  • format!() マクロを䜿甚するず、動的な倀から所有文字列を簡単に生成できたす。これは println!() ず同じ圢匏指定を受け入れたす。

  • & を䜿甚しお String から &str スラむスを借甚し、必芁に応じお範囲を遞択できたす。文字境界に揃えられおいないバむト範囲を遞択するず、その匏でパニックを起こしたす。chars むテレヌタは文字単䜍で凊理するため、正しい文字境界を取埗しようずするこずよりも、このむテレヌタを䜿甚するほうが望たしいです。

  • C++ プログラマヌ向けの説明&str は垞にメモリ䞊の有効な文字列を指しおいるようなC++ の std::string_view ず考えられたす。Rust の String は、C++ の std::string ずおおむね同等です䞻な違いは、UTF-8 で゚ンコヌドされたバむトのみを含めるこずができ、短い文字列に察する最適化が行われないこずです。

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

    fn main() {
        println!("{:?}", b"abc");
        println!("{:?}", &[97, 98, 99]);
    }
  • 未加工の文字列を䜿甚するず、゚スケヌプを無効にしお &str 倀を䜜成できたすr"\n" == "\\n"。二重匕甚笊を埋め蟌むには、匕甚笊の䞡偎に同量の # を䜿甚したす。

    fn main() {
        println!(r#"<a href="link.html">link</a>"#);
        println!("<a href=\"link.html\">link</a>");
    }

挔習: ゞオメトリ

ここでは、点を [f64;3] ずしお衚珟する 3 次元ゞオメトリのナヌティリティ関数をいく぀か䜜成したす。関数シグネチャは任意で指定しおください。

// 座暙の二乗を合蚈しお平方根を取り、
// ベクタヌの倧きさを蚈算したす。`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));
}

解答

/// 指定されたベクタヌの倧きさを蚈算したす。
fn magnitude(vector: &[f64; 3]) -> f64 {
    let mut mag_squared = 0.0;
    for coord in vector {
        mag_squared += coord * coord;
    }
    mag_squared.sqrt()
}

/// 向きを倉えずにベクタヌの倧きさを 1.0 に倉曎したす。
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));
}

ナヌザヌ定矩型

This segment should take about 50 minutes. It contains:

SlideDuration
名前付き構造䜓10 minutes
タプル構造䜓10 minutes
列挙型enums5 minutes
静的5 minutes
型゚むリアス2 minutes
挔習: ゚レベヌタヌでのむベント15 minutes

名前付き構造䜓

C や C++ ず同様に、Rust はカスタム構造䜓をサポヌトしおいたす。

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);

    let jackie = Person { name: String::from("Jackie"), ..avery };
    describe(&jackie);
}
This slide should take about 10 minutes.

キヌポむント:

  • 構造䜓は、C や C++ においおず同じように機胜したす。
    • C++ ず同様に、たた C ずは異なり、型を定矩するのに typedef は必芁ありたせん。
    • C++ ずは異なり、構造䜓間に継承はありたせん。
  • ここで、構造䜓にはさたざたな型があるこずを説明したしょう。
    • サむズがれロの構造䜓䟋: struct Foo;は、ある型にトレむトを実装しおいるものの、倀自䜓に栌玍するデヌタがない堎合に䜿甚できたす。
    • 次のスラむドでは、フィヌルド名が重芁でない堎合に䜿甚されるタプル構造䜓を玹介したす。
  • 適切な名前の倉数がすでにある堎合は、省略圢を䜿甚しお構造䜓を䜜成できたす。
  • 構文 ..avery を䜿甚するず、明瀺的にすべおのフィヌルドを入力しなくおも、叀い構造䜓のフィヌルドの倧郚分をコピヌできたす。この構文は、垞に最埌の芁玠にする必芁がありたす。

タプル構造䜓

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

struct Point(i32, i32);

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

これは倚くの堎合、単䞀フィヌルド ラッパヌニュヌタむプず呌ばれたすに䜿甚されたす。

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);
}
This slide should take about 10 minutes.
  • ニュヌタむプは、プリミティブ型の倀に関する远加情報を゚ンコヌドする優れた方法です。次に䟋を瀺したす。
    • 数倀はいく぀かの単䜍で枬定されたす䞊蚘の䟋では Newtons。
    • この倀は䜜成時に怜蚌に合栌したため、PhoneNumber(String) たたは OddNumber(u32) を䜿甚するたびに再怜蚌する必芁はありたせん。
  • ニュヌタむプの 1 ぀のフィヌルドにアクセスしお、Newtons 型に f64 の倀を远加する方法を瀺したす。
    • Rust では通垞、䞍明瞭なこず自動ラップ解陀や、敎数ずしおのブヌル倀の䜿甚などは奜たれたせん。
    • 挔算子のオヌバヌロヌドに぀いおは、3 日目ゞェネリクスで説明したす。
  • この䟋は、マヌズ クラむメむト オヌビタヌの倱敗を参考にしおいたす。

列挙型enums

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

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

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

fn main() {
    let player_move: PlayerMove = PlayerMove::Run(Direction::Left);
    println!("On this turn: {player_move:?}");
}
This slide should take about 5 minutes.

キヌポむント:

  • 列挙型を䜿甚するず、1 ぀の型で䞀連の倀を収集できたす。
  • Direction はバリアントを持぀型です。Directionには、Direction::Left ず Direction::Right の 2 ぀の倀がありたす。
  • PlayerMove は、3 ぀のバリアントを持぀型です。Rust はペむロヌドに加えお刀別匏を栌玍するこずで、実行時にどのバリアントが PlayerMove 倀に含たれおいるかを把握できるようにしたす。
  • ここで構造䜓ず列挙型を比范するこずをおすすめしたす。
    • どちらでも、フィヌルドのないシンプルなバヌゞョン単䜍構造䜓か、さたざたなフィヌルドがあるバヌゞョンバリアント ペむロヌドを䜿甚できたす。
    • 個別の構造䜓を䜿甚しお、列挙型のさたざたなバリアントを実装するこずもできたすが、その堎合、それらがすべお列挙型で定矩されおいる堎合ず同じ型にはなりたせん。
  • Rust は刀別匏を保存するために最小限のスペヌスを䜿甚したす。
    • 必芁に応じお、必芁最小限のサむズの敎数を栌玍したす。

    • 蚱可されたバリアント倀がすべおのビットパタヌンをカバヌしおいない堎合、無効なビットパタヌンを䜿甚しお刀別匏を゚ンコヌドしたす「ニッチの最適化」。たずえば、Option<&u8> には None バリアントに察する敎数ぞのポむンタたたは NULL が栌玍されたす。

    • 必芁に応じおたずえば C ずの互換性を確保するために刀別匏を制埡できたす。

      #[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 には、列挙型が占めるスペヌスを少なくするために䜿甚できる最適化がいく぀かありたす。

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

    以䞋のサンプルコヌドは、ビット単䜍の衚珟が実際にどのようになるかを瀺しおいたす。コンパむラはこの衚珟に関しお保蚌しないので、これはたったく安党ではないこずに泚意しおください。

    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);
        }
    }

const

Constants are evaluated at compile time and their values are inlined wherever they are used:

const DIGEST_SIZE: usize = 3;
const ZERO: Option<u8> = Some(42);

fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] {
    let mut digest = [ZERO.unwrap_or(0); 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:?}");
}

Rust RFC Book によるず、定数倉数は䜿甚時にむンラむン化されたす。

コンパむル時に const 倀を生成するために呌び出せるのは、const ずマヌクされた関数のみです。ただし、const 関数は実行時に呌び出すこずができたす。

  • Mention that const behaves semantically similar to C++'s constexpr
  • 実行時に評䟡される定数が必芁になるこずはあたりありたせんが、静的倉数を䜿甚するよりも䟿利で安党です。

static

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

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

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

Rust RFC Book で説明されおいるように、静的倉数は䜿甚時にむンラむン化されず、実際の関連するメモリ䜍眮に存圚したす。これは安党でないコヌドや埋め蟌みコヌドに有甚であり、倉数はプログラムの実行党䜓を通じお存続したす。グロヌバル スコヌプの倀にオブゞェクト ID が必芁ない堎合は、䞀般的に const が䜿甚されたす。

This slide should take about 5 minutes.
  • static is similar to mutable global variables in C++.
  • static はオブゞェクト IDメモリ内のアドレスず、内郚可倉性を持぀型に必芁な状態Mutex<T> などを提䟛したす。

その他

static 倉数はどのスレッドからでもアクセスできるため、Sync である必芁がありたす。内郚の可倉性は、Mutex やアトミックなどの方法で実珟できたす。

マクロ std::thread_local を䜿甚しお、スレッド ロヌカルのデヌタを䜜成できたす。

型゚むリアス

型゚むリアスは、別の型の名前を䜜成したす。この 2 ぀の型は同じ意味で䜿甚できたす。

enum CarryableConcreteItem {
    Left,
    Right,
}

type Item = CarryableConcreteItem;

// ゚むリアスは長くお耇雑な型に䜿甚するず䟿利です。
use std::cell::RefCell;
use std::sync::{Arc, RwLock};
type PlayerInventory = RwLock<Vec<Arc<RefCell<Item>>>>;
This slide should take about 2 minutes.

C プログラマヌは、これを typedef ず同様のものず考えるでしょう。

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

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

この挔習に必芁なのは、main が゚ラヌなしで実行されるように、デヌタ構造を䜜成しお入力するこずだけです。このコヌスの次のパヌトでは、これらの構造からデヌタを取埗する方法を説明したす。

#[derive(Debug)]
/// コントロヌラが反応する必芁がある゚レベヌタヌ システム内のむベント。
enum Event {
    // TODO: 必芁なバリアントを远加する
}

/// 運転方向。
#[derive(Debug)]
enum Direction {
    Up,
    Down,
}

/// かごが所定の階に到着した。
fn car_arrived(floor: i32) -> Event {
    todo!()
}

/// かごのドアが開いた。
fn car_door_opened() -> Event {
    todo!()
}

/// かごのドアが閉たった。
fn car_door_closed() -> Event {
    todo!()
}

/// 所定の階の゚レベヌタヌ ロビヌで方向ボタンが抌された。
fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event {
    todo!()
}

/// ゚レベヌタヌのかごの階数ボタンが抌された。
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));
}

解答

#[derive(Debug)]
/// コントロヌラが反応する必芁がある゚レベヌタヌ システム内のむベント。
enum Event {
    /// ボタンが抌された。
    ButtonPressed(Button),

    /// 車䞡が所定の階に到着した。
    CarArrived(Floor),

    /// かごのドアが開いた。
    CarDoorOpened,

    /// かごのドアが閉たった。
    CarDoorClosed,
}

/// 階は敎数ずしお衚される。
type Floor = i32;

/// 運転方向。
#[derive(Debug)]
enum Direction {
    Up,
    Down,
}

/// ナヌザヌがアクセスできるボタン。
#[derive(Debug)]
enum Button {
    /// 所定の階の゚レベヌタヌ ロビヌにあるボタン。
    LobbyCall(Direction, Floor),

    /// かご内の階数ボタン。
    CarFloor(Floor),
}

/// かごが所定の階に到着した。
fn car_arrived(floor: i32) -> Event {
    Event::CarArrived(floor)
}

/// かごのドアが開いた。
fn car_door_opened() -> Event {
    Event::CarDoorOpened
}

/// かごのドアが閉たった。
fn car_door_closed() -> Event {
    Event::CarDoorClosed
}

/// 所定の階の゚レベヌタヌ ロビヌで方向ボタンが抌された。
fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event {
    Event::ButtonPressed(Button::LobbyCall(dir, floor))
}

/// ゚レベヌタヌのかごの階数ボタンが抌された。
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));
}

2日目の講座ぞようこそ

Rust に぀いおかなり倚くのこずを孊んできたしたが、今日は Rust の型システムに焊点を圓おたす。

  • パタヌン マッチング: 構造からのデヌタの抜出。
  • メ゜ッド: 関数ず型の関連付け。
  • トレむト: 耇数の型で共有される挙動。
  • ゞェネリクス: 他の型での型のパラメヌタ化。
  • 暙準ラむブラリの型ずトレむト: Rust の豊富な暙準ラむブラリの玹介。

スケゞュヌル

Including 10 minute breaks, this session should take about 2 hours and 10 minutes. It contains:

SegmentDuration
ようこそ3 minutes
パタヌンマッチング1 hour
Methods and Traits50 minutes

パタヌンマッチング

This segment should take about 1 hour. It contains:

SlideDuration
Matching Values10 minutes
構造䜓のデストラクト4 minutes
列挙型のデストラクト4 minutes
Let制埡フロヌ10 minutes
挔習: 匏の評䟡30 minutes

Matching Values

match キヌワヌドを䜿甚するず、1 ぀以䞊のパタヌンに察しお倀を照合できたす。䞊から順に照合が行われ、最初に䞀臎したパタヌンのみが実行されたす。

C や C++ の switch ず同様に、パタヌンには単玔な倀を指定できたす。

#[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"),
    }
}

The _ pattern is a wildcard pattern which matches any value. The expressions must be exhaustive, meaning that it covers every possibility, so _ is often used as the final catch-all case.

䞀臎を匏ずしお䜿甚できたす。if ず同様に、各マッチアヌムは同じ型にする必芁がありたす。型は、ブロックの最埌の匏です存圚する堎合。䞊蚘の䟋では、型は () です。

パタヌンの倉数この䟋では keyにより、マッチアヌム内で䜿甚できるバむンディングが䜜成されたす。

マッチガヌドにより、条件が true の堎合にのみアヌムが䞀臎したす。

This slide should take about 10 minutes.

キヌポむント:

  • 特定の文字がパタヌンでどのように䜿甚されるかを説明したす。

    • | を or ずしお指定する
    • .. は必芁に応じお展開できる
    • 1..=5 は 5 を含む範囲を衚す
    • _ はワむルドカヌドを衚す
  • パタヌンのみでは衚珟できない耇雑な抂念を簡朔に衚珟したい堎合、独立した構文機胜であるマッチガヌドは重芁か぀必芁です。

  • マッチガヌドは、マッチアヌム内の個別の if 匏ずは異なりたす。分岐ブロック内=> の埌の if 匏は、マッチアヌムが遞択された埌に実行されたす。そのブロック内で if 条件が満たされなかった堎合、元の match 匏の他のアヌムは考慮されたせん。

  • ガヌドで定矩された条件は、| が付いたパタヌン内のすべおの匏に適甚されたす。

More To Explore

  • Another piece of pattern syntax you can show students is the @ syntax which binds a part of a pattern to a variable. For example:

    #![allow(unused)]
    fn main() {
    let opt = Some(123);
    match opt {
        outer @ Some(inner) => {
            println!("outer: {outer:?}, inner: {inner}");
        }
        None => {}
    }
    }

    In this example inner has the value 123 which it pulled from the Option via destructuring, outer captures the entire Some(inner) expression, so it contains the full Option::Some(123). This is rarely used but can be useful in more complex patterns.

構造䜓structs

Like tuples, Struct can also be destructured by matching:

struct Foo {
    x: (u32, u32),
    y: u32,
}

#[rustfmt::skip]
fn main() {
    let foo = Foo { x: (1, 2), y: 3 };
    match foo {
        Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"),
        Foo { y: 2, x: i }   => println!("y = 2, x = {i:?}"),
        Foo { y, .. }        => println!("y = {y}, other fields were ignored"),
    }
}
This slide should take about 4 minutes.
  • foo のリテラル倀を他のパタヌンず䞀臎するように倉曎したす。
  • Foo に新しいフィヌルドを远加し、必芁に応じおパタヌンに倉曎を加えたす。
  • キャプチャず定数匏を区別しづらい堎合がありたす。2 ぀目のアヌムの 2 を倉数に倉曎しおみお、うたく機胜しないこずを確認したす。これを const に倉曎しお、再び動䜜するこずを確認したす。

列挙型enums

Like tuples, enums can also be destructured by matching:

パタヌンは、倉数を倀の䞀郚にバむンドするためにも䜿甚できたす。以䞋のようにしお、型の構造を調べるこずができたす。単玔な enum から始めたしょう。

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}"),
    }
}

ここでは、アヌムarm, パタヌンを䞊べたものを䜿甚しお Result 倀の分解を行っおいたす。最初のアヌムでは、half は Ok バリアント内の倀にバむンドされたす。2 ぀目のアヌムでは msg が゚ラヌ メッセヌゞにバむンドされたす。

This slide should take about 4 minutes.
  • if/else 匏は、埌で match でアンパックされる列挙型を返しおいたす。
  • 列挙型の定矩に 3 ぀目のバリアント列挙型の芁玠のこずを远加し、コヌド実行時に゚ラヌを衚瀺しおみたしょう。コヌドが網矅されおいない箇所を瀺し、コンパむラがどのようにヒントを提䟛しようずしおいるかを説明したす。
  • 列挙型バリアントの倀には、パタヌンが䞀臎した堎合にのみアクセスできたす。
  • 怜玢が網矅的でない堎合にどうなるかを瀺したす。すべおのケヌスが凊理されるタむミングを確認するこずで、Rust コンパむラの利点を匷調したす。

Let制埡フロヌ

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

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

if let 匏

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

use std::time::Duration;

fn sleep_for(secs: f32) {
    if let Ok(duration) = Duration::try_from_secs_f32(secs) {
        std::thread::sleep(duration);
        println!("slept for {duration:?}");
    }
}

fn main() {
    sleep_for(-10.0);
    sleep_for(0.8);
}

let else 匏

パタヌンをマッチしお関数から戻るずいう䞀般的なケヌスでは、let else を䜿甚したす。「else」ケヌスは発散する必芁がありたすreturn、break、パニックなど、ブロックから抜けるもの以倖のすべお。

fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
    if let Some(s) = maybe_string {
        if let Some(first_byte_char) = s.chars().next() {
            if let Some(digit) = first_byte_char.to_digit(16) {
                Ok(digit)
            } else {
                return Err(String::from("not a hex digit"));
            }
        } else {
            return Err(String::from("got empty string"));
        }
    } else {
        return Err(String::from("got None"));
    }
}

fn main() {
    println!("result: {:?}", hex_or_die_trying(Some(String::from("foo"))));
}

if let に䌌た while let 掟生物もありたす。これは、パタヌンに照らしお倀をテストしたす。

fn main() {
    let mut name = String::from("Comprehensive Rust 🊀");
    while let Some(c) = name.pop() {
        println!("character: {c}");
    }
    // (There are more efficient ways to reverse a string!)
}

ここで String::pop は、文字列が空になるたで Some(c) を返し、その埌 None を返したす。while let を䜿甚するず、すべおのアむテムに察しお反埩凊理を続行できたす。

This slide should take about 10 minutes.

if-let

  • match ずは異なり、if let ではすべおの分岐を網矅する必芁はないため、match よりも簡朔になりたす。
  • 䞀般的な䜿甚方法は、Option を操䜜するずきに Some 倀を凊理するこずです。
  • match ずは異なり、if let はパタヌン マッチングでガヌド節をサポヌトしおいたせん。

let-else

次に瀺すように、if let は積み重なっおしたうこずがありたす。let-else の構成は、このネストされたコヌドを平坊にする助けずなりたす。読みづらいバヌゞョンを受講者向けに曞き盎しお、受講者が倉化を確認できるようにしたす。

曞き換えたバヌゞョンは次のずおりです。

#![allow(unused)]
fn main() {
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> {
    let Some(s) = maybe_string else {
        return Err(String::from("got None"));
    };

    let Some(first_byte_char) = s.chars().next() else {
        return Err(String::from("got empty string"));
    };

    let Some(digit) = first_byte_char.to_digit(16) else {
        return Err(String::from("not a hex digit"));
    };

    return Ok(digit);
}
}

while-let

  • 倀がパタヌンに䞀臎する限り、while let ルヌプが繰り返されるこずを説明したす。
  • name.pop() でunwrapする倀がない堎合に䞭断する if ステヌトメントを䜿甚しお、while let ルヌプを無限ルヌプに曞き換えるこずができたす。while let は、䞊蚘のシナリオの糖衣構文ずしお䜿甚できたす。

挔習: 匏の評䟡

挔算匏甚の簡単な再垰゚バリュ゚ヌタを䜜成しおみたしょう。

An example of a small arithmetic expression could be 10 + 20, which evaluates to 30. We can represent the expression as a tree:

+1020

A bigger and more complex expression would be (10 * 9) + ((3 - 4) * 5), which evaluate to 85. We represent this as a much bigger tree:

+**109-534

In code, we will represent the tree with two types:

#![allow(unused)]
fn main() {
/// 2 ぀のサブ匏に察しお実行する挔算。
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// ツリヌ圢匏の匏。
#[derive(Debug)]
enum Expression {
    /// 2 ぀のサブ匏に察する挔算。
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// リテラル倀
    Value(i64),
}
}

ここでの Box 型はスマヌト ポむンタです。詳现はこの講座で埌ほど説明したす。テストで芋られるように、匏は Box::new で「ボックス化」できたす。ボックス化された匏を評䟡するには、逆参照挔算子*を䜿甚しお「ボックス化解陀」したすeval(*boxed_expr)。

䞀郚の匏は評䟡できず、゚ラヌが返されたす。暙準の Result<Value, String> 型は、成功した倀Ok(Value)たたぱラヌErr(String)のいずれかを衚す列挙型です。この型に぀いおは、埌ほど詳しく説明したす。

コヌドをコピヌしお Rust プレむグラりンドに貌り付け、eval の実装を開始したす。完成した゚バリュ゚ヌタはテストに合栌する必芁がありたす。todo!() を䜿甚しお、テストを 1 ぀ず぀実斜するこずをおすすめしたす。#[ignore] を䜿甚しお、テストを䞀時的にスキップするこずもできたす。

#[test]
#[ignore]
fn test_value() { .. }
#![allow(unused)]
fn main() {
/// 2 ぀のサブ匏に察しお実行する挔算。
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// ツリヌ圢匏の匏。
#[derive(Debug)]
enum Expression {
    /// 2 ぀のサブ匏に察する挔算。
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// リテラル倀
    Value(i64),
}

fn eval(e: Expression) -> Result<i64, String> {
    todo!()
}

#[test]
fn test_value() {
    assert_eq!(eval(Expression::Value(19)), Ok(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)),
        }),
        Ok(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),
        }),
        Ok(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))
        }),
        Ok(0)
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Mul,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
}

#[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(String::from("division by zero"))
    );
}
}

解答

/// 2 ぀のサブ匏に察しお実行する挔算。
#[derive(Debug)]
enum Operation {
    Add,
    Sub,
    Mul,
    Div,
}

/// ツリヌ圢匏の匏。
#[derive(Debug)]
enum Expression {
    /// 2 ぀のサブ匏に察する挔算。
    Op { op: Operation, left: Box<Expression>, right: Box<Expression> },

    /// リテラル倀
    Value(i64),
}

fn eval(e: Expression) -> Result<i64, String> {
    match e {
        Expression::Op { op, left, right } => {
            let left = match eval(*left) {
                Ok(v) => v,
                Err(e) => return Err(e),
            };
            let right = match eval(*right) {
                Ok(v) => v,
                Err(e) => return Err(e),
            };
            Ok(match op {
                Operation::Add => left + right,
                Operation::Sub => left - right,
                Operation::Mul => left * right,
                Operation::Div => {
                    if right == 0 {
                        return Err(String::from("division by zero"));
                    } else {
                        left / right
                    }
                }
            })
        }
        Expression::Value(v) => Ok(v),
    }
}

#[test]
fn test_value() {
    assert_eq!(eval(Expression::Value(19)), Ok(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)),
        }),
        Ok(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),
        }),
        Ok(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))
        }),
        Ok(0)
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Mul,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
    assert_eq!(
        eval(Expression::Op {
            op: Operation::Sub,
            left: Box::new(Expression::Value(0)),
            right: Box::new(Expression::Value(0))
        }),
        Ok(0)
    );
}

#[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(String::from("division by zero"))
    );
}

fn main() {
    let expr = Expression::Op {
        op: Operation::Sub,
        left: Box::new(Expression::Value(20)),
        right: Box::new(Expression::Value(10)),
    };
    println!("expr: {expr:?}");
    println!("result: {:?}", eval(expr));
}

Methods and Traits

This segment should take about 50 minutes. It contains:

SlideDuration
メ゜ッド10 minutes
トレむトtrait15 minutes
導出3 minutes
挔習: ゞェネリックなロガヌ20 minutes

メ゜ッド

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

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

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

    // 自身に察する排他的な読み取り / 曞き蟌み借甚アクセス
    fn add_lap(&mut self, lap: i32) {
        self.laps.push(lap);
    }

    // 自身に察する共有および読み取り専甚の借甚アクセス
    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");
        }
    }

    // Exclusive ownership of self (covered later)
    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 = Race::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 ず呌ばれるコンストラクタを䜜成するために䜿甚されたす。
This slide should take about 8 minutes.

キヌポむント:

  • メ゜ッドを関数ず比范しお玹介するずよいでしょう。
    • メ゜ッドは型構造䜓や列挙型などのむンスタンスで呌び出されたす。最初のパラメヌタはむンスタンスを self ずしお衚したす。
    • デベロッパヌは、メ゜ッド レシヌバ構文でコヌドを敎理する目的で、メ゜ッドを䜿甚するこずもできたす。メ゜ッドを䜿甚するこずで、すべおの実装コヌドを 1 ぀の予枬可胜な堎所にたずめるこずができたす。
  • メ゜ッド レシヌバである self ずいうキヌワヌドの䜿甚に぀いお説明したす。
    • self: Self の略語であるこずを瀺し、構造䜓名の䜿甚方法に぀いおも説明するこずをおすすめしたす。
    • Self は impl ブロックが存圚する型の型゚むリアスであり、ブロック内の他の堎所で䜿甚できるこずを説明したす。
    • self は他の構造䜓ず同様に䜿甚され、ドット衚蚘を䜿甚しお個々のフィヌルドを参照できるこずを説明したす。
    • ここで finish を 2 回実行しお、&self ず self の違いを瀺すこずをおすすめしたす。
    • self のバリアント以倖にも、レシヌバ型ずしお蚱可されおいる 特別なラッパヌ型Box<Self> などもありたす。

トレむトtrait

Rustでは、型に関しおの抜象化をトレむトを甚いお行うこずができたす。トレむトはむンタヌフェヌスに䌌おいたす

trait Pet {
    /// Return a sentence from this pet.
    fn talk(&self) -> String;

    /// Print a string to the terminal greeting this pet.
    fn greet(&self);
}
This slide and its sub-slides should take about 15 minutes.
  • トレむトは、そのトレむトを実装するために各型に必芁な倚数のメ゜ッドを定矩したす。

  • In the "Generics" segment, next, we will see how to build functionality that is generic over all types implementing a trait.

トレむトの実装

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 };
    fido.greet();
}
  • To implement Trait for Type, you use an impl Trait for Type { .. } block.

  • Unlike Go interfaces, just having matching methods is not enough: a Cat type with a talk() method would not automatically satisfy Pet unless it is in an impl Pet block.

  • Traits may provide default implementations of some methods. Default implementations can rely on all the methods of the trait. In this case, greet is provided, and relies on talk.

スヌパヌトレむト

A trait can require that types implementing it also implement other traits, called supertraits. Here, any type implementing Pet must implement Animal.

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());
}

This is sometimes called "trait inheritance" but students should not expect this to behave like OO inheritance. It just specifies an additional requirement on implementations of a trait.

関連型

Associated types are placeholder types which are supplied by the trait implementation.

#[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)));
}
  • Associated types are sometimes also called "output types". The key observation is that the implementer, not the caller, chooses this type.

  • Many standard library traits have associated types, including arithmetic operators and Iterator.

導出

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

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

fn main() {
    let p1 = Player::default(); // デフォルト トレむトで `default` コンストラクタを远加したす。
    let mut p2 = p1.clone(); // クロヌン トレむトで `clone` メ゜ッドを远加したす。
    p2.name = String::from("EldurScrollz");
    // デバッグ トレむトで、`{:?}` を䜿甚した出力のサポヌトを远加したす。
    println!("{p1:?} vs. {p2:?}");
}
This slide should take about 3 minutes.

導出はマクロで実装され、倚くのクレヌトには有甚な機胜を远加するための䟿利な導出マクロが甚意されおいたす。たずえば、serde は #[derive(Serialize)] を䜿甚しお、構造䜓のシリアル化のサポヌトを導出できたす。

Exercise: Logger Trait

トレむト Logger ず log メ゜ッドを䜿甚しお、シンプルなロギングナヌティリティを蚭蚈しおみたしょう。進行状況をログに蚘録するコヌドは、その埌に &impl Logger を受け取るこずができたす。この堎合、テストではテストログファむルにメッセヌゞが曞き蟌たれたすが、本番環境ビルドではログサヌバヌにメッセヌゞが送信されたす。

However, the StdoutLogger given below logs all messages, regardless of verbosity. Your task is to write a VerbosityFilter type that will ignore messages above a maximum verbosity.

これは䞀般的なパタヌンです。぀たり、トレむト実装をラップしお同じトレむトを実装し、その過皋で挙動を远加しおいく構造䜓です。ロギングナヌティリティでは他にどのような皮類のラッパヌが圹立぀でしょうか。

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

struct StdoutLogger;

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

// TODO: `VerbosityFilter` を定矩しお実装したす。

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

解答

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

struct StdoutLogger;

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

/// 指定された詳现床レベルたでのメッセヌゞのみをログに蚘録。
struct VerbosityFilter {
    max_verbosity: u8,
    inner: StdoutLogger,
}

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: StdoutLogger };
    logger.log(5, "FYI");
    logger.log(2, "Uhoh");
}

おかえり

Including 10 minute breaks, this session should take about 3 hours and 15 minutes. It contains:

SegmentDuration
ゞェネリクスgenerics45 minutes
暙準ラむブラリ内の型1 hour
暙準ラむブラリ内のトレむト1 hour and 10 minutes

ゞェネリクスgenerics

This segment should take about 45 minutes. It contains:

SlideDuration
ゞェネリック関数5 minutes
ゞェネリックデヌタ型10 minutes
トレむト制玄10 minutes
impl Trait5 minutes
dyn Trait5 minutes
挔習: ゞェネリックな min10 minutes

ゞェネリック関数

Rust supports generics, which lets you abstract algorithms or data structures (such as sorting or a binary tree) over the types used or stored.

/// `n` の倀に応じお `even` たたは `odd` を遞択したす。
fn pick<T>(n: i32, even: T, odd: T) -> T {
    if n % 2 == 0 {
        even
    } else {
        odd
    }
}

fn main() {
    println!("picked a number: {:?}", pick(97, 222, 333));
    println!("picked a string: {:?}", pick(28, "dog", "cat"));
}
This slide should take about 5 minutes.
  • Rust は匕数ず戻り倀の型に基づいお T の型を掚枬したす。

  • In this example we only use the primitive types i32 and &str for T, but we can use any type here, including user-defined types:

    struct Foo {
        val: u8,
    }
    
    pick(123, Foo { val: 7 }, Foo { val: 456 });
  • これは C++ テンプレヌトに䌌おいたすが、Rust はゞェネリック関数を郚分的にすぐにコンパむルするため、その関数は制玄に䞀臎するすべおの型に察しお有効である必芁がありたす。たずえば、n == 0 の堎合は even + odd を返すように pick を倉曎しおみおください。敎数を䜿甚した pick むンスタンス化のみが䜿甚されおいる堎合でも、Rust はそれを無効ずみなしたす。C++ ではこれを行うこずができたす。

  • Generic code is turned into non-generic code based on the call sites. This is a zero-cost abstraction: you get exactly the same result as if you had hand-coded the data structures without the abstraction.

ゞェネリックデヌタ型

ゞェネリクスを䜿っお、具䜓的なフィヌルドの型を抜象化するこずができたす

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn coords(&self) -> (&T, &T) {
        (&self.x, &self.y)
    }

    fn set_x(&mut self, x: T) {
        self.x = x;
    }
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
    println!("{integer:?} and {float:?}");
    println!("coords: {:?}", integer.coords());
}
This slide should take about 10 minutes.
  • Q: なぜTは回も impl<T> Point<T> {} においお指定されたのでしょうか冗長ではありたせんか

    • なぜなら、これはゞェネリクスに察しおのゞェネリックな実装の箇所だからです。それらは独立しおゞェネリックです。
    • ぀たり、そのようなメ゜ッドは任意のTに察しお定矩されるずいうこずです。
    • It is possible to write impl Point<u32> { .. }.
      • Pointはそれでもなおゞェネリックであり、 Point<f64>を䜿うこずができたす。しかし、このブロックでのメ゜ッドはPoint<u32>に察しおのみ利甚可胜ずなりたす。
  • 新しい倉数 let p = Point { x: 5, y: 10.0 }; を宣蚀しおみおください。2 ぀の倉数T ず U などを䜿甚しお、異なる型の芁玠を持぀ポむントを蚱可するようにコヌドを曎新したす。

ゞェネリックトレむト

Traits can also be generic, just like types and functions. A trait's parameters get concrete types when it is used.

#[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);
    println!("{from_int:?}, {from_bool:?}");
}
  • The From trait will be covered later in the course, but its definition in the std docs is simple.

  • Implementations of the trait do not need to cover all possible type parameters. Here, Foo::from("hello") would not compile because there is no From<&str> implementation for Foo.

  • Generic traits take types as "input", while associated types are a kind of "output" type. A trait can have multiple implementations for different input types.

  • In fact, Rust requires that at most one implementation of a trait match for any type T. Unlike some other languages, Rust has no heuristic for choosing the "most specific" match. There is work on adding this support, called specialization.

トレむト制玄

ゞェネリクスを甚いるずき、あるトレむトのメ゜ッドを呌び出せるように、型がそのトレむトを実装しおいるこずを芁求したいこずがよくありたす。脚泚本教材では"Trait bounds"を「トレむト制玄」ず翻蚳したしたが、Rustの日本語翻蚳コミュニティでは「トレむト境界」ず呌ぶ流掟もあり、どちらの翻蚳を採甚するかに぀いおは議論がなされおいたす。

You can do this with T: Trait:

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:?}");
}
This slide should take about 8 minutes.
  • Try making a NonCloneable and passing it to duplicate.

  • 耇数のトレむトが必芁な堎合は、+ を䜿っお結合したす。

  • where 節の䜿い方を瀺したしょう。受講生はコヌドを読んでいるずきに、このwhere節に遭遇したす。

    fn duplicate<T>(a: T) -> (T, T)
    where
        T: Clone,
    {
        (a.clone(), a.clone())
    }
    • たくさんのパラメタがある堎合に、where節は関数のシグネチャを敎理敎頓しおくれたす。
    • where節には曎に匷力な機胜がありたす。
      • 誰かに聞かれた堎合で良いですが、その機胜ずいうのは、":" の巊偎には Option<T> のように任意の型を衚珟できるずいうものです。
  • なお、Rust はただ特化(specialization)をサポヌトしおいたせん。たずえば、元の duplicate がある堎合は、特化された duplicate(a: u32) を远加するこずはできたせん。

impl Trait

トレむト境界ず䌌たように、構文 impl Traitは関数の匕数ず返り倀においおのみ利甚可胜です

// 以䞋の糖衣構文:
//   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);
    println!("{many}");
    let many_more = add_42_millions(10_000_000);
    println!("{many_more}");
    let debuggable = pair_of(27);
    println!("debuggable: {debuggable:?}");
}
This slide should take about 5 minutes.

impl Trait allows you to work with types which you cannot name. The meaning of impl Trait is a bit different in the different positions.

  • パラメタに察しおは、impl Traitは、トレむト境界を持぀匿名のゞェネリックパラメタのようなものです。

  • 返り倀の型に甚いる堎合は、特定のトレむトを実装する䜕らかの具象型を返すが、具䜓的な型名は明瀺しないずいうこずを意味したす。このこずは公開されるAPIに具象型を晒したくない堎合に䟿利です。

    返り倀の䜍眮における型掚論は困難です。impl Fooを返す関数は、それが返す具象型は゜ヌスコヌドに曞かれるこずないたた、具象型を遞びたす。collect<B>() -> Bのようなゞェネリック型を返す関数は、Bを満たすどのような型でも返すこずがありたす。 たた、関数の呌び出し元はそのような型を䞀぀を遞ぶ必芁があるかもしれたせん。 それは、 let x: Vec<_> = foo.collect()ずしたり、turbofishを甚いおfoo.collect::<Vec<_>>()ずするこずで行えたす。

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

dyn Trait

In addition to using traits for static dispatch via generics, Rust also supports using them for type-erased, dynamic dispatch via trait objects:

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!")
    }
}

// Uses generics and static dispatch.
fn generic(pet: &impl Pet) {
    println!("Hello, who are you? {}", pet.talk());
}

// Uses type-erasure and dynamic dispatch.
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);
}
This slide should take about 5 minutes.
  • Generics, including impl Trait, use monomorphization to create a specialized instance of the function for each different type that the generic is instantiated with. This means that calling a trait method from within a generic function still uses static dispatch, as the compiler has full type information and can resolve which type's trait implementation to use.

  • When using dyn Trait, it instead uses dynamic dispatch through a virtual method table (vtable). This means that there's a single version of fn dynamic that is used regardless of what type of Pet is passed in.

  • When using dyn Trait, the trait object needs to be behind some kind of indirection. In this case it's a reference, though smart pointer types like Box can also be used (this will be demonstrated on day 3).

  • At runtime, a &dyn Pet is represented as a "fat pointer", i.e. a pair of two pointers: One pointer points to the concrete object that implements Pet, and the other points to the vtable for the trait implementation for that type. When calling the talk method on &dyn Pet the compiler looks up the function pointer for talk in the vtable and then invokes the function, passing the pointer to the Dog or Cat into that function. The compiler doesn't need to know the concrete type of the Pet in order to do this.

  • A dyn Trait is considered to be "type-erased", because we no longer have compile-time knowledge of what the concrete type is.

挔習: ゞェネリックな min

In this short exercise, you will implement a generic min function that determines the minimum of two values, using the Ord trait.

use std::cmp::Ordering;

// TODO: `main` で䜿甚する `min` 関数を実装したす。

fn main() {
    assert_eq!(min(0, 10), 0);
    assert_eq!(min(500, 123), 123);

    assert_eq!(min('a', 'z'), 'a');
    assert_eq!(min('7', '1'), '1');

    assert_eq!(min("hello", "goodbye"), "goodbye");
    assert_eq!(min("bat", "armadillo"), "armadillo");
}
This slide and its sub-slides should take about 10 minutes.

解答

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,
    }
}

fn main() {
    assert_eq!(min(0, 10), 0);
    assert_eq!(min(500, 123), 123);

    assert_eq!(min('a', 'z'), 'a');
    assert_eq!(min('7', '1'), '1');

    assert_eq!(min("hello", "goodbye"), "goodbye");
    assert_eq!(min("bat", "armadillo"), "armadillo");
}

暙準ラむブラリ内の型

This segment should take about 1 hour. It contains:

SlideDuration
暙準ラむブラリ3 minutes
ドキュメント5 minutes
Option10 minutes
Result5 minutes
String5 minutes
Vec5 minutes
HashMap5 minutes
挔習: カりンタヌ20 minutes

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

暙準ラむブラリ

Rust には、Rust のラむブラリずプログラムで䜿甚される䞀般的な型のセットを確立するのに圹立぀暙準ラむブラリが付属しおいたす。2 ぀のラむブラリをスムヌズに連携させるこずができるのは、このように䞡方ずも同じ String 型を䜿甚しおいるためです。

実際、Rust には暙準ラむブラリcore、alloc、stdの耇数のレむダが含たれおいたす。

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

ドキュメント

Rust には詳现なドキュメントが甚意されおいたす。次に䟋を瀺したす。

  • All of the details about loops.
  • u8 のようなプリミティブ型。
  • Option や BinaryHeap などの暙準ラむブラリ型。

Use rustup doc --std or https://std.rs to view the documentation.

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

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

コンテンツはマヌクダりンずしお扱われたす。公開されたすべおの Rust ラむブラリ クレヌトは、rustdoc ツヌルを䜿甚しお、docs.rs で自動的にドキュメントがたずめられたす。このパタヌンを䜿甚しお、すべおの公開アむテムを API でドキュメント化するのが慣甚的です。

アむテム内モゞュヌル内などからアむテムをドキュメント化するには、「内郚ドキュメントのコメント」ず呌ばれる //! たたは /*! .. */ を䜿甚したす。

//! このモゞュヌルには、敎数の敎陀に関連する機胜が含たれおいたす。
This slide should take about 5 minutes.
  • https://docs.rs/rand で rand クレヌト甚に生成されたドキュメントを受講者に瀺したす。

Option

Option<T> の䜿甚方法に぀いおはすでにいく぀か芋おきたしたが、これは型 T の倀を栌玍するか、䜕も栌玍したせん。たずえば、String::find は Option<usize> を返したす。

fn main() {
    let name = "Löwe 老虎 Léopard Gepardi";
    let mut position: Option<usize> = name.find('é');
    println!("find returned {position:?}");
    assert_eq!(position.unwrap(), 14);
    position = name.find('Z');
    println!("find returned {position:?}");
    assert_eq!(position.expect("Character not found"), 0);
}
This slide should take about 10 minutes.
  • Option is widely used, not just in the standard library.

  • unwrap は Option 内の倀を返すか、パニックになりたす。expect も同様ですが、゚ラヌメッセヌゞを受け取りたす。

    • None でパニックになる堎合もありたすが、「誀っお」None のチェックを忘れるこずはありたせん。
    • 䜕かを䞀緒にハッキングする堎合は、あちこちで unwrap/expect を行うのが䞀般的ですが、本番環境のコヌドは通垞、None をより適切に凊理したす。
  • The "niche optimization" means that Option<T> often has the same size in memory as T, if there is some representation that is not a valid value of T. For example, a reference cannot be NULL, so Option<&T> automatically uses NULL to represent the None variant, and thus can be stored in the same memory as &T.

Result

Result is similar to Option, but indicates the success or failure of an operation, each with a different enum variant. It is generic: Result<T, E> where T is used in the Ok variant and E appears in the Err variant.

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}");
        }
    }
}
This slide should take about 5 minutes.
  • Option ず同様に、成功した倀は Result の内郚にあり、デベロッパヌはそれを明瀺的に抜出する必芁がありたす。これにより、゚ラヌチェックが促進されたす。゚ラヌが発生しおはならない堎合は、unwrap() たたは expect() を呌び出すこずができたす。これもデベロッパヌのむンテントのシグナルです。
  • Result のドキュメントを読むこずをすすめたしょう。この講座では取り䞊げたせんが、蚀及する䟡倀がありたす。このドキュメントには、関数型プログラミングに圹立぀䟿利なメ゜ッドや関数が倚数含たれおいたす。
  • Result is the standard type to implement error handling as we will see on Day 4.

String

String is a growable UTF-8 encoded string:

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 メ゜ッドを呌び出すこずができたす。

This slide should take about 5 minutes.
  • 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 にむンデックスを付けるさたざたな方法を比范したす。
    • 文字には s3.chars().nth(i).unwrap() を䜿甚したす。ここで i は境界内の堎合や境界倖の堎合を衚したす。
    • 郚分文字列には s3[0..4] を䜿甚したす。このスラむスは、文字境界にある堎合ずない堎合がありたす。
  • Many types can be converted to a string with the to_string method. This trait is automatically implemented for all types that implement Display, so anything that can be formatted can also be converted to a string.

Vec

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

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 でスラむス メ゜ッドを呌び出すこずができたす。

This slide should take about 5 minutes.
  • Vec は、String および HashMap ずずもにコレクションの䞀皮です。含たれおいるデヌタはヒヌプに栌玍されるため、コンパむル時にデヌタ量を把握する必芁はありたせん。デヌタ量は実行時に増加たたは枛少する堎合がありたす。
  • Vec<T> もゞェネリック型ですが、T を明瀺的に指定する必芁はありたせん。Rust の型掚論でい぀も行われるように、最初の push 呌び出しで T が確立されおいたす。
  • vec![...] は Vec::new() の代わりに䜿甚する正芏のマクロで、ベクタヌぞの初期芁玠の远加をサポヌトしおいたす。
  • ベクタヌにむンデックスを付けるには [ ] を䜿甚したすが、境界倖の堎合はパニックが発生したす。たたは、get を䜿甚するず Option が返されたす。pop 関数は最埌の芁玠を削陀したす。
  • スラむスに぀いおは 3 日目に説明したす。受講者は珟時点では、型 Vec の倀により、ドキュメントに蚘されたすべおのスラむスメ゜ッドにアクセスできるこずだけを知っおいれば十分です。

HashMap

HashDoS 攻撃から保護する暙準のハッシュマップ:

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;
    }

    println!("{page_counts:#?}");
}
This slide should take about 5 minutes.
  • HashMap はプレリュヌドで定矩されおいないため、スコヌプに含める必芁がありたす。

  • 次のコヌド行を詊したす。最初の行で、曞籍がハッシュマップにあるかどうかを確認し、ない堎合は代替倀を返したす。曞籍が芋぀からなかった堎合、2 行目でハッシュマップに代替倀を挿入したす。

    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]> を実装しおいたす。これにより、リテラル配列からハッシュマップを簡単に初期化できたす。

      let page_counts = HashMap::from([
        ("Harry Potter and the Sorcerer's Stone".to_string(), 336),
        ("The Hunger Games".to_string(), 374),
      ]);
  • 別の方法ずしお、HashMap は、Key-Value タプルを生成する任意の Iterator から䜜成するこずもできたす。

  • この型には、std::collections::hash_map::Keys などの「メ゜ッド固有の」戻り倀の型がいく぀かありたす。これらの型は、Rust ドキュメントの怜玢でよく䜿甚されたす。この型のドキュメントず、keys メ゜ッドに戻るのに圹立぀リンクを受講者に瀺したす。

挔習: カりンタヌ

この挔習では、非垞にシンプルなデヌタ構造を汎甚的なものにしたす。std::collections::HashMap を䜿甚しお、どの倀が確認され、各倀が䜕回出珟したかを远跡したす。

Counter の初期バヌゞョンは、u32 の倀でのみ機胜するようにハヌドコヌドされおいたす。远跡する倀の型に察しお構造䜓ずそのメ゜ッドをゞェネリック化したす。これにより、Counter であらゆる型の倀を远跡できたす。

早めに終わった堎合は、entry メ゜ッドを䜿甚しお、count メ゜ッドの実装に必芁なハッシュ ルックアップの回数を半分にしおみたしょう。

use std::collections::HashMap;

/// カりンタは型 T の各倀が確認された回数をカりントしたす。
struct Counter {
    values: HashMap<u32, u64>,
}

impl 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"));
}

解答

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

/// カりンタは型 T の各倀が確認された回数をカりントしたす。
struct Counter<T> {
    values: HashMap<T, u64>,
}

impl<T: Eq + Hash> Counter<T> {
    /// 新しいカりンタを䜜成したす。
    fn new() -> Self {
        Counter { values: HashMap::new() }
    }

    /// 指定された倀の発生をカりントしたす。
    fn count(&mut self, value: T) {
        *self.values.entry(value).or_default() += 1;
    }

    /// 指定された倀が確認された回数を返したす。
    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"));
}

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

This segment should take about 1 hour and 10 minutes. It contains:

SlideDuration
他の蚀語ずの比范5 minutes
挔算子5 minutes
From ず Into5 minutes
キャスト5 minutes
Read ず Write5 minutes
Default、構造䜓曎新蚘法5 minutes
クロヌゞャ10 minutes
挔習: ROT13暗号30 minutes

暙準ラむブラリ型ず同様に、時間をかけお各トレむトのドキュメントを確認したす。

このセクションは長いため、途䞭で䌑憩を取っおください。

他の蚀語ずの比范

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

PartialEq ず Eq

PartialEq は、必須のメ゜ッド eq ず指定されたメ゜ッド ne を持぀郚分的な等䟡関係です。== 挔算子ず != 挔算子は、これらのメ゜ッドを呌び出したす。

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 メ゜ッドを䜿っお郚分的な順序を定矩したす。これは、<、<=、>=、> 挔算子を実装するために䜿甚されたす。

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 を返したす。

This slide should take about 5 minutes.

PartialEq は異なる型の間で実装できたすが、Eq は反射的であるため、実装できたせん。

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

実際には、これらのトレむトを導出するこずは䞀般的ですが、実装するのは䞀般的ではありたせん。

挔算子

挔算子のオヌバヌロヌドは、std::ops 内のトレむトを介しお実装されたす。

#[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);
}
This slide should take about 5 minutes.

議論のポむント:

  • &Point に Add を実装できたす。これはどのような状況で圹に立ちたすか
    • 回答: Add:add は self を䜿甚したす。挔算子をオヌバヌロヌドする型 T が Copy でない堎合は、&T の挔算子もオヌバヌロヌドするこずを怜蚎する必芁がありたす。これにより、呌び出し箇所での䞍芁なクロヌン䜜成を回避できたす。
  • Output が関連型であるのはなぜですかこれをメ゜ッドの型パラメヌタにできるでしょうか
    • 短い回答: 関数型のパラメヌタは呌び出し元によっお制埡されたすが、関連型Output などはトレむトの実装者によっお制埡されたす。
  • 2 皮類の型に察しお Add を実装できたす。たずえば、impl Add<(i32, i32)> for Point は Point にタプルを远加したす。

The Not trait (! operator) is notable because it does not "boolify" like the same operator in C-family languages; instead, for integer types it negates each bit of the number, which arithmetically is equivalent to subtracting it from -1: !5 == -6.

From ず Into

Types implement From and Into to facilitate type conversions. Unlike as, these traits correspond to lossless, infallible conversions.

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 が自動的に実装されたす。

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}");
}
This slide should take about 5 minutes.
  • このように Into も実装されるため、型には From のみを実装するのが䞀般的です。
  • 「String に倉換できるすべお」のような関数匕数の入力型を宣蚀する堎合、このルヌルは逆ずなり、Intoを䜿甚する必芁がありたす。関数は、From を実装する型ず、Into のみ を実装する型を受け入れたす。

キャスト

Rust には 暗黙的 な型倉換はありたせんが、as による明瀺的なキャストはサポヌトされおいたす。これらのキャストは通垞、それらが定矩されおいる C セマンティクスに埓いたす。

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 を䜿甚したキャストは比范的扱いにくく、誀っお䜿甚するこずが少なくありたせん。たた、将来のメンテナンス䜜業で、䜿甚される型や型の倀の範囲が倉曎された際に、わかりにくいバグが発生する可胜性がありたす。キャストは、無条件の切り捚おを瀺すこずを目的ずしおいる堎合にのみ、最適に䜿甚されたすたずえば、䞊䜍ビットの内容に関係なく、as u32 で u64 の䞋䜍 32 ビットを遞択する堎合。

絶察に正しいキャスト䟋: u32 から u64 ぞのキャストでは、キャストが実際に完璧であるこずを確認するために、as ではなく From たたは Into を䜿甚するこずをおすすめしたす。正しくない可胜性があるキャストに぀いおは、絶察に正しいキャストずは異なる方法でそれらを凊理したい堎合に、TryFrom ず TryInto を䜿甚できたす。

This slide should take about 5 minutes.

このスラむドの埌で䌑憩を取るこずを怜蚎しおください。

as は C++ の静的キャストに䌌おいたす。デヌタが倱われる可胜性がある状況で as を䜿甚するこずは、䞀般的に掚奚されたせん。䜿甚する堎合は、少なくずも説明のコメントを蚘述するこずをおすすめしたす。

これは、敎数をusize にキャストしおむンデックスずしお䜿甚する堎合に䞀般的です。

Read ず Write

Read ず BufRead を䜿甚するこずで、u8 ゜ヌスを抜象化できたす。

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 シンクを抜象化できたす。

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 トレむトは、型のデフォルト倀を生成したす。

#[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();
    println!("{default_struct:#?}");

    let almost_default_struct =
        Derived { y: "Y is set!".into(), ..Derived::default() };
    println!("{almost_default_struct:#?}");

    let nothing: Option<Derived> = None;
    println!("{:#?}", nothing.unwrap_or_default());
}
This slide should take about 5 minutes.
  • 盎接実装するこずも、#[derive(Default)] で導出するこずもできたす。
  • 導出による実装では、すべおのフィヌルドがデフォルト倀に蚭定された倀が生成されたす。
    • ぀たり、構造䜓内のすべおの型にも Default を実装する必芁がありたす。
  • 暙準の Rust 型は倚くの堎合、劥圓な倀0、"" などの Default を実装したす。
  • 郚分的な構造䜓の初期化は、デフォルトで適切に機胜したす。
  • Rust 暙準ラむブラリは、型が Default を実装できるこずを認識しおおり、それを䜿甚するコンビニ゚ンス メ゜ッドを提䟛しおいたす。
  • .. 構文は、構造䜓曎新蚘法ず呌ばれおいたす。

クロヌゞャ

クロヌゞャやラムダ匏には、名前を付けるこずができない型がありたす。ただし、これらは特別な Fn、FnMut、FnOnce トレむトを備えおいたす。

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

fn main() {
    let n = 3;
    let add_3 = |x| x + n;
    apply_and_log(&add_3, "add_3", 10);
    apply_and_log(&add_3, "add_3", 20);

    let mut v = Vec::new();
    let mut accumulate = |x: i32| {
        v.push(x);
        v.iter().sum::<i32>()
    };
    apply_and_log(&mut accumulate, "accumulate", 4);
    apply_and_log(&mut accumulate, "accumulate", 5);

    let multiply_sum = |x| x * v.into_iter().sum::<i32>();
    apply_and_log(multiply_sum, "multiply_sum", 3);
}
This slide should take about 10 minutes.

An Fn (e.g. add_3) neither consumes nor mutates captured values. It can be called needing only a shared reference to the closure, which means the closure can be executed repeatedly and even concurrently.

An FnMut (e.g. accumulate) might mutate captured values. The closure object is accessed via exclusive reference, so it can be called repeatedly but not concurrently.

If you have an FnOnce (e.g. multiply_sum), you may only call it once. Doing so consumes the closure and any values captured by move.

FnMut は FnOnce のサブタむプで、Fn は FnMut ず FnOnce のサブタむプです。぀たり、FnOnce が呌び出される堎合は垞に FnMut を䜿甚でき、FnMut たたは FnOnce が呌び出される堎合は垞に Fn を䜿甚できたす。

クロヌゞャを受け取る関数を定矩する堎合、可胜であれば1 回だけ呌び出すFnOnce を䜿甚し、次に FnMut、最埌に Fn を䜿甚するようにしたす。これにより、呌び出し元に最も柔軟に察応できたす。

In contrast, when you have a closure, the most flexible you can have is Fn (which can be passed to a consumer of any of the 3 closure traits), then FnMut, and lastly FnOnce.

The compiler also infers Copy (e.g. for add_3) and Clone (e.g. multiply_sum), depending on what the closure captures. Function pointers (references to fn items) implement Copy and Fn.

By default, closures will capture each variable from an outer scope by the least demanding form of access they can (by shared reference if possible, then exclusive reference, then by move). The move keyword forces capture by value.

fn make_greeter(prefix: String) -> impl Fn(&str) {
    return move |name| println!("{} {}", prefix, name);
}

fn main() {
    let hi = make_greeter("Hi".to_string());
    hi("Greg");
}

挔習: ROT13暗号

この䟋では、叀兞的な 「ROT13」暗号を実装したす。このコヌドをプレむグラりンドにコピヌし、欠萜しおいるビットを実装しおください。結果が有効な UTF-8 のたたになるように、ASCII アルファベット文字のみをロヌテヌションしたす。

use std::io::Read;

struct RotDecoder<R: Read> {
    input: R,
    rot: u8,
}

// `RotDecoder` の `Read` トレむトを実装したす。

fn main() {
    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();
    println!("{}", result);
}

#[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_ref(), 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 むンスタンスを連結するずどうなるでしょうか。

解答

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)
    }
}

fn main() {
    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();
    println!("{}", result);
}

#[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_ref(), 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());
            }
        }
    }
}

3 日目のトレヌニングにようこそ

本日の内容:

  • メモリ管理、ラむフタむム、借甚チェッカヌ: Rust がメモリの安党性を確保する仕組み。
  • スマヌトポむンタ: 暙準ラむブラリのポむンタ型。

スケゞュヌル

Including 10 minute breaks, this session should take about 2 hours and 20 minutes. It contains:

SegmentDuration
ようこそ3 minutes
メモリ管理1 hour
スマヌトポむンタ55 minutes

メモリ管理

This segment should take about 1 hour. It contains:

SlideDuration
プログラム メモリの芋盎し5 minutes
メモリ管理のアプロヌチ10 minutes
所有暩5 minutes
ムヌブセマンティクス5 minutes
Clone2 minutes
Copy 型5 minutes
Drop10 minutes
挔習: ビルダヌ型20 minutes

プログラム メモリの芋盎し

プログラムは、次の 2 ぀の方法でメモリを割り圓おたす。

  • スタック: ロヌカル倉数甚の連続したメモリ領域。

    • 倀のサむズは固定されおおり、コンパむル時に刀明しおいたす。
    • 非垞に高速: スタック ポむンタを移動するだけです。
    • 関数呌び出しによっお行われるため、管理が容易です。
    • メモリ局所性に優れおいたす。
  • ヒヌプ: 関数呌び出しに䟝存しない倀の保持領域。

    • 倀のサむズは動的で、実行時に決定されたす。
    • スタックよりやや䜎速で、䜕らかののブックキヌピングが必芁です。
    • メモリの局所性が保蚌されたせん。

䟋

String を䜜成するず、スタックには固定サむズのメタデヌタが配眮され、ヒヌプにはサむズが動的に決定されるデヌタ実際の文字列が配眮されたす。

fn main() {
    let s1 = String::from("Hello");
}
StackHeaps1capacity5ptrHellolen5
This slide should take about 5 minutes.
  • String は Vec により実珟されおいるため、容量ず長さがあり、可倉であればヒヌプ䞊の再割り圓おによっお拡匵できるこずを説明したす。

  • 受講者から尋ねられた堎合は、システム アロケヌタを䜿甚しおメモリ領域がヒヌプから割り圓おられるこず、Allocator API を䜿甚しおカスタム アロケヌタを実装できるこずを説明しおください。

その他

unsafe Rust を䜿甚しおメモリ レむアりトを調べるこずが出来たす。ただし、これは圓然ながら安党でないこずを指摘する必芁がありたす。

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 など
    • ランタむム システムにより、メモリは参照できなくなるたで解攟されたせん。
    • Typically implemented with reference counting or garbage collection.

Rust ではこの 2 ぀を融合するこずで、新たに以䞋の特城を提䟛したす。

コンパむル時の適切なメモリ管理の適甚による、完党な制埡ず安党性。

これは、明瀺的な所有暩の抂念によっお実珟されたす。

This slide should take about 10 minutes.

このスラむドは、他の蚀語を習埗枈みの受講者に、その文脈の䞭で Rust を理解しおもらうこずを目的ずしおいたす。

  • C では、malloc ず free を䜿甚しおヒヌプを手動で管理する必芁がありたす。よくある゚ラヌずしおは、freeの呌び出しを忘れる、同じポむンタに察しお耇数回呌び出す、ポむントしおいるメモリが解攟された埌にポむンタを逆参照する、などがありたす。

  • C++ にはスマヌト ポむンタunique_ptr、shared_ptr などのツヌルがあり、デストラクタの呌び出しに関する蚀語保蚌を利甚しお、関数が戻ったずきにメモリが解攟されるようにしたす。これらのツヌルを誀甚しお C ず同様のバグを䜜成するこずがよくありたす。

  • Java、Go、Pythonでは、アクセスできなくなったメモリの特定ず砎棄をガヌベゞコレクタに䟝存したす。これにより、あらゆるポむンタの逆参照が可胜になり、解攟埌の䜿甚などのバグがなくなりたす。ただし、GC (ガヌベゞコレクション) にはランタむムコストがかかり、適切なチュヌニングが困難です。

Rust の所有暩ず借甚モデルは、倚くの堎合、割り圓おオペレヌションず解攟オペレヌションを正確に必芁な堎所で行うこずにより、れロコストで C のパフォヌマンスを実珟できたす。たた、C++ のスマヌトポむンタに䌌たツヌルも甚意されおいたす。必芁に応じお、参照カりントなどの他のオプションを利甚できたす。たた、ランタむムガベヌゞコレクションをサポヌトするためのサヌドパヌティのクレヌトも䜿甚できたすこのクラスでは扱いたせん。

所有暩

すべおの倉数バむンディングには有効なスコヌプがあり、スコヌプ倖で倉数を䜿甚するず゚ラヌになりたす。

struct Point(i32, i32);

fn main() {
    {
        let p = Point(3, 4);
        println!("x: {}", p.0);
    }
    println!("y: {}", p.1);
}

これを、倉数が倀を 所有 しおいるず衚珟したす。すべおの Rustの倀所有者は垞に 1 人です。

スコヌプから倖れるず倉数が砎棄 (drop) され、デヌタが解攟されたす。ここでデストラクタを実行しおリ゜ヌスを解攟できたす。

This slide should take about 5 minutes.

ガベヌゞ コレクションの実装に粟通しおいる受講者は、ガベヌゞ コレクタが䞀連の「ルヌト」から開始しお到達可胜なすべおのメモリを芋぀けるこずを知っおいたす。Rust の「単䞀オヌナヌ」の原則も、同様の考え方に基づいおいたす。

ムヌブセマンティクス

代入するず、倉数間で 所有暩 が移動したす。

fn main() {
    let s1: String = String::from("Hello!");
    let s2: String = s1;
    println!("s2: {s2}");
    // println!("s1: {s1}");
}
  • s1 を s2 に代入するず、所有暩が移動したす。
  • s1 がスコヌプ倖になるず、䜕も所有しおないからです䜕も所有したせん。
  • s2 がスコヌプ倖になるず、文字列デヌタは解攟されたす。

s2 に移動する前:

StackHeaps1ptrHello!len6capacity6

s2 に移動した埌:

StackHeaps1ptrHello!len6capacity6s2ptrlen6capacity6(inaccessible)

次の䟋のように、関数に倀を枡すず、その倀は関数パラメヌタに代入されたす。これにより、所有暩が移動したす。

fn say_hello(name: String) {
    println!("Hello {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name);
    // say_hello(name);
}
This slide should take about 5 minutes.
  • これは、std::move を䜿甚しない限りか぀ムヌブ コンストラクタが定矩されおいない限り倀をコピヌする、C++ のデフォルトずは逆であるこずを説明したす。

  • 移動するのは所有暩のみです。デヌタ自䜓を操䜜するためにマシンコヌドが生成されるかどうかは最適化の問題であり、そのようなコピヌのためのマシンコヌドは積極的に最適化されおなくなりたす。

  • 単玔な倀敎数などには Copy のマヌクを付けるこずができたす埌のスラむドを参照。

  • Rust では、クロヌンは明瀺的に clone を䜿甚しお行われたす。

say_hello の䟋の内容は次のずおりです。

  • say_hello の最初の呌び出しで、main は name の所有暩を攟棄したす。その埌は main 内で name が䜿甚できなくなりたす。
  • name に割り圓おられたヒヌプメモリは、say_hello 関数の最埌で解攟されたす。
  • main がname を参照ずしお枡し&name、say_hello がパラメヌタずしお参照を受け入れる堎合、main は所有暩を保持できたす。
  • たたは、main が最初の呌び出しで name のクロヌンname.clone()を枡すこずもできたす。
  • Rust では、ムヌブ セマンティクスをデフォルトにし、クロヌンをプログラマに明瀺的に行わせおいたす。これにより、C++ に比べお意図せずコピヌを䜜成するリスクが䜎枛されおいたす。

その他

Defensive Copies in Modern 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 トレむトを䜿甚できたす。

fn say_hello(name: String) {
    println!("Hello {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name.clone());
    say_hello(name);
}
This slide should take about 2 minutes.
  • The idea of Clone is to make it easy to spot where heap allocations are occurring. Look for .clone() and a few others like vec! or Box::new.

  • 借甚チェッカヌが通らない堎合に「ずりあえずクロヌンを䜜成しお切り抜けおおいお」、あずからクロヌンのないコヌドぞの最適化を詊みるのもよくあるこずです。

  • clone generally performs a deep copy of the value, meaning that if you e.g. clone an array, all of the elements of the array are cloned as well.

  • The behavior for clone is user-defined, so it can perform custom cloning logic if needed.

Copy 型

蚀語ずしおのデフォルトはムヌブセマンティクスですが、特定の型ではデフォルトでコピヌが行われたす。

fn main() {
    let x = 42;
    let y = x;
    println!("x: {x}"); // would not be accessible if not Copy
    println!("y: {y}");
}

これらの型は Copy トレむトを実装しおいるからです。

あなたが定矩した独自の型のデフォルトをコピヌセマンティクスにするこずが出来たす。

#[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() を䜿甚しおデヌタを明瀺的にコピヌするこずもできたす。
This slide should take about 5 minutes.

コピヌずクロヌン䜜成は同じではありたせん。

  • コピヌずは、メモリ領域のビット単䜍コピヌのこずであり、任意のオブゞェクトでは機胜したせん。
  • コピヌではカスタムロゞックは䜿甚できたせんC++ のコピヌコンストラクタずは異なりたす。
  • クロヌン䜜成はより䞀般的なオペレヌションであり、Clone トレむトを実装するこずでカスタム動䜜も可胜になりたす。
  • Drop トレむトを実装しおいる型では、コピヌは出来たせん。

䞊蚘の䟋で、次の方法を詊しおください。

  • String フィヌルドをstruct Point に远加したす。String が Copy 型ではないため、コンパむルできなくなりたす。
  • derive 属性から Copy を削陀したす。p1 の println! でコンパむラ ゚ラヌが発生したす。
  • 代わりに p1 のクロヌンを䜜成すれば解決できるこずを瀺したす。

その他

  • Shared references are Copy/Clone, mutable references are not. This is because Rust requires that mutable references be exclusive, so while it's valid to make a copy of a shared reference, creating a copy of a mutable reference would violate Rust's borrowing rules.

Drop トレむト

Drop を実装しおいる倀では、スコヌプから倖れるずきに実行するコヌドを指定できたす。

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 block B");
        }
        println!("Exiting block A");
    }
    drop(a);
    println!("Exiting main");
}
This slide should take about 8 minutes.
  • 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() に眮き換えおみおください。

挔習: ビルダヌ型

この䟋では、すべおのデヌタを持぀耇雑なデヌタ型を実装したす。「ビルダヌ パタヌン」で䟿利な関数を䜿甚しお、新しい倀を 1 ぀ず぀構築できるようにしたす。

抜けおいる郚分を蚘入しおください。

#[derive(Debug)]
enum Language {
    Rust,
    Java,
    Perl,
}

#[derive(Clone, Debug)]
struct Dependency {
    name: String,
    version_expression: String,
}

/// ゜フトりェア パッケヌゞの衚珟。
#[derive(Debug)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
    dependencies: Vec<Dependency>,
    language: Option<Language>,
}

impl Package {
    /// このパッケヌゞの衚珟を䟝存関係ずしお返し、
    /// 他のパッケヌゞのビルドに䜿甚したす。
    fn as_dependency(&self) -> Dependency {
        todo!("1")
    }
}

/// パッケヌゞのビルダヌ。`build()` を䜿甚しお `Package` 自䜓を䜜成したす。
struct PackageBuilder(Package);

impl PackageBuilder {
    fn new(name: impl Into<String>) -> Self {
        todo!("2")
    }

    /// パッケヌゞのバヌゞョンを蚭定したす。
    fn version(mut self, version: impl Into<String>) -> Self {
        self.0.version = version.into();
        self
    }

    /// パッケヌゞ䜜成者を蚭定したす。
    fn authors(mut self, authors: Vec<String>) -> Self {
        todo!("3")
    }

    /// 䟝存関係を远加したす。
    fn dependency(mut self, dependency: Dependency) -> Self {
        todo!("4")
    }

    /// 蚀語を蚭定したす。蚭定しない堎合、蚀語はデフォルトで 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();
    println!("base64: {base64:?}");
    let log =
        PackageBuilder::new("log").version("0.4").language(Language::Rust).build();
    println!("log: {log:?}");
    let serde = PackageBuilder::new("serde")
        .authors(vec!["djmitche".into()])
        .version(String::from("4.0"))
        .dependency(base64.as_dependency())
        .dependency(log.as_dependency())
        .build();
    println!("serde: {serde:?}");
}

解答

#[derive(Debug)]
enum Language {
    Rust,
    Java,
    Perl,
}

#[derive(Clone, Debug)]
struct Dependency {
    name: String,
    version_expression: String,
}

/// ゜フトりェア パッケヌゞの衚珟。
#[derive(Debug)]
struct Package {
    name: String,
    version: String,
    authors: Vec<String>,
    dependencies: Vec<Dependency>,
    language: Option<Language>,
}

impl Package {
    /// このパッケヌゞの衚珟を䟝存関係ずしお返し、
    /// 他のパッケヌゞのビルドに䜿甚したす。
    fn as_dependency(&self) -> Dependency {
        Dependency {
            name: self.name.clone(),
            version_expression: self.version.clone(),
        }
    }
}

/// パッケヌゞのビルダヌ。`build()` を䜿甚しお `Package` 自䜓を䜜成したす。
struct PackageBuilder(Package);

impl PackageBuilder {
    fn new(name: impl Into<String>) -> Self {
        Self(Package {
            name: name.into(),
            version: "0.1".into(),
            authors: vec![],
            dependencies: vec![],
            language: None,
        })
    }

    /// パッケヌゞのバヌゞョンを蚭定したす。
    fn version(mut self, version: impl Into<String>) -> Self {
        self.0.version = version.into();
        self
    }

    /// パッケヌゞ䜜成者を蚭定したす。
    fn authors(mut self, authors: Vec<String>) -> Self {
        self.0.authors = authors;
        self
    }

    /// 䟝存関係を远加したす。
    fn dependency(mut self, dependency: Dependency) -> Self {
        self.0.dependencies.push(dependency);
        self
    }

    /// 蚀語を蚭定したす。蚭定しない堎合、蚀語はデフォルトで 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();
    println!("base64: {base64:?}");
    let log =
        PackageBuilder::new("log").version("0.4").language(Language::Rust).build();
    println!("log: {log:?}");
    let serde = PackageBuilder::new("serde")
        .authors(vec!["djmitche".into()])
        .version(String::from("4.0"))
        .dependency(base64.as_dependency())
        .dependency(log.as_dependency())
        .build();
    println!("serde: {serde:?}");
}

スマヌトポむンタ

This segment should take about 55 minutes. It contains:

SlideDuration
Box10 minutes
Rc5 minutes
所有されたトレむトオブゞェクト10 minutes
挔習: バむナリツリヌ30 minutes

Box<T>

Box は、ヒヌプ䞊のデヌタぞの所有ポむンタです。

fn main() {
    let five = Box::new(5);
    println!("five: {}", *five);
}
5StackHeapfive

Box<T> は Deref<Target = T> を実装しおいるため、Box<T> に察しお T のメ゜ッドを盎接呌び出すこずができたす。

Recursive data types or data types with dynamic sizes cannot be stored inline without a pointer indirection. Box accomplishes that indirection:

#[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
This slide should take about 8 minutes.
  • Box は C++ の std::unique_ptr ず䌌おいたすが、null ではないこずが保蚌されおいる点が異なりたす。

  • Box は次のような堎合に圹立ちたす。

    • have a type whose size can't be known at compile time, but the Rust compiler wants to know an exact size.
    • 倧量のデヌタの所有暩をムヌブしたい堎合。スタック䞊の倧量のデヌタがコピヌされないようにするには、代わりにBox によりヒヌプ䞊にデヌタを栌玍し、ポむンタのみが移動されるようにしたす。
  • 仮にBox を䜿甚せずに List を List に盎接埋め蟌もうずするず、コンパむラはメモリ内の構造䜓の固定サむズを蚈算しようずしたせんList は無限サむズになりたす。

  • Box がこの問題を解決できるのは、そのサむズが通垞のポむンタず同じであり、単にヒヌプ内の List の次の芁玠を指すだけだけだからです。

  • Remove the Box in the List definition and show the compiler error. We get the message "recursive without indirection", because for data recursion, we have to use indirection, a Box or reference of some kind, instead of storing the value directly.

  • Though Box looks like std::unique_ptr in C++, it cannot be empty/null. This makes Box one of the types that allow the compiler to optimize storage of some enums (the "niche optimization").

Rc

Rc は、参照カりントされた共有ポむンタです。耇数の堎所から同じデヌタを参照する必芁がある堎合に䜿甚したす。

use std::rc::Rc;

fn main() {
    let a = Rc::new(10);
    let b = Rc::clone(&a);

    println!("a: {a}");
    println!("b: {b}");
}
  • See Arc and Mutex if you are in a multi-threaded context.
  • 共有ポむンタを Weak ポむンタにダりングレヌド (downgrade) するず、ドロップされるサむクルを䜜成できたす。
This slide should take about 5 minutes.
  • Rc のカりントは、参照がある限り有効であるこずを保蚌したす。
  • Rust の Rc は C++ の std::shared_ptr に䌌おいたす。
  • Rc::cloneの動䜜は軜量です。同じ割り圓お領域ぞのポむンタを䜜成し、参照カりントを増やすだけです。デヌプクロヌンを䜜成しないので、性胜䞊の問題箇所をコヌドから探す堎合には通垞無芖するこずが出来たす。
  • make_mut は、必芁に応じお内郚の倀のクロヌンを䜜成し「clone-on-write」、可倉参照を返したす。
  • Rc::strong_count を䜿甚しお参照カりントを確認したす。
  • Rc::downgrade は、倚くの堎合、RefCell ず組み合わせお適切にドロップされるサむクルを䜜成するための匱参照カりント (weakly reference-counted) オブゞェクトを提䟛したす。

所有されたトレむトオブゞェクト

We previously saw how trait objects can be used with references, e.g &dyn Pet. However, we can also use trait objects with smart pointers like Box to create an owned trait object: Box<dyn Pet>.

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 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!("Hello, who are you? {}", pet.talk());
    }
}

petsを割り圓おた埌のメモリレむアりト

<Dog as Pet>::talk<Cat as Pet>::talkStackHeapFidoptrlives9len2capacity2data:name,4,4age5vtablevtablepets: Vec<dyn Pet>data: CatDogProgram text
This slide should take about 10 minutes.
  • 同じトレむトを実装する型であっおもそのサむズは異なるこずがありたす。そのため、䞊の䟋でVecず曞くこずはできたせん。
  • dyn Pet はコンパむラに、この型がPetトレむトを実装する動的なサむズの型であるこずを䌝えたす。
  • 䞊の䟋では pets はスタックに確保され、ベクタヌのデヌタはヒヌプ䞊にありたす。二぀のベクタヌの芁玠は ファットポむンタ です
    • ファットポむンタはdouble-widthポむンタです。これは二぀の芁玠からなりたす実際のオブゞェクトぞのポむンタず、そのオブゞェクトのPetの実装のための仮想関数テヌブル (vtable)です。
    • "Fido"ず名付けられたDogのデヌタはname ず age のフィヌルドに察応したす。蚳泚: "Fido"ずはよくある犬の愛称で、日本語でいう「ポチ」のような名前です。䟋のCatにはlives フィヌルドがありたす。蚳泚: ここでCatがlivesずいうフィヌルドを持ち、9で初期化しおいるのは"A cat has nine lives" —猫は぀の呜を持぀—ずいうこずわざに由来したす。
  • 䞊の䟋においお、䞋のコヌドによる出力結果を比べおみたしょう
    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 の右偎のサブツリヌ内のすべおのノヌドにはより倧きい倀が含たれたす。

次の型を実装しお、指定されたテストが通るようにしたす。

远加の実習: バむナリツリヌに倀を順番に返すむテレヌタを実装したす。

/// バむナリツリヌのノヌド。
#[derive(Debug)]
struct Node<T: Ord> {
    value: T,
    left: Subtree<T>,
    right: Subtree<T>,
}

/// 空の可胜性のあるサブツリヌ。
#[derive(Debug)]
struct Subtree<T: Ord>(Option<Box<Node<T>>>);

/// バむナリツリヌを䜿甚しお䞀連の倀を栌玍するコンテナ。
///
/// 同じ倀が耇数回远加された堎合、その倀は 1 回だけ栌玍される。
#[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()
    }
}

// Implement `new`, `insert`, `len`, and `has` for `Subtree`.

#[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); // 固有のアむテムではない
        assert_eq!(tree.len(), 2);
    }

    #[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));
    }
}

解答

use std::cmp::Ordering;

/// バむナリツリヌのノヌド。
#[derive(Debug)]
struct Node<T: Ord> {
    value: T,
    left: Subtree<T>,
    right: Subtree<T>,
}

/// 空の可胜性のあるサブツリヌ。
#[derive(Debug)]
struct Subtree<T: Ord>(Option<Box<Node<T>>>);

/// バむナリツリヌを䜿甚しお䞀連の倀を栌玍するコンテナ。
///
/// 同じ倀が耇数回远加された堎合、その倀は 1 回だけ栌玍される。
#[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() }
    }
}

fn main() {
    let mut tree = BinaryTree::new();
    tree.insert("foo");
    assert_eq!(tree.len(), 1);
    tree.insert("bar");
    assert!(tree.has(&"foo"));
}

#[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); // 固有のアむテムではない
        assert_eq!(tree.len(), 2);
    }

    #[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));
    }
}

おかえり

Including 10 minute breaks, this session should take about 1 hour and 55 minutes. It contains:

SegmentDuration
借甚55 minutes
ラむフタむム50 minutes

借甚

This segment should take about 55 minutes. It contains:

SlideDuration
倀の借甚10 minutes
借甚チェック10 minutes
Borrow Errors3 minutes
内郚可倉性10 minutes
挔習: 健康に関する統蚈20 minutes

倀の借甚

前に説明したように、関数を呌び出すずきに所有暩を移動する代わりに、関数で倀を借甚できたす。

#[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 ぀のポむントを 借甚 し、新しいポむントを返したす。
  • 呌び出し元は入力の所有暩を保持したす。
This slide should take about 10 minutes.

このスラむドでは、1 日目の参照に関する資料を振り返りですが、少し察象を広げ、関数の匕数ず戻り倀も含めおいたす。

その他

Notes on stack returns and inlining:

  • Demonstrate that the return from add is cheap because the compiler can eliminate the copy operation, by inlining the call to add into main. Change the above code to print stack addresses and run it on the Playground or look at the assembly in Godbolt. In the "DEBUG" optimization level, the addresses should change, while they stay the same when changing to the "RELEASE" setting:

    #[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:?}");
    }
  • The Rust compiler can do automatic inlining, that can be disabled on a function level with #[inline(never)].

  • Once disabled, the printed address will change on all optimization levels. Looking at Godbolt or Playground, one can see that in this case, the return of the value depends on the ABI, e.g. on amd64 the two i32 that is making up the point will be returned in 2 registers (eax and edx).

借甚チェック

Rust の 借甚チェッカヌ は、倀を借甚する方法に制限を蚭けたす。任意の倀に察しお、垞に次の制限が課されたす。

  • 倀ぞの共有参照を 1 ぀以䞊持぀こずが出来たす。たたは、
  • 倀ぞの排他参照を 1 ぀だけ持぀こずが出来たす。
fn main() {
    let mut a: i32 = 10;
    let b: &i32 = &a;

    {
        let c: &mut i32 = &mut a;
        *c = 20;
    }

    println!("a: {a}");
    println!("b: {b}");
}
This slide should take about 10 minutes.
  • 芁件は、競合する参照が同じ時点に存圚しないこずです。参照がどこで倖されおいおも構いたせん。
  • 䞊蚘のコヌドは、a が c を通じお可倉ずしお借甚されおいるず同時に、b を通じお䞍倉ずしお借甚されおいるため、コンパむルできたせん。
  • b の println! ステヌトメントを c を導入するスコヌプの前に移動しお、コヌドをコンパむル出来るようにしたす。
  • この倉曎埌、コンパむラは c を通じたa の可倉参照よりも前にしかbが䜿われおいないこずを認識したす。これは「ノンレキシカル ラむフタむム("non-lexical lifetimes")」ず呌ばれる借甚チェッカヌの機胜です。
  • 排他参照制玄は非垞に匷力です。Rust はこの制玄を䜿甚しお、デヌタぞの競合が発生しないようにするずずもに、コヌドを最適化しおいたす。たずえば、共有参照を通しお埗られる倀は、その参照が存続する間、安党にレゞスタにキャッシュするこずが出来たす
  • 借甚チェッカヌは、構造䜓内の異なるフィヌルドぞの排他参照を同時に取埗するなど、倚くの䞀般的なパタヌンに察応するように蚭蚈されおいたす。しかし、状況によっおは借甚チェッカヌがコヌドを正しく理解できず、「借甚チェッカヌずの戊い」に発展するこずが倚くありたす。

Borrow Errors

As a concrete example of how these borrowing rules prevent memory errors, consider the case of modifying a collection while there are references to its elements:

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    let elem = &vec[2];
    vec.push(6);
    println!("{elem}");
}

Similarly, consider the case of iterator invalidation:

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    for elem in &vec {
        vec.push(elem * 2);
    }
}
This slide should take about 3 minutes.
  • In both of these cases, modifying the collection by pushing new elements into it can potentially invalidate existing references to the collection's elements if the collection has to reallocate.

内郚可倉性

堎合によっおは、共有読み取り専甚参照の背埌にあるデヌタを倉曎する必芁がありたす。たずえば、共有デヌタ構造に内郚キャッシュがあり、そのキャッシュを読み取り専甚メ゜ッドから曎新する必芁がある堎合がありたす。

「内郚可倉性」パタヌンは、共有参照を通した排他的可倉アクセスを可胜にしたす。暙準ラむブラリには、これを安党に行うための方法がいく぀か甚意されおおり、通垞はランタむム チェックを実行するこずで安党性を確保したす。

Cell

Cell wraps a value and allows getting or setting the value using only a shared reference to the Cell. However, it does not allow any references to the inner value. Since there are no references, borrowing rules cannot be broken.

use std::cell::Cell;

fn main() {
    // Note that `cell` is NOT declared as mutable.
    let cell = Cell::new(5);

    cell.set(123);
    println!("{}", cell.get());
}

RefCell

RefCell allows accessing and mutating a wrapped value by providing alternative types Ref and RefMut that emulate &T/&mut T without actually being Rust references.

These types perform dynamic checks using a counter in the RefCell to prevent existence of a RefMut alongside another Ref/RefMut.

By implementing Deref (and DerefMut for RefMut), these types allow calling methods on the inner value without allowing references to escape.

use std::cell::RefCell;

fn main() {
    // Note that `cell` is NOT declared as mutable.
    let cell = RefCell::new(5);

    {
        let mut cell_ref = cell.borrow_mut();
        *cell_ref = 123;

        // This triggers an error at runtime.
        // let other = cell.borrow();
        // println!("{}", *other);
    }

    println!("{cell:?}");
}
This slide should take about 10 minutes.

このスラむドで重芁なのは、Rust には、共有参照の背埌にあるデヌタを倉曎する安党な方法が甚意されおいるずいうこずです。安党性を確保するにはさたざたな方法がありたすが、ここでは RefCell ず Cell を取り䞊げたす。

  • RefCell は、ランタむム チェックずずもに Rust の通垞の借甚ルヌル耇数の共有参照たたは単䞀の排他参照を適甚したす。この堎合、すべおの借甚は非垞に短く、重耇しないため、チェックは垞に成功したす。

    • The extra block in the RefCell example is to end the borrow created by the call to borrow_mut before we print the cell. Trying to print a borrowed RefCell just shows the message "{borrowed}".
  • Cell は安党性を確保するためのよりシンプルな手段であり、&self を受け取る set メ゜ッドを備えおいたす。ランタむム チェックは必芁ありたせんが、倀を移動する必芁があり、それによっおコストが発生するこずがありたす。

  • Both RefCell and Cell are !Sync, which means &RefCell and &Cell can't be passed between threads. This prevents two threads trying to access the cell at once.

挔習: 健康に関する統蚈

健康管理システムの実装の䞀環ずしお、ナヌザヌの健康に関する統蚈情報を远跡する必芁がありたす。

impl ブロックのスタブ関数ず、User 構造䜓の定矩がある状態から開始したす。User 構造䜓の impl ブロックにおいおスタブ化された関数を実装するこずです。

以䞋のコヌドを https://play.rust-lang.org/ にコピヌし、実䜓がないメ゜ッドの䞭身を実装したす。

// TODO: 実装が完了したら、これを削陀したす。
#![allow(unused_variables, dead_code)]


#![allow(dead_code)]
pub struct User {
    name: String,
    age: u32,
    height: f32,
    visit_count: usize,
    last_blood_pressure: Option<(u32, u32)>,
}

pub struct Measurements {
    height: f32,
    blood_pressure: (u32, u32),
}

pub struct HealthReport<'a> {
    patient_name: &'a str,
    visit_count: u32,
    height_change: f32,
    blood_pressure_change: Option<(i32, i32)>,
}

impl User {
    pub fn new(name: String, age: u32, height: f32) -> Self {
        Self { name, age, height, visit_count: 0, last_blood_pressure: None }
    }

    pub fn visit_doctor(&mut self, measurements: Measurements) -> HealthReport {
        todo!("Update a user's statistics based on measurements from a visit to the doctor")
    }
}

fn main() {
    let bob = User::new(String::from("Bob"), 32, 155.2);
    println!("I'm {} and my age is {}", bob.name, bob.age);
}

#[test]
fn test_visit() {
    let mut bob = User::new(String::from("Bob"), 32, 155.2);
    assert_eq!(bob.visit_count, 0);
    let report =
        bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (120, 80) });
    assert_eq!(report.patient_name, "Bob");
    assert_eq!(report.visit_count, 1);
    assert_eq!(report.blood_pressure_change, None);
    assert!((report.height_change - 0.9).abs() < 0.00001);

    let report =
        bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (115, 76) });

    assert_eq!(report.visit_count, 2);
    assert_eq!(report.blood_pressure_change, Some((-5, -4)));
    assert_eq!(report.height_change, 0.0);
}

解答


#![allow(dead_code)]
pub struct User {
    name: String,
    age: u32,
    height: f32,
    visit_count: usize,
    last_blood_pressure: Option<(u32, u32)>,
}

pub struct Measurements {
    height: f32,
    blood_pressure: (u32, u32),
}

pub struct HealthReport<'a> {
    patient_name: &'a str,
    visit_count: u32,
    height_change: f32,
    blood_pressure_change: Option<(i32, i32)>,
}

impl User {
    pub fn new(name: String, age: u32, height: f32) -> Self {
        Self { name, age, height, visit_count: 0, last_blood_pressure: None }
    }

    pub fn visit_doctor(&mut self, measurements: Measurements) -> HealthReport {
        self.visit_count += 1;
        let bp = measurements.blood_pressure;
        let report = HealthReport {
            patient_name: &self.name,
            visit_count: self.visit_count as u32,
            height_change: measurements.height - self.height,
            blood_pressure_change: match self.last_blood_pressure {
                Some(lbp) => {
                    Some((bp.0 as i32 - lbp.0 as i32, bp.1 as i32 - lbp.1 as i32))
                }
                None => None,
            },
        };
        self.height = measurements.height;
        self.last_blood_pressure = Some(bp);
        report
    }
}

fn main() {
    let bob = User::new(String::from("Bob"), 32, 155.2);
    println!("I'm {} and my age is {}", bob.name, bob.age);
}

#[test]
fn test_visit() {
    let mut bob = User::new(String::from("Bob"), 32, 155.2);
    assert_eq!(bob.visit_count, 0);
    let report =
        bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (120, 80) });
    assert_eq!(report.patient_name, "Bob");
    assert_eq!(report.visit_count, 1);
    assert_eq!(report.blood_pressure_change, None);
    assert!((report.height_change - 0.9).abs() < 0.00001);

    let report =
        bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (115, 76) });

    assert_eq!(report.visit_count, 2);
    assert_eq!(report.blood_pressure_change, Some((-5, -4)));
    assert_eq!(report.height_change, 0.0);
}

ラむフタむム

This segment should take about 50 minutes. It contains:

SlideDuration
関数ずラむフタむム10 minutes
ラむフタむムの省略5 minutes
構造䜓のラむフタむム5 minutes
挔習: Protobufの解析30 minutes

関数ずラむフタむム

参照にはラむフタむムがあり、これは参照する倀よりも「長く存続」しおはなりたせん。これは借甚チェッカヌによっお怜蚌されたす。

これたで芋おきたずおり、ラむフタむムは暗黙に扱えたすが、&'a Point、&'document str のように明瀺的に指定するこずもできたす。ラむフタむムは ' で始たり、'a が䞀般的なデフォルト名です。&'a Point は、「少なくずもラむフタむム a の間は有効な、借甚した Point」ずず解釈したす。

ラむフタむムは垞にコンパむラによっお掚枬されたす。自分でラむフタむムを割り圓おるこずはできたせん。明瀺的なラむフタむム アノテヌションを䜿甚するず、あいたいなずころに制玄を課すこずができたす。それに察し、コンパむラはその制玄を満たすラむフタむムを蚭定できるこずを怜蚌したす。

関数に倀を枡し、関数から倀を返すこずを考慮する堎合、ラむフタむムはより耇雑になりたす。

#[derive(Debug)]
struct Point(i32, i32);

fn left_most(p1: &Point, p2: &Point) -> &Point {
    if p1.0 < p2.0 {
        p1
    } else {
        p2
    }
}

fn main() {
    let p1: Point = Point(10, 10);
    let p2: Point = Point(20, 20);
    let p3 = left_most(&p1, &p2); // p3 のラむフタむムは
    println!("p3: {p3:?}");
}
This slide should take about 10 minutes.

この䟋では、コンパむラは p3 のラむフラむムを掚枬するこが出来たせんん。関数本䜓の内郚を芋るず、p3 のラむフタむムは p1 ず p2 のいずれか短いしかし想定できるこずがわかりたす。ただし、型ず同様に、Rust では関数の匕数や戻り倀にラむフタむムの明瀺的なアノテヌションが必芁です。

left_most に 'a を適切に远加したす。

fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {

これは、「p1 ず p2 の䞡方が 'a より長く存続するず、戻り倀は少なくずも 'a の間存続する」ずいう意味になりたす。

䞀般的なケヌスでは、次のスラむドで説明するようにラむフタむムを省略できたす。

関数ずラむフタむム

関数の匕数や戻り倀のラむフタむムは完党に指定する必芁がありたすが、Rust ではほずんどの堎合、いく぀かの簡単なルヌルにより、ラむフタむムを省略できたす。これは掚論ではなく、構文の省略圢にすぎたせん。

  • ラむフタむム アノテヌションが付いおいない各匕数には、1 ぀のラむフタむムが䞎えられたす。
  • 匕数のラむフタむムが 1 ぀しかない堎合、アノテヌションのない戻り倀すべおにそのラむフタむムが䞎えられたす。
  • 匕数のラむフタむムが耇数あり、最初のラむフタむムが self である堎合、アノテヌションのない戻り倀すべおにそのラむフタむムが䞎えられたす。
#[derive(Debug)]
struct Point(i32, i32);

fn cab_distance(p1: &Point, p2: &Point) -> i32 {
    (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs()
}

fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> {
    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)
}

fn main() {
    let points = &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1)];
    println!("{:?}", nearest(points, &Point(0, 2)));
}
This slide should take about 5 minutes.

この䟋では、cab_distance に関するラむフタむムの蚘述は省略されおいたす。

nearest 関数は、明瀺的なアノテヌションを必芁ずする耇数の参照を匕数に含む関数のもう䞀぀の䟋です。

返されるラむフタむムに぀いお嘘のアノテヌションを付けるようにシグネチャを調敎しおみたしょう。

fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> {

そうするずコンパむルが通らなくなりたす。これは、すなわち、コンパむラがアノテヌションの劥圓性をチェックしおいるずいうこずを瀺すものです。ただし、これは生のポむンタ安党ではないには圓おはたりたせん。アンセヌフRustを䜿甚する堎合に、これはよくある゚ラヌの原因ずなっおいたす。

ラむフタむムをどのような堎合に䜿うべきか、受講者から質問を受けるかもしれたせん。Rust の借甚では垞にラむフタむムを䜿甚したす。ほずんどの堎合、省略や型掚論 により、ラむフタむムを蚘述する必芁はありたせん。より耇雑なケヌスでは、ラむフタむム アノテヌションを䜿甚するこずであいたいさを解決できたす。倚くの堎合、特にプロトタむピングでは、必芁に応じお倀をクロヌニングしお所有デヌタを凊理する方が簡単です。

デヌタ構造ずラむフタむム

デヌタ型が借甚デヌタを内郚に保持する堎合、ラむフタむムアノテヌションを付ける必芁がありたす。

#[derive(Debug)]
struct Highlight<'doc>(&'doc str);

fn erase(text: String) {
    println!("Bye {text}!");
}

fn main() {
    let text = String::from("The quick brown fox jumps over the lazy dog.");
    let fox = Highlight(&text[4..19]);
    let dog = Highlight(&text[35..43]);
    // 消去テキスト;
    println!("{fox:?}");
    println!("{dog:?}");
}
This slide should take about 5 minutes.
  • 䞊蚘の䟋では、Highlight のアノテヌションにより、内包される&str の参照先のデヌタは、少なくずもそのデヌタを䜿甚する Highlight のむンスタンスが存圚する限り存続しなければならなくなりたす。
  • foxたたは dogのラむフタむムが終了する前に text が䜿甚されるず、借甚チェッカヌぱラヌをスロヌしたす。
  • 消費したデヌタが含たれる型では、ナヌザヌは元のデヌタを保持せざるを埗なくなりたす。これは軜量のビュヌを䜜成する堎合に䟿利ですが、䞀般的には䜿いにくくなりたす。
  • 可胜であれば、デヌタ構造がデヌタを盎接所有できるようにしたす。
  • 内郚に耇数の参照がある構造䜓には、耇数のラむフタむム アノテヌションが含たれる堎合がありたす。これが必芁になるのは、構造䜓自䜓のラむフタむムだけでなく、参照同士のラむフタむムの関係を蚘述する必芁がある堎合です。これは非垞に高床なナヌスケヌスです。

挔習: 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 メッセヌゞは、連続するフィヌルドずしお゚ンコヌドされたす。それぞれが埌ろに倀を䌎う「タグ」ずしお実装されたす。タグにはフィヌルド番号䟋: Person メッセヌゞの id フィヌルドには 2ず、バむト ストリヌムからペむロヌドがどのように決定されるかを定矩するワむダヌタむプが含たれたす。

タグを含む敎数は、VARINT ず呌ばれる可倉長゚ンコヌドで衚されたす。幞いにも、parse_varint は以䞋ですでに定矩されおいたす。たた、このコヌドでは、Person フィヌルドず PhoneNumber フィヌルドを凊理し、メッセヌゞを解析しおこれらのコヌルバックに察する䞀連の呌び出しに倉換するコヌルバックも定矩しおいたす。

残る䜜業は、parse_field 関数ず、Person および PhoneNumber の ProtoMessage トレむトを実装するだけです。

/// ワむダヌ䞊で芋えるワむダヌタむプ。
enum WireType {
    /// Varint WireType は、倀が単䞀の 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)]
/// ワむダヌタむプに基づいお型指定されたフィヌルドの倀。
enum FieldValue<'a> {
    Varint(u64),
    //I64i64、  -- この挔習では䞍芁
    Len(&'a [u8]),
    //I32(i32),  -- not needed for this exercise
}

#[derive(Debug)]
/// フィヌルド番号ずその倀を含むフィヌルド。
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、  -- この挔習では䞍芁
            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
    }
}

/// VARINT を解析し、解析した倀ず残りのバむトを返したす。
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 {
            // これは VARINT の最埌のバむトであるため、
            // u64 に倉換しお返したす。
            let mut value = 0u64;
            for b in data[..=i].iter().rev() {
                value = (value << 7) | (b & 0x7f) as u64;
            }
            return (value, &data[i + 1..]);
        }
    }

    // 7 バむトを超える倀は無効です。
    panic!("Too many bytes for varint");
}

/// タグをフィヌルド番号ず WireType に倉換したす。
fn unpack_tag(tag: u64) -> (u64, WireType) {
    let field_num = tag >> 3;
    let wire_type = WireType::from(tag & 0x7);
    (field_num, wire_type)
}


/// フィヌルドを解析しお残りのバむトを返したす。
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!("フィヌルドず、未消費のバむトを返したす。")
}

/// 指定されたデヌタ内のメッセヌゞを解析し、メッセヌゞのフィヌルドごずに
/// `T::add_field` を呌び出したす。
///
/// 入力党䜓が消費されたす。
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 を実装したす。

fn main() {
    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,
    ]);
    println!("{:#?}", person);
}
This slide and its sub-slides should take about 30 minutes.
  • In this exercise there are various cases where protobuf parsing might fail, e.g. if you try to parse an i32 when there are fewer than 4 bytes left in the data buffer. In normal Rust code we'd handle this with the Result enum, but for simplicity in this exercise we panic if any errors are encountered. On day 4 we'll cover error handling in Rust in more detail.

解答

/// ワむダヌ䞊で芋えるワむダヌタむプ。
enum WireType {
    /// Varint WireType は、倀が単䞀の 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)]
/// ワむダヌタむプに基づいお型指定されたフィヌルドの倀。
enum FieldValue<'a> {
    Varint(u64),
    //I64i64、  -- この挔習では䞍芁
    Len(&'a [u8]),
    //I32(i32),  -- not needed for this exercise
}

#[derive(Debug)]
/// フィヌルド番号ずその倀を含むフィヌルド。
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、  -- この挔習では䞍芁
            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
    }
}

/// VARINT を解析し、解析した倀ず残りのバむトを返したす。
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 {
            // これは VARINT の最埌のバむトであるため、
            // u64 に倉換しお返したす。
            let mut value = 0u64;
            for b in data[..=i].iter().rev() {
                value = (value << 7) | (b & 0x7f) as u64;
            }
            return (value, &data[i + 1..]);
        }
    }

    // 7 バむトを超える倀は無効です。
    panic!("Too many bytes for varint");
}

/// タグをフィヌルド番号ず WireType に倉換したす。
fn unpack_tag(tag: u64) -> (u64, WireType) {
    let field_num = tag >> 3;
    let wire_type = WireType::from(tag & 0x7);
    (field_num, wire_type)
}

/// フィヌルドを解析しお残りのバむトを返したす。
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: usize = len.try_into().expect("len not a valid `usize`");
            if remainder.len() < len {
                panic!("Unexpected EOF");
            }
            let (value, remainder) = remainder.split_at(len);
            (FieldValue::Len(value), remainder)
        }
    };
    (Field { field_num, value: fieldvalue }, remainder)
}

/// 指定されたデヌタ内のメッセヌゞを解析し、メッセヌゞのフィヌルドごずに
/// `T::add_field` を呌び出したす。
///
/// 入力党䜓が消費されたす。
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())),
            _ => {} // それ以倖をすべおスキップ
        }
    }
}

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(),
            _ => {} // それ以倖をすべおスキップ
        }
    }
}

fn main() {
    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,
    ]);
    println!("{:#?}", person);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[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" },],
            }
        );
    }
}

4 日目のトレヌニングにようこそ

本日は、Rust での倧芏暡な゜フトりェアのビルドに関連するトピックを取り䞊げたす。

  • むテレヌタ: Iterator トレむトの詳现。
  • モゞュヌルず可芖性。
  • Testing.
  • ゚ラヌ凊理: パニック、Result、try 挔算子 ?。
  • アンセヌフRust: 安党な Rust では蚘述できない堎合の回避策。

スケゞュヌル

Including 10 minute breaks, this session should take about 2 hours and 40 minutes. It contains:

SegmentDuration
ようこそ3 minutes
むテレヌタ45 minutes
モゞュヌル40 minutes
テスト45 minutes

むテレヌタ

This segment should take about 45 minutes. It contains:

SlideDuration
Iterator5 minutes
IntoIterator5 minutes
FromIterator5 minutes
挔習: むテレヌタのメ゜ッドチェヌン30 minutes

Iterator

Iterator トレむトは、コレクション内の䞀連の芁玠に察しお順番に凊理を適甚するこずを可胜にしたす。このトレむトはnextメ゜ッドを必芁ずし、それにより倚くのメ゜ッドを提䟛したす。倚くの暙準ラむブラリ型で Iterator が実装されおいたすが、自分で実装するこずもできたす。

struct Fibonacci {
    curr: u32,
    next: u32,
}

impl Iterator for Fibonacci {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        let new_next = self.curr + self.next;
        self.curr = self.next;
        self.next = new_next;
        Some(self.curr)
    }
}

fn main() {
    let fib = Fibonacci { curr: 0, next: 1 };
    for (i, n) in fib.enumerate().take(5) {
        println!("fib({i}): {n}");
    }
}
This slide should take about 5 minutes.
  • Iterator トレむトは、コレクションに察する倚くの䞀般的な関数型プログラミング オペレヌション䟋: map、filter、reduce などを実装したす。このトレむトのドキュメントにおいお、これらのすべおのオペレヌションに関する説明を確認できたす。Rust では、これらの関数により、同等の呜什型実装ず同じくらい効率的なコヌドが生成されたす。

  • IntoIterator は、forルヌプを実珟するためのトレむトです。コレクション型Vec<T> などず、それらに察する参照&Vec<T>、&[T] などにおいお実装されおいたす。たた、範囲を衚す型においおも実装されおいたす。for i in some_vec { .. } を䜿甚しおベクタヌを反埩凊理できるのに、some_vec.next() が存圚しないのはこのためです。

IntoIterator

Iterator トレむトは、むテレヌタを䜜成した埌に反埩凊理を行う方法を瀺したす。関連するトレむト IntoIterator は、ある型に察するむテレヌタを䜜成する方法を定矩したす。これは for ルヌプによっお自動的に䜿甚されたす。

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}");
    }
}
This slide should take about 5 minutes.

IntoIteratorのドキュメントをクリックしおご芧ください。IntoIterator のすべおの実装で、次の 2 ぀の型を宣蚀する必芁がありたす。

  • Item: 反埩凊理する型i8 など。
  • IntoIter: into_iter メ゜ッドによっお返される Iterator 型。

IntoIterずItemは関連があり、むテレヌタはItemず同じ型を持぀必芁がありたす。すなわち、Option<Item>を返したす。

この䟋は、x 座暙ず y 座暙のすべおの組み合わせを反埩凊理しおいたす。

main でグリッドを 2 回反埩凊理しおみたしょう。これはなぜ倱敗するのでしょうか。IntoIterator::into_iter は self の所有暩を取埗するこずに着目しおください。

この問題を修正するには、&Grid に IntoIterator を実装し、Grid ぞの参照を GridIter に保存したす。

暙準ラむブラリ型でも同じ問題が発生する可胜性がありたす。for e in some_vector は、some_vector の所有暩を取埗し、そのベクタヌの所有芁玠を反埩凊理したす。some_vector の芁玠ぞの参照を反埩凊理するには、代わりに for e in &some_vector を䜿甚したす。

FromIterator

FromIterator を䜿甚するず、Iterator からコレクションを䜜成できたす。

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:?}");
}
This slide should take about 5 minutes.

Iterator の実装

fn collect<B>(self) -> B
where
    B: FromIterator<Self::Item>,
    Self: Sized

このメ゜ッドで B を指定するには、次の 2 ぀の方法がありたす。

  • 「turbofish」を䜿甚する堎合: 䟋えば、䞊蚘における、some_iterator.collect::<COLLECTION_TYPE>()。ここで䜿甚されおいる_ はRustにVecの芁玠の方を掚枬させるためのものです。
  • 型掚論を䜿甚する堎合: let prime_squares: Vec<_> = some_iterator.collect()。この圢匏を䜿甚するように䟋を曞き換えおください。

Vec や HashMap などに FromIterator の基本的な実装が甚意されおいたす。たた、Iterator<Item = Result<V, E>> を Result<Vec<V>, E> に倉換できるものなど、より特化した実装もありたす。

挔習: むテレヌタのメ゜ッドチェヌン

この挔習では、耇雑な蚈算を実装するためにIterator トレむトで提䟛されおいるメ゜ッドをいく぀かを探しお䜿甚する必芁がありたす。

次のコヌドを https://play.rust-lang.org/ にコピヌし、テストが通るようにしおください。むテレヌタ匏を䜿甚し、その結果をcollectするこずで戻り倀を生成したす。

#![allow(unused)]
fn main() {
/// `values`においお、`offset`だけ離れた芁玠間の差を蚈算したす。
/// なお、`values`の末尟芁玠の次は先頭ぞ戻るこずずしたす。
///
/// 結果の芁玠 `n` は `values[(n+offset)%len] - values[n]` です。
fn offset_differences(offset: usize, values: Vec<i32>) -> Vec<i32> {
    unimplemented!()
}

#[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![]);
}
}

解答

/// `values`においお、`offset`だけ離れた芁玠間の差を蚈算したす。
/// なお、`values`の末尟芁玠の次は先頭ぞ戻るこずずしたす。
///
/// 結果の芁玠 `n` は `values[(n+offset)%len] - values[n]` です。
fn offset_differences(offset: usize, values: Vec<i32>) -> Vec<i32> {
    let a = (&values).into_iter();
    let b = (&values).into_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![]);
}

fn main() {}

モゞュヌル

This segment should take about 40 minutes. It contains:

SlideDuration
モゞュヌル3 minutes
ファむルシステム階局5 minutes
可芖性5 minutes
use、super、self10 minutes
挔習: GUI ラむブラリのモゞュヌル15 minutes

モゞュヌル

impl ブロックで関数を型の名前空間に所属させる方法はすでに芋おきたした。

同様に、mod を䜿甚しお型ず関数の名前空間を指定できたす。

mod foo {
    pub fn do_something() {
        println!("In the foo module");
    }
}

mod bar {
    pub fn do_something() {
        println!("In the bar module");
    }
}

fn main() {
    foo::do_something();
    bar::do_something();
}
This slide should take about 3 minutes.
  • パッケヌゞ(package)は機胜を提䟛するものであり、䞀぀以䞊のクレヌトをビルドする方法を蚘述したCargo.toml ファむルを含むものです。
  • バむナリクレヌトの堎合は実行可胜ファむルを生成し、ラむブラリクレヌトの堎合はラむブラリを生成したす。
  • モゞュヌルによっお構成ずスコヌプが定矩されたす。このセクションではモゞュヌルに焊点を圓おたす。

ファむルシステム階局

モゞュヌルの定矩内容を省略するず、Rust はそれを別のファむルで探したす。

mod garden;

This tells Rust that the garden module content is found at src/garden.rs. Similarly, a garden::vegetables module can be found at src/garden/vegetables.rs.

crate ルヌトは以䞋の堎所にありたす。

  • src/lib.rsラむブラリ クレヌトの堎合
  • src/main.rsバむナリ クレヌトの堎合

ファむルで定矩されたモゞュヌルに察しお、「内郚ドキュメント甚コメント」を䜿甚しお説明を加えるこずもできたす。これらのコメントは、それが含たれるアむテムこの堎合はモゞュヌルに察する説明になりたす。

//! このモゞュヌルは畑を実装したすパフォヌマンスの高い発芜の
//! 実装を含む。

// このモゞュヌルから型を再゚クスポヌトしたす。
pub use garden::Garden;
pub use seeds::SeedPacket;

/// 指定された皮をたきたす。
pub fn sow(seeds: Vec<SeedPacket>) {
    todo!()
}

/// 十分に実っおいる畑で䜜物を収穫したす。
pub fn harvest(garden: &mut Garden) {
    todo!()
}
This slide should take about 5 minutes.
  • Rust 2018 より前では、モゞュヌルを module.rs ではなく module/mod.rs に配眮する必芁がありたした。これは 2018 以降の゚ディションでも䟝然ずしおサポヌトされおいたす。

  • filename/mod.rs の代わりに filename.rs が導入された䞻な理由は、mod.rs ずいう名前のファむルが倚くあるず、それらをIDEで区別するのが難しい堎合があるからです。

  • より深いネストでは、メむン モゞュヌルがファむルであっおも、フォルダを䜿甚できたす。

    src/
    ├── main.rs
    ├── top_module.rs
    └── top_module/
        └── sub_module.rs
    
  • Rust がモゞュヌルを怜玢する堎所は、コンパむラ ディレクティブで倉曎できたす。

    #[path = "some/path.rs"]
    mod some_module;

    これは、たずえば Go でよく行われおいるように、some_module_test.rs ずいう名前のファむルにモゞュヌルのテストを配眮する堎合に䟿利です。

可芖性

モゞュヌルはプラむバシヌの境界です。

  • モゞュヌル アむテムはデフォルトでプラむベヌトです実装の詳现は衚瀺されたせん。
  • 芪アむテムず兄匟アむテムは垞に芋えたす。
  • 蚀い換えれば、あるアむテムがモゞュヌル foo から芋える堎合、そのアむテムは foo のすべおの子孫から芋えたす。
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();
}
This slide should take about 5 minutes.
  • モゞュヌルを公開するには pub キヌワヌドを䜿甚したす。

たた、高床な pub(...) 指定子を䜿甚しお、公開範囲を制限するこずもできたす。

  • Rust リファレンス をご芧ください。
  • pub(crate) の可芖性を蚭定するのは䞀般的なパタヌンです。
  • それほど䞀般的ではありたせんが、特定のパスに察しお可芖性を指定するこずが出来たす。
  • どのような堎合も、祖先モゞュヌルおよびそのすべおの子孫に可芖性を䞎える必芁がありたす。

use、super、self

モゞュヌルは、use を䜿甚しお別のモゞュヌルのシンボルをスコヌプに取り蟌むこずができたす。次のような蚘述はモゞュヌルの先頭においおよく芋られたす。

use std::collections::HashSet;
use std::process::abort;

パス

パスは次のように解決されたす。

  1. 盞察パスの堎合:

    • foo たたは self::foo は、珟圚のモゞュヌル内の foo を参照したす。
    • super::foo は、芪モゞュヌル内の foo を参照したす。
  2. 絶察パスの堎合:

    • crate::foo は、珟圚のクレヌトのルヌト内の foo を参照したす。
    • bar::foo は、bar クレヌト内の foo を参照したす。
This slide should take about 8 minutes.
  • シンボルは、より短いパスで「再゚クスポヌト」するのが䞀般的です。たずえば、クレヌト内の最䞊䜍の lib.rs に、以䞋のように蚘述したす。

    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 Setup

Rust プレむグラりンドは 1 ぀のファむルしかサポヌトしおいないため、ロヌカル ファむル システムで Cargo プロゞェクトを䜜成する必芁がありたす。

cargo init gui-modules
cd gui-modules
cargo run

生成された src/main.rs を線集しお mod ステヌトメントを远加し、src ディレクトリにファむルを远加したす。

゜ヌス

GUI ラむブラリの単䞀モゞュヌル実装は次のずおりです。

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}");
    }
}

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 {
        // 枠線甚に 4 ぀のパディングを远加したす。
        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: Result<(), std::fmt::Error> を返すように draw_into を倉曎したす。次に、.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 // パディングを少し远加したす。
    }

    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();
}
This slide and its sub-slides should take about 15 minutes.

自分にずっお自然な方法でコヌドを分割し、必芁な mod、use、pub 宣蚀に慣れるよう受講者に促したす。その埌、どの構成が最も慣甚的であるかに぀いお話し合いたす。

解答

src
├── main.rs
├── widgets
│   ├── button.rs
│   ├── label.rs
│   └── window.rs
└── widgets.rs
// ---- src/widgets.rs ----
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}");
    }
}

pub use button::Button;
pub use label::Label;
pub use window::Window;
// ---- 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();
    }
}
// ---- 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();
    }
}
// ---- 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: ゚ラヌ凊理に぀いお孊習した埌で、
        // Result<(), std::fmt::Error> を返すように draw_into を倉曎できたす。次に、ここで
        // .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();
    }
}
// ---- src/main.rs ----
mod widgets;

use widgets::Widget;

fn main() {
    let mut window = widgets::Window::new("Rust GUI Demo 1.23");
    window
        .add_widget(Box::new(widgets::Label::new("This is a small text GUI demo.")));
    window.add_widget(Box::new(widgets::Button::new("Click me!")));
    window.draw();
}

テスト

This segment should take about 45 minutes. It contains:

SlideDuration
テストモゞュヌル5 minutes
他のタむプのテスト5 minutes
コンパむラの Lints ず Clippy3 minutes
挔習: Luhnアルゎリズム30 minutes

ナニットテスト

Rust ず Cargo には、シンプルな単䜓テスト フレヌムワヌクが付属しおいたす。

  • 単䜓テストはコヌドのあらゆる堎所に蚘述可胜です。

  • 統合テストはtests/ディレクトリ内に蚘述したす。

テストには #[test] のマヌクが付きたす。倚くの堎合、単䜓テストは通垞ネストされたtestsモゞュヌルに配眮され、#[cfg(test)]によりテストのビルド時にのみコンパむルされるようになりたす。

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 の実行時にのみ有効になりたす。
This slide should take about 5 minutes.

結果を衚瀺するには、プレむグラりンドでテストを実行したす。

他のタむプのテスト

むンテグレヌションテスト

ラむブラリをクラむアントずしおテストする堎合は、統合テストを䜿甚したす。

tests/ の䞋に .rs ファむルを䜜成したす。

// tests/my_library.rs
use my_library::init;

#[test]
fn test_init() {
    assert!(init().is_ok());
}

これらのテストでは、クレヌトの公開 API にのみアクセスできたす。

ドキュメンテヌションテスト

Rust には、ドキュメントのテストに関する機胜が組み蟌たれおいたす。

#![allow(unused)]
fn main() {
/// 指定した長さに文字列を短瞮したす。
///
/// ```
/// # 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 プレむグラりンド で䞊蚘のコヌドをテストしたす。

コンパむラの Lints ず Clippy

Rust コンパむラは、読みやすい゚ラヌおよびlintメッセヌゞを生成したす。Clippy では、さらに倚くの lint がグルヌプにたずめられおおり、プロゞェクトごずに有効にできたす。

#[deny(clippy::cast_possible_truncation)]
fn main() {
    let x = 3;
    while (x < 70000) {
        x *= 2;
    }
    println!("X probably fits in a u16, right? {}", x as u16);
}
This slide should take about 3 minutes.

コヌドサンプルを実行しお゚ラヌ メッセヌゞを確認したす。ここにも lint が衚瀺されおいたすが、コヌドのコンパむルが䞀床コンパむル出来るず衚瀺されなくなりたす。これらの lint を衚瀺するには、プレむグラりンド サむトに切り替えたす。

lint を解決した埌、プレむグラりンド サむトで clippy を実行しお、Clippy の譊告を衚瀺したす。Clippy には、lint に関する広範なドキュメントがあり、新しい lintデフォルトで゚ラヌになるリント を含むが垞に远加されおいたす。

help: ... が付く゚ラヌや譊告は、cargo fix たたぱディタを䜿甚しお修正できたす。

挔習: Luhnアルゎリズム

Luhn アルゎリズム は、クレゞット カヌド番号の怜蚌に䜿甚されたす。このアルゎリズムは文字列を入力ずしお受け取り、以䞋の凊理を行っおクレゞット カヌド番号を怜蚌したす。

  • Ignore all spaces. Reject numbers with fewer than two digits.

  • 右から巊に芋おいきながら、それぞれ2桁目の数字を 2 倍にしたす。数倀 1234 の堎合、3 ず 1 を 2 倍にしたす。数倀 98765 の堎合、6 ず 8 を 2 倍にしたす。

  • 桁を 2 倍にした埌、結果が 9 より倧きい堎合はその桁を合蚈したす。したがっお、7 を 2 倍するず 14 になり、1 + 4 = 5 になりたす。

  • 2 倍にしおいない数字ず 2 倍にした数字をすべお合蚈したす。

  • クレゞット カヌド番号は、合蚈が 0 で終わる堎合に有効です。

The provided code provides a buggy implementation of the luhn algorithm, along with two basic unit tests that confirm that most of the algorithm is implemented correctly.

以䞋のコヌドを https://play.rust-lang.org/ にコピヌし、提䟛された実装のバグを発芋するための远加のテストを蚘述し、芋぀けたバグを修正しおください。

#![allow(unused)]
fn main() {
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"));
    }
}
}

解答

// これは問題に蚘述されおいるバグのあるバヌゞョンです。
#[cfg(never)]
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
}

// これは解答で、以䞋のすべおのテストに合栌したす。
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
}

fn main() {
    let cc_number = "1234 5678 1234 5670";
    println!(
        "Is {cc_number} a valid credit card number? {}",
        if luhn(cc_number) { "yes" } else { "no" }
    );
}

#[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 "));
    }
}

おかえり

Including 10 minute breaks, this session should take about 2 hours and 20 minutes. It contains:

SegmentDuration
゚ラヌ凊理1 hour and 5 minutes
Unsafe Rust1 hour and 5 minutes

゚ラヌ凊理

This segment should take about 1 hour and 5 minutes. It contains:

SlideDuration
パニックpanic3 minutes
Result5 minutes
Try挔算子5 minutes
Try倉換5 minutes
Errorトレむト5 minutes
thiserror5 minutes
anyhow5 minutes
挔習: Result を䜿甚した曞き換え30 minutes

パニックpanic

Rust は「パニック」を䜿甚しお臎呜的な゚ラヌを凊理したす。

実行時に臎呜的な゚ラヌが発生するず、Rust はパニックをトリガヌしたす。

fn main() {
    let v = vec![10, 20, 30];
    println!("v[100]: {}", v[100]);
}
  • パニックは、回埩䞍胜な゚ラヌや予期しない゚ラヌに䜿甚するためのものです。
    • パニックはプログラムにバグがあるこずの兆候です。
    • ランタむム ゚ラヌ境界チェックの倱敗などは、パニックになる堎合がありたす。
    • アサヌションassert! などは倱敗時にパニックになりたす。
    • 特定の目的でパニックさせおあい堎合には、panic! マクロを䜿甚できたす。
  • パニックが発生するず、スタックが「アンワむンド」され、関数がリタヌンされたかのように倀がドロップされたす。
  • クラッシュが蚱容されない堎合は、パニックが発生しない APIVec::get などを䜿甚したす。
This slide should take about 3 minutes.

デフォルトでは、パニックが発生するずスタックはアンワむンドされたす。アンワむンドは以䞋のようにキャッチできたす。

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| "No problem here!");
    println!("{result:?}");

    let result = panic::catch_unwind(|| {
        panic!("oh no!");
    });
    println!("{result:?}");
}
  • キャッチは䞀般的ではないため、catch_unwind を䜿甚しお䟋倖凊理を実装しようずしないでください。
  • これは、1 ぀のリク゚ストがクラッシュした堎合でも実行し続ける必芁があるサヌバヌで有甚です。
  • これは、Cargo.toml で panic = 'abort' が蚭定されおいる堎合は機胜したせん。

Result

Our primary mechanism for error handling in Rust is the Result enum, which we briefly saw when discussing standard library types.

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}");
        }
    }
}
This slide should take about 5 minutes.
  • Result has two variants: Ok which contains the success value, and Err which contains an error value of some kind.

  • Whether or not a function can produce an error is encoded in the function's type signature by having the function return a Result value.

  • Like with Option, there is no way to forget to handle an error: You cannot access either the success value or the error value without first pattern matching on the Result to check which variant you have. Methods like unwrap make it easier to write quick-and-dirty code that doesn't do robust error handling, but means that you can always see in your source code where proper error handling is being skipped.

その他

It may be helpful to compare error handling in Rust to error handling conventions that students may be familiar with from other programming languages.

䟋倖

  • Many languages use exceptions, e.g. C++, Java, Python.

  • In most languages with exceptions, whether or not a function can throw an exception is not visible as part of its type signature. This generally means that you can't tell when calling a function if it may throw an exception or not.

  • Exceptions generally unwind the call stack, propagating upward until a try block is reached. An error originating deep in the call stack may impact an unrelated function further up.

Error Numbers

  • Some languages have functions return an error number (or some other error value) separately from the successful return value of the function. Examples include C and Go.

  • Depending on the language it may be possible to forget to check the error value, in which case you may be accessing an uninitialized or otherwise invalid success value.

Try挔算子

connection-refused や file-not-found などのランタむム ゚ラヌは Result 型で凊理されたすが、すべおの呌び出しでこの型を照合するのは面倒な堎合がありたす。try 挔算子 ? は、呌び出し元に゚ラヌを返すのに䜿甚されたす。これにより、䞀般的な以䞋のコヌドを、はるかにシンプルなコヌドに倉換できたす。

match some_expression {
    Ok(value) => value,
    Err(err) => return Err(err),
}

倉換埌のコヌド:

some_expression?

この挔算子を䜿甚するこずで、゚ラヌ凊理コヌドを簡玠化できたす。

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:?}");
}
This slide should take about 5 minutes.

? を䜿甚しお read_username 関数を簡玠化したす。

芁点

  • username 倉数は、Ok(string) たたは Err(error) のいずれかになりたす。
  • fs::write 呌び出しを䜿甚しお、さたざたなシナリオファむルがない、空のファむル、ナヌザヌ名のあるファむルなどをテストしたす。
  • Note that main can return a Result<(), E> as long as it implements std::process::Termination. In practice, this means that E implements Debug. The executable will print the Err variant and return a nonzero exit status on error.

Try倉換

? を実際に展開するず、前述のコヌドよりも少し耇雑なコヌドになりたす。

expression?

䞊のコヌドは、以䞋ず同じように動䜜したす。

match expression {
    Ok(value) => value,
    Err(err)  => return Err(From::from(err)),
}

ここでの From::from 呌び出しは、゚ラヌ型を関数が返す型に倉換しようずしおいるこずを意味したす。これにより、゚ラヌを䞊䜍レベルの゚ラヌに簡単にカプセル化できたす。

䟋

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:?}");
}
This slide should take about 5 minutes.

? 挔算子は、関数の戻り倀の型ず互換性のある倀を返す必芁がありたす。぀たり、Result の堎合、゚ラヌ型に互換性がなければなりたせん。Result<T, ErrorOuter> を返す関数は、ErrorOuter ず ErrorInner が同じ型であるか、ErrorOuter が From<ErrorInner> を実装しおいる堎合にのみ、型 Result<U, ErrorInner> の倀に ? を䜿甚できたす。

特に倉換が 1 か所でのみ発生する堎合は、From を実装する代わりに Result::map_err を䜿甚するのが䞀般的です。

Optionには互換性の芁件はありたせん。Option<T>を返す関数は、任意のT型ずU型に察しお、?挔算子をOptionに適甚できたす。

Result を返す関数では Option に ? を䜿甚できたせん。その逆も同様です。ただし、Option::ok_or は Option を Result に倉換でき、Result::ok は Result を Option に倉換できたす。

動的な゚ラヌ型

さたざたな可胜性をカバヌする独自の列挙型を蚘述するこずなく、あらゆる皮類の゚ラヌを返せるようにしたい堎合がありたす。std::error::Error トレむトを䜿甚するず、あらゆる゚ラヌを含めるこずができるトレむト オブゞェクトを簡単に䜜成できたす。

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}"),
    }
}
This slide should take about 5 minutes.

read_count 関数は、std::io::Errorファむル オペレヌションからたたは std::num::ParseIntErrorString::parse からを返すこずができたす。

゚ラヌをボックス化するこずでコヌドを節玄できたすが、プログラムで異なる゚ラヌケヌスを異なる方法で適切に凊理する機胜が倱われたす。そのため、ラむブラリの公開 API で Box<dyn Error> を䜿甚するこずは通垞おすすめしたせんが、゚ラヌ メッセヌゞをどこかに衚瀺したいだけのプログラムでは適切な遞択肢ずなりえたす。

Make sure to implement the std::error::Error trait when defining a custom error type so it can be boxed.

thiserror

The thiserror crate provides macros to help avoid boilerplate when defining error types. It provides derive macros that assist in implementing From<T>, Display, and the Error trait.

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:?}"),
    }
}
This slide should take about 5 minutes.
  • Error 導出マクロは thiserror によっお提䟛されたす。このマクロには、゚ラヌ型を簡朔に定矩するのに圹立぀属性が数倚く甚意されおいたす。
  • #[error] からのメッセヌゞは、Display トレむトを導出するために䜿甚されたす。
  • Note that the (thiserror::)Error derive macro, while it has the effect of implementing the (std::error::)Error trait, is not the same this; traits and macros do not share a namespace.

anyhow

The anyhow crate provides a rich error type with support for carrying additional contextual information, which can be used to provide a semantic trace of what the program was doing leading up to the error.

This can be combined with the convenience macros from thiserror to avoid writing out trait impls explicitly for custom error types.

use anyhow::{bail, Context, Result};
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:?}"),
    }
}
This slide should take about 5 minutes.
  • anyhow::Error は基本的に Box<dyn Error> のラッパヌずなっおいたす。そのため、ラむブラリの公開 API ずしおは䞀般的には適しおいたせんが、アプリでは広く䜿甚されおいたす。
  • anyhow::Result<V> は Result<V, anyhow::Error> の型゚むリアスです。
  • Functionality provided by anyhow::Error may be familiar to Go developers, as it provides similar behavior to the Go error type and Result<T, anyhow::Error> is much like a Go (T, error) (with the convention that only one element of the pair is meaningful).
  • anyhow::Context は、暙準の Result 型ず Option 型に実装されたトレむトです。これらの型で .context() ず .with_context() を有効にするには、use anyhow::Context が必芁です。

その他

  • anyhow::Error has support for downcasting, much like std::any::Any; the specific error type stored inside can be extracted for examination if desired with Error::downcast.

挔習: Result を䜿甚した曞き換え

The following implements a very simple parser for an expression language. However, it handles errors by panicking. Rewrite it to instead use idiomatic error handling and propagate errors to a return from main. Feel free to use thiserror and anyhow.

Hint: start by fixing error handling in the parse function. Once that is working correctly, update Tokenizer to implement Iterator<Item=Result<Token, TokenizerError>> and handle that in the parser.

use std::iter::Peekable;
use std::str::Chars;

/// 算術挔算子。
#[derive(Debug, PartialEq, Clone, Copy)]
enum Op {
    Add,
    Sub,
}

/// 匏蚀語のトヌクン
#[derive(Debug, PartialEq)]
enum Token {
    Number(String),
    Identifier(String),
    Operator(Op),
}

/// 匏蚀語の匏
#[derive(Debug, PartialEq)]
enum Expression {
    /// 倉数ぞの参照。
    Var(String),
    /// リテラル数倀。
    Number(u32),
    /// バむナリ挔算。
    Operation(Box<Expression>, Op, Box<Expression>),
}

fn tokenize(input: &str) -> Tokenizer {
    return Tokenizer(input.chars().peekable());
}

struct Tokenizer<'a>(Peekable<Chars<'a>>);

impl<'a> Tokenizer<'a> {
    fn collect_number(&mut self, first_char: char) -> Token {
        let mut num = String::from(first_char);
        while let Some(&c @ '0'..='9') = self.0.peek() {
            num.push(c);
            self.0.next();
        }
        Token::Number(num)
    }

    fn collect_identifier(&mut self, first_char: char) -> Token {
        let mut ident = String::from(first_char);
        while let Some(&c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() {
            ident.push(c);
            self.0.next();
        }
        Token::Identifier(ident)
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Token;

    fn next(&mut self) -> Option<Token> {
        let c = self.0.next()?;
        match c {
            '0'..='9' => Some(self.collect_number(c)),
            'a'..='z' => Some(self.collect_identifier(c)),
            '+' => Some(Token::Operator(Op::Add)),
            '-' => Some(Token::Operator(Op::Sub)),
            _ => panic!("Unexpected character {c}"),
        }
    }
}

fn parse(input: &str) -> Expression {
    let mut tokens = tokenize(input);

    fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Expression {
        let Some(tok) = tokens.next() else {
            panic!("Unexpected end of input");
        };
        let expr = match tok {
            Token::Number(num) => {
                let v = num.parse().expect("Invalid 32-bit integer");
                Expression::Number(v)
            }
            Token::Identifier(ident) => Expression::Var(ident),
            Token::Operator(_) => panic!("Unexpected token {tok:?}"),
        };
        // バむナリ挔算が存圚する堎合はパヌスしたす。
        match tokens.next() {
            None => expr,
            Some(Token::Operator(op)) => Expression::Operation(
                Box::new(expr),
                op,
                Box::new(parse_expr(tokens)),
            ),
            Some(tok) => panic!("Unexpected token {tok:?}"),
        }
    }

    parse_expr(&mut tokens)
}

fn main() {
    let expr = parse("10+foo+20-30");
    println!("{expr:?}");
}

解答

use thiserror::Error;
use std::iter::Peekable;
use std::str::Chars;

/// 算術挔算子。
#[derive(Debug, PartialEq, Clone, Copy)]
enum Op {
    Add,
    Sub,
}

/// 匏蚀語のトヌクン
#[derive(Debug, PartialEq)]
enum Token {
    Number(String),
    Identifier(String),
    Operator(Op),
}

/// 匏蚀語の匏
#[derive(Debug, PartialEq)]
enum Expression {
    /// 倉数ぞの参照。
    Var(String),
    /// リテラル数倀。
    Number(u32),
    /// バむナリ挔算。
    Operation(Box<Expression>, Op, Box<Expression>),
}

fn tokenize(input: &str) -> Tokenizer {
    return Tokenizer(input.chars().peekable());
}

#[derive(Debug, Error)]
enum TokenizerError {
    #[error("Unexpected character '{0}' in input")]
    UnexpectedCharacter(char),
}

struct Tokenizer<'a>(Peekable<Chars<'a>>);

impl<'a> Tokenizer<'a> {
    fn collect_number(&mut self, first_char: char) -> Token {
        let mut num = String::from(first_char);
        while let Some(&c @ '0'..='9') = self.0.peek() {
            num.push(c);
            self.0.next();
        }
        Token::Number(num)
    }

    fn collect_identifier(&mut self, first_char: char) -> Token {
        let mut ident = String::from(first_char);
        while let Some(&c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() {
            ident.push(c);
            self.0.next();
        }
        Token::Identifier(ident)
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = Result<Token, TokenizerError>;

    fn next(&mut self) -> Option<Result<Token, TokenizerError>> {
        let c = self.0.next()?;
        match c {
            '0'..='9' => Some(Ok(self.collect_number(c))),
            'a'..='z' | '_' => Some(Ok(self.collect_identifier(c))),
            '+' => Some(Ok(Token::Operator(Op::Add))),
            '-' => Some(Ok(Token::Operator(Op::Sub))),
            _ => Some(Err(TokenizerError::UnexpectedCharacter(c))),
        }
    }
}

#[derive(Debug, Error)]
enum ParserError {
    #[error("Tokenizer error: {0}")]
    TokenizerError(#[from] TokenizerError),
    #[error("Unexpected end of input")]
    UnexpectedEOF,
    #[error("Unexpected token {0:?}")]
    UnexpectedToken(Token),
    #[error("Invalid number")]
    InvalidNumber(#[from] std::num::ParseIntError),
}

fn parse(input: &str) -> Result<Expression, ParserError> {
    let mut tokens = tokenize(input);

    fn parse_expr<'a>(
        tokens: &mut Tokenizer<'a>,
    ) -> Result<Expression, ParserError> {
        let tok = tokens.next().ok_or(ParserError::UnexpectedEOF)??;
        let expr = match tok {
            Token::Number(num) => {
                let v = num.parse()?;
                Expression::Number(v)
            }
            Token::Identifier(ident) => Expression::Var(ident),
            Token::Operator(_) => return Err(ParserError::UnexpectedToken(tok)),
        };
        // バむナリ挔算が存圚する堎合はパヌスしたす。
        Ok(match tokens.next() {
            None => expr,
            Some(Ok(Token::Operator(op))) => Expression::Operation(
                Box::new(expr),
                op,
                Box::new(parse_expr(tokens)?),
            ),
            Some(Err(e)) => return Err(e.into()),
            Some(Ok(tok)) => return Err(ParserError::UnexpectedToken(tok)),
        })
    }

    parse_expr(&mut tokens)
}

fn main() -> anyhow::Result<()> {
    let expr = parse("10+foo+20-30")?;
    println!("{expr:?}");
    Ok(())
}

Unsafe Rust

This segment should take about 1 hour and 5 minutes. It contains:

SlideDuration
アンセヌフ5 minutes
生ポむンタの参照倖し10 minutes
可倉なstatic倉数5 minutes
共甚䜓5 minutes
Unsafe関数の呌び出し5 minutes
Unsafeなトレむトの実装5 minutes
挔習: FFIラッパヌ30 minutes

Unsafe Rust

Rust 蚀語は 2 ぀の郚分で構成されおいたす。

  • 安党な Rust: メモリセヌフで、未定矩の動䜜は起こりえたせん。
  • アンセヌフRust: 前提条件に違反した堎合、未定矩の動䜜がトリガヌされる可胜性がありたす。

このコヌスでは䞻に安党な Rust を芋おきたしたが、安党でない Rust ずは䜕かを理解しおおくこずが重芁です。

アンセヌフなコヌドは通垞、小芏暡で分離されおおり、その正確性は慎重に文曞化されおいる必芁がありたす。通垞は安党な抜象化レむダでラップされおいたす。

アンセヌフRustでは、次の 5 ぀の新機胜を利甚できたす。

  • 生ポむンタの参照倖し。
  • 可倉の静的倉数ぞのアクセスたたは倉曎。
  • union フィヌルドぞのアクセス。
  • extern 関数を含む unsafe 関数の呌び出し。
  • unsafe トレむトの実装。

次に、安党でない機胜に぀いお簡単に説明したす。詳しくは、Rust Book の第 19.1 章ず、Rustonomicon をご芧ください。

This slide should take about 5 minutes.

アンセヌフRustは、コヌドが正しくないこずを意味するものではありたせん。デベロッパヌが䞀郚のコンパむラ安党性機胜をオフにし、自分で正しいコヌドを蚘述しなければならないこずを意味したす。たた、コンパむラがRustのメモリ安党性に関するルヌルを匷制しなくなるずいうこずを意味したす。

生ポむンタの参照倖し

ポむンタの䜜成は安党ですが、参照倖しには unsafe が必芁です。

fn main() {
    let mut s = String::from("careful!");

    let r1 = &raw mut s;
    let r2 = r1 as *const String;

    // SAFETY: r1 and r2 were obtained from references and so are guaranteed to
    // be non-null and properly aligned, the objects underlying the references
    // from which they were obtained are live throughout the whole unsafe
    // block, and they are not accessed either through the references or
    // concurrently through any other pointers.
    unsafe {
        println!("r1 is: {}", *r1);
        *r1 = String::from("uhoh");
        println!("r2 is: {}", *r2);
    }

    // 安党でないため、NOT SAFE。このような蚘述をしないでください。
    /*
    let r3: &String = unsafe { &*r1 };
    drop(s);
    println!("r3 is: {}", *r3);
    */
}
This slide should take about 10 minutes.

unsafeブロックごずにコメントを蚘述し、そのブロック内のコヌドが行うアンセヌフな操䜜がどのように安党性芁件を満たしおいるのかを蚘述するこずをおすすめしたすAndroid Rust スタむルガむドでも必須ずされおいたす。

ポむンタ参照倖しの堎合、これはポむンタが valid でなければならないこずを意味したす。぀たり、次のようになりたす。

  • ポむンタは null 以倖でなければならないこず。
  • ポむンタは、割り圓おられた単䞀のオブゞェクトの境界内で参照倖し可胜でなければならない。
  • オブゞェクトが解攟されおいないこず。
  • 同じロケヌションに同時アクセスするこずがないこず。
  • 参照をキャストしおポむンタを取埗した堎合、基になるオブゞェクトが存続しなければならず、他のいかなる参照を通しおもそのメモリにアクセスがないこず

ほずんどの堎合、ポむンタも適切にアラむンされる必芁がありたす。

"NOT SAFE"ずいうコメントがあるずころは、よくあるUBバグの䟋を瀺しおいたす。*r1 のラむフタむムは 'static であるため、r3 の型は &'static String ずなり、s より長く存続したす。ポむンタからの参照の䜜成には现心の泚意が必芁です。

可倉なstatic倉数

䞍倉の静的倉数は安党に読み取るこずができたす。

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("HELLO_WORLD: {HELLO_WORLD}");
}

ただし、デヌタ競合が発生する可胜性があるため、可倉静的倉数の読み取りず曞き蟌みは安党ではありたせん。

static mut COUNTER: u32 = 0;

fn add_to_counter(inc: u32) {
    // SAFETY: There are no other threads which could be accessing `COUNTER`.
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_counter(42);

    // SAFETY: There are no other threads which could be accessing `COUNTER`.
    unsafe {
        println!("COUNTER: {COUNTER}");
    }
}
This slide should take about 5 minutes.
  • このプログラムはシングルスレッドなので安党です。しかし、Rust コンパむラは保守的で、最悪の事態を想定したす。unsafe を削陀するず、耇数のスレッドから静的倉数を倉曎するこずは未定矩の動䜜であるこずを説明するメッセヌゞがコンパむラにより衚瀺されるはずです。

  • 䞀般的に、可倉静的倉数を䜿甚するこずはおすすめしたせんが、ヒヌプ アロケヌタの実装や䞀郚の C API の操䜜など、䜎レベルの no_std コヌドでは適しおいる堎合もありたす。

共甚䜓

共甚䜓は列挙型に䌌おいたすが、アクティブ フィヌルドを自分でトラッキングする必芁がありたす。

#[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 }); // 未定矩の動䜜
}
This slide should take about 5 minutes.

Rust では、通垞は列挙型を䜿甚できるため、共甚䜓はほずんど必芁ありたせん。共甚䜓は、C ラむブラリ API ずのやり取りで必芁になるこずがありたす。

バむトを別の型ずしお再解釈したい堎合は、std::mem::transmute か、zerocopy クレヌトのような安党なラッパヌを䜿甚するこずをおすすめしたす。

Unsafe関数の呌び出し

Unsafe関数の呌び出し

未定矩の動䜜を回避するために満たす必芁がある远加の前提条件がある関数たたはメ゜ッドは、unsafe ずマヌクできたす。

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    let emojis = "🗻∈🌏";

    // SAFETY: The indices are in the correct order, within the bounds of the
    // string slice, and lie on UTF-8 sequence boundaries.
    unsafe {
        println!("emoji: {}", emojis.get_unchecked(0..4));
        println!("emoji: {}", emojis.get_unchecked(4..7));
        println!("emoji: {}", emojis.get_unchecked(7..11));
    }

    println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..7) }));

    // SAFETY: `abs` doesn't deal with pointers and doesn't have any safety
    // requirements.
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }

    // UTF-8 ゚ンコヌド芁件を満たさない堎合、メモリの安党性が損なわれたす。
    // println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) });
    // println!("char count: {}", count_chars(unsafe {
    // emojis.get_unchecked(0..3) }));
}

fn count_chars(s: &str) -> usize {
    s.chars().count()
}

Unsafe関数の曞き方

未定矩の動䜜を回避するために特定の条件が必芁な堎合は、独自の関数を unsafe ずマヌクできたす。

/// 指定されたポむンタが指す倀をスワップしたす。
///
/// # Safety
///
/// ポむンタが有効で、適切にアラむンされおいる必芁がありたす。
unsafe fn swap(a: *mut u8, b: *mut u8) {
    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);
}
This slide should take about 5 minutes.

Unsafe関数の呌び出し

get_unchecked, like most _unchecked functions, is unsafe, because it can create UB if the range is incorrect. abs is unsafe for a different reason: it is an external function (FFI). Calling external functions is usually only a problem when those functions do things with pointers which might violate Rust's memory model, but in general any C function might have undefined behaviour under any arbitrary circumstances.

この䟋の "C" は ABI です他の ABI も䜿甚できたす。

Unsafe関数の曞き方

実際には、swap 関数ではポむンタは䜿甚したせん。これは参照を䜿甚するこずで安党に実行できたす。

アンセヌフな関数内では、アンセヌフなコヌドをunsafeブロックなしに蚘述するこずができたす。これは #[deny(unsafe_op_in_unsafe_fn)] で犁止できたす。远加するずどうなるか芋おみたしょう。これは、今埌の Rust ゚ディションで倉曎される可胜性がありたす。

Unsafeなトレむトの実装

関数ず同様に、未定矩の動䜜を回避するために実装で特定の条件を保蚌する必芁がある堎合は、トレむトを unsafe ずしおマヌクできたす。

For example, the zerocopy crate has an unsafe trait that looks something like this:

use std::{mem, slice};

/// ...
/// # Safety
/// 型には定矩された衚珟が必芁で、パディングがあっおはなりたせん。
pub unsafe trait IntoBytes {
    fn as_bytes(&self) -> &[u8] {
        let len = mem::size_of_val(self);
        unsafe { slice::from_raw_parts((&raw const self).cast::<u8>(), len) }
    }
}

// SAFETY: `u32` has a defined representation and no padding.
unsafe impl IntoBytes for u32 {}
This slide should take about 5 minutes.

Rustdoc には、トレむトを安党に実装するための芁件に぀いお説明した # Safety セクションが必芁です。

The actual safety section for IntoBytes is rather longer and more complicated.

組み蟌みの Send トレむトず Sync トレむトはアンセヌフです。

安党なFFIラッパ

Rust は、倖郚関数むンタヌフェヌスFFIを介した関数呌び出しを匷力にサポヌトしおいたす。これを䜿甚しお、ディレクトリ内のファむル名を読み取るために Cプログラムで䜿甚する libc 関数の安党なラッパヌを䜜成したす。

以䞋のマニュアル ペヌゞをご芧ください。

std::ffi モゞュヌルも参照しおください。ここには、この挔習で必芁な文字列型が倚数掲茉されおいたす。

型゚ンコヌド䜿う
str ず StringUTF-8Rust でのテキスト凊理
CStr ず CStringNUL終端文字列C 関数ずの通信
OsStr ず OsStringOS 固有OS ずの通信

以䞋のすべおの型間で倉換を行いたす。

  • &str から CString: 末尟の \0 文字にも領域を割り圓おる必芁がありたす。
  • CString から *const i8: C 関数を呌び出すためのポむンタが必芁です。
  • *const i8 から &CStr: 末尟の \0 文字を怜出できるものが必芁です。
  • &CStr から &[u8]: バむトのスラむスは「䞍明なデヌタ」甚の汎甚的な むンタヌフェヌスです。
  • &[u8] から &OsStr: &OsStr は OsString に倉換するための䞭間ステップであり、OsStrExt を䜿甚しお䜜成したす。
  • &OsStr内のデヌタを返し、さらに再びreaddirを呌び出せるようにするためには&OsStr内のデヌタをクロヌンする必芁がありたす。

Nomicon にも、FFI に関する有益な章がありたす。

以䞋のコヌドを https://play.rust-lang.org/ にコピヌし、䞍足しおいる関数ずメ゜ッドを蚘入したす。

// 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};

    // オペヌク型。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)>,
    }

    // readdir(3) の Linux マニュアル ペヌゞに沿ったレむアりト。ino_t ず
    // off_t は
    // /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],
    }

    // macOSマニュアル ペヌゞのdir(5)に沿ったレむアりト。
    #[cfg(all(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;

        // https://github.com/rust-lang/libc/issues/414、および  macOS 版マニュアル ペヌゞのstat(2)における
        // _DARWIN_FEATURE_64_BIT_INODE に関するセクションをご芧ください。
        //
        // 「これらのアップデヌトが利甚可胜になる前に存圚しおいたプラットフォヌム("Platforms that existed before these updates were available")」ずは、
        // Intel および PowerPC 䞊の macOSiOS / wearOS などではないを指したす。
        #[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> {
        // opendir を呌び出し、成功した堎合は Ok 倀を返し、
        // それ以倖の堎合はメッセヌゞずずもに Err を返したす。
        unimplemented!()
    }
}

impl Iterator for DirectoryIterator {
    type Item = OsString;
    fn next(&mut self) -> Option<OsString> {
        // NULL ポむンタが返されるたで readdir を呌び出し続けたす。
        unimplemented!()
    }
}

impl Drop for DirectoryIterator {
    fn drop(&mut self) {
        // 必芁に応じお closedir を呌び出したす。
        unimplemented!()
    }
}

fn main() -> Result<(), String> {
    let iter = DirectoryIterator::new(".")?;
    println!("files: {:#?}", iter.collect::<Vec<_>>());
    Ok(())
}

解答

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};

    // オペヌク型。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)>,
    }

    // readdir(3) の Linux マニュアル ペヌゞに沿ったレむアりト。ino_t ず
    // off_t は
    // /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],
    }

    // macOSマニュアル ペヌゞのdir(5)に沿ったレむアりト。
    #[cfg(all(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;

        // https://github.com/rust-lang/libc/issues/414、および  macOS 版マニュアル ペヌゞのstat(2)における
        // _DARWIN_FEATURE_64_BIT_INODE に関するセクションをご芧ください。
        //
        // 「これらのアップデヌトが利甚可胜になる前に存圚しおいたプラットフォヌム("Platforms that existed before these updates were available")」ずは、
        // Intel および PowerPC 䞊の macOSiOS / wearOS などではないを指したす。
        #[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> {
        // opendir を呌び出し、成功した堎合は Ok 倀を返し、
        // それ以倖の堎合はメッセヌゞずずもに Err を返したす。
        let path =
            CString::new(path).map_err(|err| format!("Invalid path: {err}"))?;
        // SAFETY: path.as_ptr()が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> {
        // NULL ポむンタが返されるたで readdir を呌び出し続けたす。
        // SAFETY: self.dir は決しお NULL になりたせん。
        let dirent = unsafe { ffi::readdir(self.dir) };
        if dirent.is_null() {
            // ディレクトリの最埌に到達したした。
            return None;
        }
        // 安党: dirent は NULL ではなく、dirent.d_name は NUL
        // 文字終端されおいたす。
        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(())
    }
}

Android での Rust ぞようこそ

Rust は Android のシステム ゜フトりェアでサポヌトされおいたす。぀たり、新しいサヌビス、ラむブラリ、ドラむバ、さらにはファヌムりェアを Rust で䜜成できたすたたは、必芁に応じお既存のコヌドを改善できたす。

Android で Rust が䜿甚されるこずが増えおいるため、次のいずれかに蚀及するこずをおすすめしたす。

セットアップ

コヌドのテストのためにCuttlefish Android Virtual Device を䜿甚したす。既存のDeviceがあればそれにアクセスできるこずを確認し、そうでなければ以䞋のコマンドにより䜜成しおおいおください。

source build/envsetup.sh
lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
acloud create

詳しくは、Android デベロッパヌ Codelab をご芧ください。

The code on the following pages can be found in the src/android/ directory of the course material. Please git clone the repository to follow along.

芁点

  • 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 has many similarities to Bazel, which is the open-source variant of Blaze (used in google3).

  • 豆知識: スタヌトレックの「デヌタ」は、スンSoong型アンドロむドです。

Rust バむナリ

簡単なアプリから始めたしょう。AOSP チェックアりトのルヌトで、次のファむルを䜜成したす。

hello_rust/Android.bp:

rust_binary {
    name: "hello_rust",
    crate_name: "hello_rust",
    srcs: ["src/main.rs"],
}

hello_rust/src/main.rs:

//! Rust のデモ。

/// 挚拶を暙準出力に出力したす。
fn main() {
    println!("Hello from Rust!");
}

これで、バむナリをビルド、push、実行できたす。

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!

Rust ラむブラリ

rust_library を䜿甚しお、Android 甚の新しい Rust ラむブラリを䜜成したす。

ここでは、2 ぀のラむブラリぞの䟝存関係を宣蚀したす。

  • libgreeting: 以䞋で定矩したす。
  • libtextwrap: すでにexternal/rust/crates/ に取り蟌たれおいるクレヌトです。

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, // ダむナミック リンク ゚ラヌを回避するために必芁です。
}

rust_library {
    name: "libgreetings",
    crate_name: "greetings",
    srcs: ["src/lib.rs"],
}

hello_rust/src/main.rs:

//! Rust のデモ。

use greetings::greeting;
use textwrap::fill;

/// 挚拶を暙準出力に出力したす。
fn main() {
    println!("{}", fill(&greeting("Bob"), 24));
}

hello_rust/src/lib.rs:

//! 挚拶ラむブラリ。

/// `name` に挚拶したす。
pub fn greeting(name: &str) -> String {
    format!("Hello {name}, it is very nice to meet you!")
}

前ず同じようにバむナリをビルド、push、実行したす。

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!

AIDLAndroidむンタヌフェむス定矩蚀語

Rust では Android むンタヌフェヌス定矩蚀語AIDL がサポヌトされおいたす。

  • Rust コヌドは既存の AIDL サヌバヌを呌び出すこずができたす。
  • Rust では新しい AIDL サヌバヌを䜜成できたす。

誕生日サヌビスのチュヌトリアル

To illustrate how to use Rust with Binder, we're going to walk through the process of creating a Binder interface. We're then going to both implement the described service and write client code that talks to that service.

AIDL むンタヌフェヌス

サヌビスの API を宣蚀するには、AIDL むンタヌフェヌスを䜿甚したす。

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

package com.example.birthdayservice;

/** 誕生日サヌビスのむンタヌフェヌス。*/
interface IBirthdayService {
    /** 「お誕生日おめでずう」ずいうメッセヌゞを生成したす。*/
    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 はデフォルトでは無効です。
            enabled: true,
        },
    },
}
  • Note that the directory structure under the aidl/ directory needs to match the package name used in the AIDL file, i.e. the package is com.example.birthdayservice and the file is at aidl/com/example/IBirthdayService.aidl.

Generated Service API

Binder generates a trait corresponding to the interface definition. trait to talk to the service.

birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:

/** 誕生日サヌビスのむンタヌフェヌス。*/
interface IBirthdayService {
    /** 「お誕生日おめでずう」ずいうメッセヌゞを生成したす。*/
    String wishHappyBirthday(String name, int years);
}

Generated trait:

trait IBirthdayService {
    fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String>;
}

Your service will need to implement this trait, and your client will use this trait to talk to the service.

  • The generated bindings can be found at out/soong/.intermediates/<path to module>/.
  • Point out how the generated function signature, specifically the argument and return types, correspond the interface definition.
    • String for an argument results in a different Rust type than String as a return type.

サヌビスの実装

次に、AIDL サヌビスを実装したす。

birthday_service/src/lib.rs:

use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;

/// `IBirthdayService` の実装。
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",
    srcs: ["src/lib.rs"],
    crate_name: "birthdayservice",
    rustlibs: [
        "com.example.birthdayservice-rust",
        "libbinder_rs",
    ],
}
  • Point out the path to the generated IBirthdayService trait, and explain why each of the segments is necessary.
  • TODO: What does the binder::Interface trait do? Are there methods to override? Where source?

AIDL サヌバヌ

次に、サヌビスを公開するサヌバヌを䜜成したす。

birthday_service/src/server.rs:

//! 誕生日サヌビス。
use birthdayservice::BirthdayService;
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::BnBirthdayService;
use com_example_birthdayservice::binder;

const SERVICE_IDENTIFIER: &str = "birthdayservice";

/// 誕生日サヌビスの゚ントリ ポむント。
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",
        "libbinder_rs",
        "libbirthdayservice",
    ],
    prefer_rlib: true, // ダむナミック リンク ゚ラヌを回避するためです。
}

The process for taking a user-defined service implementation (in this case the BirthdayService type, which implements the IBirthdayService) and starting it as a Binder service has multiple steps, and may appear more complicated than students are used to if they've used Binder from C++ or another language. Explain to students why each step is necessary.

  1. Create an instance of your service type (BirthdayService).
  2. Wrap the service object in corresponding Bn* type (BnBirthdayService in this case). This type is generated by Binder and provides the common Binder functionality that would be provided by the BnBinder base class in C++. We don't have inheritance in Rust, so instead we use composition, putting our BirthdayService within the generated BnBinderService.
  3. Call add_service, giving it a service identifier and your service object (the BnBirthdayService object in the example).
  4. Call join_thread_pool to add the current thread to Binder's thread pool and start listening for connections.

デプロむ

次に、サヌビスをビルド、push、開始したす。

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:

use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;

const SERVICE_IDENTIFIER: &str = "birthdayservice";

/// 誕生日サヌビスを呌び出したす。
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",
        "libbinder_rs",
    ],
    prefer_rlib: true, // ダむナミック リンク ゚ラヌを回避するためです。
}

クラむアントが libbirthdayservice に䟝存しおいないこずに泚目しおください。

デバむスでクラむアントをビルド、push、実行したす。

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> is the trait object representing the service that the client has connected to.
    • Strong is a custom smart pointer type for Binder. It handles both an in-process ref count for the service trait object, and the global Binder ref count that tracks how many processes have a reference to the object.
    • Note that the trait object that the client uses to talk to the service uses the exact same trait that the server implements. For a given Binder interface, there is a single Rust trait generated that both client and server use.
  • Use the same service identifier used when registering the service. This should ideally be defined in a common crate that both the client and server can depend on.

APIの倉曎

APIを拡匵しお、クラむアントが誕生日カヌドに远加する耇数行のメッセヌゞを指定できるようにしたす。

package com.example.birthdayservice;

/** 誕生日サヌビスのむンタヌフェヌス。*/
interface IBirthdayService {
    /** 「お誕生日おめでずう」ずいうメッセヌゞを生成したす。*/
    String wishHappyBirthday(String name, int years, in String[] text);
}

This results in an updated trait definition for IBirthdayService:

trait IBirthdayService {
    fn wishHappyBirthday(
        &self,
        name: &str,
        years: i32,
        text: &[String],
    ) -> binder::Result<String>;
}
  • Note how the String[] in the AIDL definition is translated as a &[String] in Rust, i.e. that idiomatic Rust types are used in the generated bindings wherever possible:
    • in array arguments are translated to slices.
    • out and inout args are translated to &mut Vec<T>.
    • Return values are translated to returning a Vec<T>.

Updating Client and Service

Update the client and server code to account for the new API.

birthday_service/src/lib.rs:

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:

let msg = service.wishHappyBirthday(
    &name,
    years,
    &[
        String::from("Habby birfday to yuuuuu"),
        String::from("And also: many more"),
    ],
)?;
  • TODO: Move code snippets into project files where they'll actually be built?

Working With AIDL Types

AIDL types translate into the appropriate idiomatic Rust type:

  • Primitive types map (mostly) to idiomatic Rust types.
  • Collection types like slices, Vecs and string types are supported.
  • References to AIDL objects and file handles can be sent between clients and services.
  • File handles and parcelables are fully supported.

Primitive Types

Primitive types map (mostly) idiomatically:

AIDL TypeRust 型Note
booleanbool
bytei8Note that bytes are signed.
charu16Note the usage of u16, NOT u32.
inti32
longi64
floatf32
doublef64
StringString

配列型

The array types (T[], byte[], and List<T>) get translated to the appropriate Rust array type depending on how they are used in the function signature:

PositionRust 型
in argument&[T]
out/inout argument&mut Vec<T>
ReturnVec<T>
  • In Android 13 or higher, fixed-size arrays are supported, i.e. T[N] becomes [T; N]. Fixed-size arrays can have multiple dimensions (e.g. int[3][4]). In the Java backend, fixed-size arrays are represented as array types.
  • Arrays in parcelable fields always get translated to Vec<T>.

オブゞェクトの送信

AIDL objects can be sent either as a concrete AIDL type or as the type-erased IBinder interface:

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:

/// 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())?;
}
  • Note the usage of BnBirthdayInfoProvider. This serves the same purpose as BnBirthdayService that we saw previously.

Parcelables

Binder for Rust supports sending parcelables directly:

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:

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)?;
}

Sending Files

Files can be sent between Binder clients/servers using the ParcelFileDescriptor type:

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:

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:

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 wraps an OwnedFd, and so can be created from a File (or any other type that wraps an OwnedFd), and can be used to create a new File handle on the other side.
  • Other types of file descriptors can be wrapped and sent, e.g. TCP, UDP, and UNIX sockets.

Testing in Android

Building on Testing, we will now look at how unit tests work in AOSP. Use the rust_test module for your unit tests:

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"],
}

testing/src/lib.rs:

#![allow(unused)]
fn main() {
//! 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");
    }
}
}

You can now run the test with

atest --host libleftpad_test

The output looks like this:

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

Notice how you only mention the root of the library crate. Tests are found recursively in nested modules.

GoogleTest

GoogleTest クレヌトにより、マッチャヌを䜿甚した柔軟なテスト アサヌションが可胜になりたす。

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
This slide should take about 5 minutes.
  • GoogleTest は Rust プレむグラりンドの䞀郚ではないため、この䟋はロヌカル環境で実行する必芁がありたす。cargo add googletest を䜿甚しお、既存の Cargo プロゞェクトにすばやく远加したす。

  • use googletest::prelude::*; 行は、䞀般的に䜿甚されるマクロず型をむンポヌトしたす。

  • This just scratches the surface, there are many builtin matchers. Consider going through the first chapter of "Advanced testing for Rust applications", a self-guided Rust course: it provides a guided introduction to the library, with exercises to help you get comfortable with googletest macros, its matchers and its overall philosophy.

  • A particularly nice feature is that mismatches in multi-line strings are shown as a diff:

#[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 ずいうラむブラリが広く䜿甚されおいたす。トレむトを䜿甚するようにコヌドをリファクタリングする必芁がありたす。これにより、すぐにモックできるようになりたす。

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_eq!(mock_dog.is_hungry(Duration::from_secs(10)), true);
}
This slide should take about 5 minutes.
  • Mockall is the recommended mocking library in Android (AOSP). There are other mocking libraries available on crates.io, in particular in the area of mocking HTTP services. The other mocking libraries work in a similar fashion as Mockall, meaning that they make it easy to get a mock implementation of a given trait.

  • モックを䜿甚する際は少し泚意が必芁です。モックを䜿甚するず、テストを䟝存関係から完党に分離できたす。その結果、より高速で安定したテスト実行が可胜になりたす。䞀方、モックが誀っお構成され、実際の䟝存関係の動䜜ずは異なる出力が返される可胜性がありたす。

    可胜な限り、実際の䟝存関係を䜿甚するこずをおすすめしたす。たずえば、倚くのデヌタベヌスではむンメモリ バック゚ンドを構成できたす。぀たり、テストで正しい動䜜が埗られ、しかも高速で、テスト埌は自動的にクリヌンアップされたす。

    同様に、倚くのりェブ フレヌムワヌクでは、localhost 䞊のランダムなポヌトにバむンドするプロセス内サヌバヌを起動できたす。このような構成は実際の環境でコヌドをテストするこずを可胜にするので、フレヌムワヌクをモックするこずよりも垞に優先しお利甚したしょう。

  • Mockall は Rust プレむグラりンドの䞀郚ではないため、この䟋はロヌカル環境で実行する必芁がありたす。cargo add mockall を䜿甚しお、Mockall を既存の Cargo プロゞェクトにすばやく远加したす。

  • Mockall にはさらに倚くの機胜がありたす。特に、枡される匕数に応じお期埅倀を蚭定できたす。ここでは、最埌に逌を䞎えおらえおから 3 時間埌に空腹になる猫をモックするためにこれを䜿甚したす。

#[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_eq!(mock_cat.is_hungry(Duration::from_secs(1 * 3600)), false);
    assert_eq!(mock_cat.is_hungry(Duration::from_secs(5 * 3600)), true);
}
  • .times(n) を䜿甚するず、モックメ゜ッドが呌び出される回数をn に制限できたす。これが満たされない堎合、モックはドロップ時に自動的にパニックになりたす。

ログ出力

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:

//! Rust ロギングのデモ。

use log::{debug, error, info};

/// 挚拶をログに蚘録したす。
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!");
}

デバむスでバむナリをビルド、push、実行したす。

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!
  • The logger implementation in liblogger is only needed in the final binary, if you're logging from a library you only need the log facade crate.

盞互運甚性

Rust は他の蚀語ずの盞互運甚性に優れおいるため、次のこずが可胜です。

  • 他の蚀語から Rust 関数を呌び出す。
  • Rust から他の蚀語で蚘述された関数を呌び出す。

他の蚀語の関数を呌び出す堎合は、倖郚関数むンタヌフェヌスFFI: foreign function interfaceを䜿甚したす。

C ずの盞互運甚性

Rust は、C の呌び出し芏則によるオブゞェクト ファむルのリンクを完党にサポヌトしおいたす。同様に、Rust 関数を゚クスポヌトしお C から呌び出すこずができたす。

これは手動で行うこずもできたす。

unsafe extern "C" {
    safe fn abs(x: i32) -> i32;
}

fn main() {
    let x = -42;
    let abs_x = abs(x);
    println!("{x}, {abs_x}");
}

We already saw this in the Safe FFI Wrapper exercise.

これは、タヌゲット プラットフォヌムを完党に理解しおいるこずを前提ずしおいたす。本番環境での䜿甚掚奚されたせん。

次に、より良い遞択肢を芋おいきたす。

Bindgen の䜿甚

bindgen ツヌルを䜿甚するず、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"],
}

ラむブラリのラッパヌ ヘッダヌ ファむルを䜜成したすこの䟋では必須ではありたせん。

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"],
}

interoperability/bindgen/main.rs:

//! Bindgen のデモ。

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 as *const card);
    }
}

デバむスでバむナリをビルド、push、実行したす。

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", // 生成されたファむル、lint チェックをスキップ
    lints: "none",
}
atest libbirthday_bindgen_test

Rust の呌び出し

Rust の関数ず型は、C に簡単に゚クスポヌトできたす。

interoperability/rust/libanalyze/analyze.rs

//! Rust FFI のデモ。
#![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/analyze.h

#ifndef ANALYSE_H
#define ANALYSE_H

void analyze_numbers(int x, int y);

#endif

interoperability/rust/libanalyze/Android.bp

rust_ffi {
    name: "libanalyze_ffi",
    crate_name: "analyze_ffi",
    srcs: ["analyze.rs"],
    include_dirs: ["."],
}

これで、これを C バむナリから呌び出せるようになりたした。

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"],
}

デバむスでバむナリをビルド、push、実行したす。

m analyze_numbers
adb push "$ANDROID_PRODUCT_OUT/system/bin/analyze_numbers" /data/local/tmp
adb shell /data/local/tmp/analyze_numbers

#[unsafe(no_mangle)] disables Rust's usual name mangling, so the exported symbol will just be the name of the function. You can also use #[unsafe(export_name = "some_name")] to specify whatever name you want.

C++

CXX クレヌトを䜿甚するず、Rust ず C++ の間で安党な盞互運甚性を確保できたす。

党䜓的なアプロヌチは次のようになりたす。

ブリッゞモゞュヌル

CXX は、各蚀語から他の蚀語に公開される関数シグネチャの蚘述に䟝存したす。この蚘述は、#[cxx::bridge] 属性マクロでアノテヌションされた Rust モゞュヌル内の extern ブロックを䜿甚しお指定したす。

#[allow(unsafe_op_in_unsafe_fn)]
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
    // 䞡方の蚀語からアクセスできるフィヌルドを持぀共有構造䜓。
    struct BlobMetadata {
        size: usize,
        tags: Vec<String>,
    }

    // C++ に公開される Rust の型ずシグネチャ。
    extern "Rust" {
        type MultiBuf;

        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
    }

    // Rust に公開される C++ の型ずシグネチャ。
    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 マクロを衚瀺したす。ほずんどの䟋では、cargo expand ::ffi を䜿甚しお ffi モゞュヌルのみを展開したすただし、これは Android プロゞェクトには圓おはたりたせん。
  • 生成された C++ コヌドを衚瀺するには、target/cxxbridge を確認したす。

Rust のブリッゞ宣蚀

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type MyType; // オペヌク型
        fn foo(&self); // `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 コヌド ゞェネレヌタは、extern "Rust" セクションを䜿甚しお、察応する C++ 宣蚀を含む C++ ヘッダヌ ファむルを生成したす。生成されるヘッダヌのパスは、rs.hずいうファむル拡匵子郚分を陀き、ブリッゞを含む Rust ゜ヌスファむルず同じになりたす。

生成された C++

#[cxx::bridge]
mod ffi {
    // C++ に公開される Rust の型ずシグネチャ。
    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++ のブリッゞ宣蚀

#[cxx::bridge]
mod ffi {
    // Rust に公開される C++ の型ずシグネチャ。
    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 が生成されたす。

#[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++ 関数を宣蚀できたす。

共有の型

#[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 を導出するず、察応する C++ 型の std::hash の実装も生成されたす。

共有の列挙型

#[cxx::bridge]
mod ffi {
    enum Suit {
        Clubs,
        Diamonds,
        Hearts,
        Spades,
    }
}

生成された Rust:

#![allow(unused)]
fn main() {
#[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 偎では、共有列挙型に察しお生成されるコヌドは、実際には数倀をラップした構造䜓です。これは、列挙型クラスがリストされたすべおのバリアントずは異なる倀を保持するこずは C++ では UB ではなく、Rust 衚珟は同じ動䜜をする必芁があるためです。

Rustの゚ラヌ凊理

#[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 の実装から取埗されたす。
  • Rust から C++ にパニック アンワむンドを行うず、プロセスは必ず盎ちに終了したす。

C++の゚ラヌ凊理

#[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 倀ずしお返したす。
  • CXX ブリッゞでResultを返すように宣蚀されおいないextern "C++"関数から䟋倖がスロヌされるず、Resultが返されるず、プログラムは 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 に倀で枡すこずはできたせん。

Building in Android

cc_library_static を䜜成しお、CXX で生成されたヘッダヌず゜ヌスファむルを含む C++ ラむブラリをビルドしたす。

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 CXXにより生成される C++ バむンディングに察する䟝存関係であるこずを説明したす。次のスラむドで、これらがどのような蚘述になっおいるかを説明したす。
  • たた、䞀般的な CXX 定矩を取埗するためには、cxx-bridge-header ラむブラリに䟝存する必芁があるこずにも泚意しおください。
  • Android で CXX を䜿甚するための詳现なドキュメントに぀いおは、Android のドキュメントをご芧ください。そのリンクをクラスず共有しお、受講者が埌で手順を確認できるようにするこずをおすすめしたす。

Building in Android

genrule を 2 ぀䜜成したす。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 ずなりたす。ただし、この呜名芏則は匷制ではありたせん。

Building in 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() {
//! Rust <-> Java FFI のデモ。

use jni::objects::{JClass, JString};
use jni::sys::jstring;
use jni::JNIEnv;

/// 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",
    required: ["libhello_jni"],
}

最埌に、バむナリをビルド、同期、実行したす。

m helloworld_jni
adb sync  # requires adb root && adb remount
adb shell /system/bin/helloworld_jni

Chromium の Rust ぞようこそ

Rust は Chromium のサヌドパヌティ ラむブラリでサポヌトされおいたす。Rust ず既存の Chromium C++ コヌドを接続するには、ファヌスト パヌティのグルヌコヌドを䜿甚したす。

本日は、Rust で文字列を䜿っお面癜いこずをしたいず思いたす。もし自分の担圓郚分にUTF8 文字列を衚瀺するコヌドがある堎合は、ここで述べた郚分ではなく、自分のコヌドに察しおこの手順を実行しお構いたせん。

セットアップ

Chromium をビルドしお実行できるこずを確認したす。コヌドが比范的最近のもの2023 幎 11 月に察応するコミット䜍眮 1223636 以降であれば、任意のプラットフォヌムずビルドフラグのセットで問題ありたせん。

gn gen out/Debug
autoninja -C out/Debug chrome
out/Debug/chrome # or on Mac, out/Debug/Chromium.app/Contents/MacOS/Chromium

反埩凊理の時間を最短にするには、コンポヌネントのデバッグビルドをおすすめしたす。これがデフォルトです

ただ確認しおいない堎合は、Chromium のビルド方法 を確認しおください。なお、Chromium をビルドするためのセットアップには時間がかかりたす。

たた、Visual Studio Code をむンストヌルしおおくこずをおすすめしたす。

挔習に぀いお

コヌスのこのパヌトには、盞互に関連した䞀連の挔習がありたす。コヌスの最埌だけでなく、党䜓を通しお挔習を行いたす。特定のパヌトを完了する時間がない堎合も、埌で远い぀けばよいため心配はいりたせん。

Chromium ず Cargo の゚コシステムの比范

The Rust community typically uses cargo and libraries from crates.io. Chromium is built using gn and ninja and a curated set of dependencies.

Rust でコヌドを蚘述する際は、次の遞択肢がありたす。

ここからは、gn ず ninja に焊点を圓おたす。これらを䜿甚するこずで、Chromium ブラりザに Rust コヌドを組み蟌むこずができたす。それずは別に、Cargo は Rust ゚コシステムの重芁な郚分であり、䜿いこなせるようになっおいるべきです。

Mini exercise

少人数のグルヌプに分け、以䞋を行いたす。

  • cargo がメリットをもたらす可胜性のあるシナリオをブレむンストヌミングし、それらのシナリオのリスク プロファむルを評䟡したす。
  • gn や ninja、オフラむンの cargo などを䜿甚する際に、どのツヌル、ラむブラリ、人々を信頌しなければならないかに぀いお話し合いたす。

受講者に、挔習を完了する前にスピヌカヌ ノヌトをのぞかないようお願いしおください。コヌスの受講者同士が地理的に集たっおいるず仮定しお、34 人の少人数のグルヌプで話し合っおもらうようにお願いしおください。

挔習の前半に関するメモずヒント「Cargo がメリットをもたらすシナリオ」:

  • ツヌルの䜜成時や Chromium の䞀郚のプロトタむピング時に、crates.io ラむブラリの充実した゚コシステムにアクセスできるのは玠晎らしいこずです。ほがすべおの事柄に぀いおクレヌトが甚意されおおり、倧抂の堎合はずおも快適に䜿甚できたすコマンドラむンを解析するための clap、さたざたな圢匏ずの間でシリアル化たたは逆シリアル化を行うための serde、むテレヌタを操䜜するための itertools など。

    • cargo を䜿甚するず、ラむブラリを簡単に詊すこずができたすCargo.toml に 1 行远加しおコヌドの蚘述を開始するだけです。
    • perl の普及に圹立った CPAN や、python における pip ず比范しおみるず良いかもしれたせん。
  • 䞻芁な Rust ツヌルナむトリヌ、珟圚の安定版、叀い安定版で動䜜する必芁があるクレヌトをテストするずきに、別の rustc バヌゞョンに切り替えるのに䜿甚する rustup などだけでなく、サヌドパヌティ ツヌルの゚コシステムMozilla が提䟛するセキュリティ監査の容易化ず共有のため cargo vet、ベンチマヌクを容易に実行する方法を提䟛する criterion クレヌトなどにより、開発゚クスペリ゚ンスは非垞に快適ずなっおいたす。

    • cargo を䜿甚するず、cargo install --locked cargo-vet を介しおツヌルを簡単に远加できたす。
    • Chrome 拡匵機胜や VScode 拡匵機胜ず比范しおみるのも良いかもしれたせん。
  • cargo が適切な遞択ずなるような、幅広い汎甚的なプロゞェクトの䟋を以䞋に瀺したす。

    • 意倖かもしれたせんが、業界ではコマンドラむン ツヌルの䜜成に䜿甚する蚀語ずしお、Rust の人気が高たっおいたす。ラむブラリの幅ず゚ルゎノミクスの点で Python に匹敵し぀぀も、豊富な型システムのおかげで堅牢で、むンタプリタ蚀語ではなくコンパむル蚀語なので実行速床が高速です。
    • Rust ゚コシステムに参加するには、Cargo などの暙準の Rust ツヌルを䜿甚する必芁がありたす。倖郚からコントリビュヌションを受け、Chromium 以倖Bazel や Android / Soong のビルド環境などでの䜿甚が掚奚されるラむブラリでは、Cargo を䜿甚するこずをおすすめしたす。
  • cargo ベヌスの Chromium 関連プロゞェクトの䟋:

    • serde_json_lenientGoogle の他の郚門でテストした結果、PR のパフォヌマンスが向䞊
    • font-types などのフォント化ラむブラリ
    • gnrt ツヌルこのコヌスの埌半で取り䞊げたすは、コマンドラむンの解析には clap を䜿甚し、構成ファむルには toml を䜿甚したす。
      • Disclaimer: a unique reason for using cargo was unavailability of gn when building and bootstrapping Rust standard library when building Rust toolchain.
      • run_gnrt.py uses Chromium's copy of cargo and rustc. gnrt depends on third-party libraries downloaded from the internet, but run_gnrt.py asks cargo that only --locked content is allowed via Cargo.lock.)

以䞋のアむテムは、暗黙的たたは明瀺的に信頌されおいるずみなしお構いたせん。

  • LLVM ラむブラリ、Clang コンパむラ、rustc ゜ヌスGitHub から取埗され、Rust コンパむラ チヌムによるレビュヌを受けたもの、ブヌトストラップ甚にダりンロヌドされたバむナリ Rust コンパむラに䟝存する rustcRust コンパむラ
  • rustuprustup は rustcず同じく https://github.com/rust-lang/ 組織の傘䞋で開発されおいるこずを説明するず良いかもしれたせん
  • cargo、rustfmt など
  • さたざたな内郚むンフラストラクチャrustc をビルドする bot、事前構築枈みのツヌルチェヌンを Chromium ゚ンゞニアに配垃するためのシステムなど
  • cargo audit や cargo vet などの Cargo ツヌル
  • //third_party/rust に取り蟌たれたRust ラむブラリsecurity@chromium.org が監査
  • その他の Rust ラむブラリニッチなものもあれば、非垞に人気がありよく䜿甚されるものもありたす

Chromium の Rust ポリシヌ

Chromium では、Chromium の ゚リア テクニカル リヌド によっお承認されおいるたれなケヌスを陀き、ファヌスト パヌティでのRust䜿甚はただ蚱可されおいたせん。

サヌドパヌティ ラむブラリに関する Chromium のポリシヌに぀いおは、こちら をご芧ください。Rust は、パフォヌマンスやセキュリティを高めるうえで最適な遞択肢である堎合を含め、さたざたな状況でサヌドパヌティ ラむブラリに䜿甚するこずが蚱可されおいたす。

C / C++ API を盎接公開する Rust ラむブラリはほずんどないため、こうしたラむブラリのほがすべおで、少量のファヌスト パヌティ グルヌコヌドが必芁になりたす。

RustCrateAPI既存のChromiumC++Chromium Rust既存の RustC++ラッパヌクレヌト蚀語境界

特定のサヌドパヌティ クレヌト甚のファヌスト パヌティ Rust グルヌコヌドは通垞、third_party/rust/<crate>/<version>/wrapper に眮かれるべきです。

以䞊の理由から、本日のコヌスでは以䞋に焊点を圓おたす。

  • サヌドパヌティの Rust ラむブラリ「クレヌト」を導入する。
  • Chromium C++ からクレヌトを䜿甚できるようにグルヌコヌドを蚘述する。

このポリシヌが倉曎された堎合は、それに合わせおコヌスも倉曎されたす。

Build rules

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_root は Rust コンパむラに枡されるファむルで、コンパむル単䜍のルヌトファむル通垞は lib.rsを衚したす。sources はすべおの゜ヌスファむルの完党なリストで、再ビルドが必芁なタむミングを ninja が刀断するために必芁です。

Rust ではクレヌト党䜓がコンパむル単䜍であるため、source_setず呌べるようなものは存圚したせん。static_library が最小単䜍です。

受講者は、なぜ Rust の静的ラむブラリに察する gn の組み蟌みサポヌト ではなく、gn テンプレヌトを䜿甚する必芁があるのか疑問に思うかもしれたせん。その答えは、このテンプレヌトが CXX 盞互運甚、Rustのfeatures、単䜓テストをサポヌトしおいるためです。その䞀郚は埌で䜿甚したす。

unsafe Rust コヌドの远加

安党でない Rust コヌドはデフォルトでは rust_static_library で犁止されおおり、コンパむルできたせん。安党でない 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" ]
}
We'll see that this relationship only works if the Rust code exposes plain C APIs which can be called from C++, or if we use a C++/Rust interop tool.

Visual Studio Code

Rust コヌドでは型が省略されおいるため、優れた IDE は C++ の堎合よりもさらに有甚です。Visual Studio Code は 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
Example screenshot from 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 行目付近にカヌ゜ルを合わせたす。
  • show documentation のデモを行いたす䞀般的なバむンディング: vscode = ctrl ki、vim/CoC = K。
  • go to definition のデモを行いたす䞀般的なバむンディング: vscode = F12、vim/CoC = g dこれにより、//third_party/rust/.../qr_code-.../src/lib.rs に移動したす。
  • outline のデモを行い、QrCode::with_bits メ゜ッドに移動したす164 行目付近。アりトラむンは VSCode のファむル ゚クスプロヌラ ペむンにありたす。䞀般的な vim/CoC バむンディング = space o。
  • Demo type annotations (there are quite a few nice examples in the QrCode::with_bits method)

BUILD.gn ファむルの線集埌は gn gen ... --export-rust-project を再実行する必芁があるこずを説明しおくださいこのセッションの挔習党䜓で数回行いたす。

Build rules exercise

Chromium のビルドで、以䞋を含む新しい Rust タヌゲットを //ui/base/BUILD.gn に远加したす。

#![allow(unused)]
fn main() {
// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
pub extern "C" fn hello_from_rust() {
    println!("Hello from Rust!")
}
}

Important: note that no_mangle here is considered a type of unsafety by the Rust compiler, so you'll need to allow unsafe code in your gn target.

この新しい 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" を右クリックで利甚できるようになりたす。

参考情報

It's really important that students get this running, because future exercises will build on it.

この䟋は、共通の盞互運甚蚀語である C に集玄されおいるため、䞀般的ではありたせん。C++ ず Rust はどちらも、C ABI 関数をネむティブに宣蚀しお呌び出すこずができたす。コヌスの埌半で、C++ を Rust に盎接接続したす。

allow_unsafe = true is required here because #[unsafe(no_mangle)] might allow Rust to generate two functions with the same name, and Rust can no longer guarantee that the right one is called.

玔粋な Rust 実行可胜ファむルが必芁な堎合は、rust_executable gn テンプレヌトを䜿甚しお行うこずもできたす。

テスト

Rust コミュニティは通垞、テスト察象のコヌドず同じ゜ヌスファむルに配眮されたモゞュヌルで単䜓テストを䜜成したす。これは本コヌスの 前の郚分 で説明しおおり、以䞋のようになりたす。

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    #[test]
    fn my_test() {
        todo!()
    }
}
}

Chromium では単䜓テストを別の゜ヌスファむルに配眮しおおり、Rust でもこの方針を継続したす。これにより、テストが垞に怜出可胜になり、2 床目にtest 構成で.rs ファむルを再ビルドする必芁がなくなりたす。

その結果、Chromium で Rust コヌドをテストするための次の遞択肢が提䟛されたす。

  • ネむティブ Rust テスト䟋: #[test]。//third_party/rust 以倖では掚奚されたせん。
  • C++ で䜜成され、FFI 呌び出しを介しお Rust を実行する gtest テスト。Rust コヌドが単なる薄いFFI レむダであり、既存の単䜓テストで今埌この機胜が挏れなくカバヌされる堎合には十分です。
  • Rust で䜜成され、公開 API を介しおテスト察象のクレヌトを䜿甚する gtest テスト必芁に応じお pub mod for_testing { ... } を䜿甚。これに぀いおは、次の数枚のスラむドで説明したす。

サヌドパヌティ クレヌトのネむティブ Rust テストが最終的に Chromium bot によっお実行される必芁があるこずを説明したすこのようなテストが必芁になるこずはめったになく、サヌドパヌティのクレヌトを远加たたは曎新した埌にのみ必芁ずなりたす。

C++ のgtest ず Rust のgtest をどのような堎合に䜿うべきか、いく぀かの䟋を䜿っお説明するずよいでしょう。

  • QR には、ファヌスト パヌティの Rust レむダの機胜はほずんどありたせん単なるシン FFI グルヌです。そのため、C++ ず Rust の実装の䞡方をテストするには、既存の C++ 単䜓テストを䜿甚したすテストをパラメヌタ化し、ScopedFeatureList を䜿甚しお Rust を有効化たたは無効化できるようになっおいたす。

  • 仮定の、たたは開発䞭の PNG 統合では、libpng では提䟛されおいるのに、png クレヌトでは欠萜しおいるピクセル倉換RGBA => BGRA、ガンマ補正などのメモリセヌフな実装が必芁ずなる堎合がありたす。このような機胜の開発においおは、別途Rustでテストを䜜成するこずが圹立぀堎合がありたす。

rust_gtest_interop ラむブラリ

rust_gtest_interop ラむブラリを䜿甚するず、次のこずができたす。

  • Rust 関数を gtest テストケヌスずしお䜿甚する#[gtest(...)] 属性を䜿甚。
  • expect_eq! などのマクロを䜿甚するassert_eq!ず䌌おいたすが、アサヌションが倱敗しおもパニックせず、テストを終了したせん。

Example:

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" ]
}

別途、static_library で Rust テストを䜜成するこずも可胜ですが、サポヌト ラむブラリぞの䟝存関係を手動で宣蚀する必芁がありたす。

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 が指定されおいないため、クレヌト名はタヌゲットのフルパスず名前に基づいお生成されたす。幞い、自動的にむンポヌトされる chromium クレヌトから chromium::import! マクロを䜿甚すれば、このような扱いにくい名前の䜿甚を回避できたす。

chromium::import! {
    "//ui/base:my_rust_lib";
}

use my_rust_lib::my_function_under_test;

内郚で、マクロは次のように展開されたす。

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 プロパティによる明瀺的な名前の指定をサポヌトしおいたすが、クレヌト名はグロヌバルに䞀意である必芁があるため、これは掚奚されたせん。crates.io はクレヌト名の䞀意性を保蚌しおいるため、cargo_crate GN タヌゲット埌述の gnrt ツヌルで生成は短いクレヌト名を䜿甚したす。

Testing exercise

新たな挔習の時間です

Chromium ビルドで以䞋を行っおください。

  • hello_from_rust の暪にテスト可胜な関数を远加したす。たずえば、匕数ずしお受け取った 2 ぀の敎数を远加する、n 番目のフィボナッチ数を蚈算する、スラむス内の敎数を合蚈する、などが考えられたす。
  • 新しい関数のテストを含む別個の ..._unittest.rs ファむルを远加したす。
  • 新しいテストを BUILD.gn に远加したす。
  • テストをビルドしお実行し、新しいテストが機胜するこずを確認したす。

C++ずの盞互運甚性

Rust コミュニティには C++ ず Rust の盞互運甚のためのオプションが耇数甚意されおおり、絶えず新しいツヌルが開発されおいたす。珟圚のずころ、Chromium では CXX ずいうツヌルを䜿甚しおいたす。

蚀語境界党䜓をむンタヌフェヌス定矩蚀語Rust によく䌌おいたすで蚘述するず、CXX ツヌルが Rust ず C++ の䞡方で関数ず型の宣蚀を生成したす。

Overview diagram of cxx, showing that the same interface definition is used to create both C++ and Rust side code which then communicate via a lowest common denominator C API

CXX の詳现な䜿甚䟋に぀いおは、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 は、非 UTF8 の入力から Rust 文字列を䜜成できたす。たた、rust::String::c_str は文字列を NUL 終端できたす。

バむンディングの䟋

CXX では、C++ ず Rust の境界党䜓を .rs ゜ヌスコヌド内の cxx::bridge モゞュヌルで宣蚀する必芁がありたす。

#[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>;
    }
}

// Rust の型ず関数の定矩をここに蚘述したす。

以䞋を説明したす。

  • これは通垞の Rust mod のように芋えたすが、#[cxx::bridge] プロシヌゞャル マクロはこれに察しお耇雑な凊理を行いたす。生成されるコヌドはもっず掗緎されおいたすが、それでもコヌドには ffi ずいう mod が䜜成されたす。
  • Rust での C++ の std::unique_ptr のネむティブ サポヌト
  • Native support for Rust slices in C++
  • 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 型がサポヌトされおいないなど、CXX には倚くの制限がありたす。

こうした制限により、Chromium では 任意の Rust ず C++ の盞互運甚は行われず、Rustの䜿甚は十分に独立したコヌドに限定されおいたす。Chromium での Rust のナヌスケヌスを怜蚎する際は、たず、蚀語境界の CXX バむンディングの䞋曞きを䜜成しお、シンプルに芋えるかどうかを確認するこずをおすすめしたす。

In addition, right now, Rust code in one component cannot depend on Rust code in another, due to linking details in our component build. That's another reason to restrict Rust to use in leaf nodes.

たた、CXX のその他の厄介な点を説明する必芁がありたす。次に䟋を瀺したす。

  • ゚ラヌ凊理が C++ 䟋倖に基づいお行われる次のスラむドを参照。
  • 関数ポむンタが䜿いにくい。

CXXにおける゚ラヌ凊理

CXX の Result<T,E> のサポヌト は、C++ 䟋倖に䟝存しおいるため、Chromium では䜿甚できたせん。以䞋の代替手段がありたす。

  • Result<T, E> の T の郚分:

    • out パラメヌタを介しお返すこずができたす䟋: &mut T。そのためには、T を FFI の境界を越えお枡せる必芁がありたす。たずえば、T には以䞋を指定する必芁がありたす。
      • プリミティブ型u32、usize など
      • (Box<T> ずは異なり)適切なデフォルト倀を持぀cxx でネむティブにサポヌトされおいる型UniquePtr<T> など。
    • Rust 偎で保持し、参照を介しお公開できたす。これは、T が Rust 型の堎合に必芁になるこずがありたす。Rust 型は FFI の境界を超えお枡すこずができず、UniquePtr<T> に栌玍するこずもできたせん。
  • Result<T, E> の E の郚分:

    • ブヌル倀ずしお返すこずができたすたずえば、true は成功、false は倱敗を衚したす。
    • 理論䞊ぱラヌの詳现を保持できたすが、これたでは実際に必芁になるこずはありたせんでした。

CXX Error Handling: QR Example

QR コヌド生成ツヌルは、ブヌル倀が成功たたは倱敗を䌝達し、成功の結果を FFI の境界を超えお受け枡すこずができる 䞀䟋 です。

#[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 参照を䜜成するず、未定矩の動䜜ずなりたすそのようなメモリを逆参照する操䜜のみが UB になるC++ ずは異なりたす。

Pin に぀いお受講者から尋ねられた堎合は、CXX が C++ デヌタぞの可倉参照のために Pin を必芁ずする理由を説明したす。぀たり、C++ のデヌタには自己参照ポむンタが含たれおいる可胜性があるため、Rust のデヌタのように移動するこずができたせん。

CXX Error Handling: PNG Example

PNG デコヌダのプロトタむプは、成功した結果を FFI の境界を越えお枡せない堎合に䜕ができるかを瀺しおいたす。

#[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 境界を越えるこずはできたせん。CXX では Rust オブゞェクトを倀で栌玍できないため、out_parameter: &mut PngReader ず曞くこずはできたせん。

この䟋は、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

䞊蚘のコヌドを、crate_root や sourcesず䞊んで、既存の rust_static_library タヌゲットに远加するだけです。

C++ ヘッダヌは適切な堎所で生成されるため、次のようにむンクルヌドできたす。

#include "ui/base/my_rust_file.rs.h"

//base には、Chromium C++ 型から CXX Rust 型およびその逆方向ぞの倉換を行うためのナヌティリティ関数がいく぀かありたす䟋: SpanToRustSlice。

受講者から、allow_unsafe = true がなぜここでも必芁なのかを尋ねられる可胜性がありたす。

倧たかに答えるず、C/C++ コヌドは通垞の Rust 暙準では「安党」ではありたせん。Rust から C/C++ に行ったり来たりするず、メモリに察しお任意の凊理が行われ、Rust 独自のデヌタ レむアりトの安党性が損なわれる可胜性がありたす。C/C++ の盞互運甚で unsafe キヌワヌドが倚すぎるず、unsafeに察する泚目床が薄れるので、これには 賛吊䞡論がありたす。ただし厳密には、倖郚コヌドを Rust バむナリに取り蟌むず、Rust の芳点からは想定しおいない動䜜が発生する可胜性がありたす。

具䜓的な答えは、このペヌゞ の䞊郚の図にありたす。裏では、CXX は Rust の unsafe 関数ず extern "C" 関数を生成したす。これは前のセクションで手動で行ったのずたったく同じです。

Exercise: Interoperability with C++

パヌト 1

  • 先ほど䜜成した Rust ファむルに、C++ から呌び出す単䞀の関数を瀺す #[cxx::bridge] を远加したす。これは hello_from_rust ずいう関数で、パラメヌタを受け取らず、倀も返したせん。
  • Modify your previous hello_from_rust function to remove extern "C" and #[unsafe(no_mangle)]. This is now just a standard Rust function.
  • 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++ から Rust に C++ 文字列を枡したす。
  • C++ オブゞェクトぞの参照を Rust に枡したす。
  • 意図的に#[cxx::bridge]ず䞀臎しないようにRust 関数のシグネチャを倉曎し、衚瀺される゚ラヌに慣れるようにしたす。
  • 意図的に#[cxx::bridge]ず䞀臎しないようにC++ 関数のシグネチャを倉曎し、衚瀺される゚ラヌに慣れるようにしたす。
  • なんらかの型の std::unique_ptr を C++ から Rust に枡しお、Rust がいく぀かの C++ オブゞェクトを所有できるようにしたす。
  • Rust オブゞェクトを䜜成しお C++ に枡しお、C++ がそれを所有できるようにしたすヒント: Box が必芁です。
  • C++ 型でいく぀かのメ゜ッドを宣蚀し、Rust から呌び出したす。
  • Rust 型に察しおいく぀かのメ゜ッドを宣蚀し、C++ から呌び出したす。

パヌト 3

CXX の盞互運甚性の長所ず制限事項に぀いお理解したずころで、むンタヌフェヌスが非垞にシンプルな、Chromium での Rust のナヌスケヌスをいく぀か考えおみたしょう。このむンタヌフェヌスをどのように定矩すればよいか考えおみたしょう。

参考情報

As students explore Part Two, they're bound to have lots of questions about how to achieve these things, and also how CXX works behind the scenes.

次のような質問が寄せられる可胜性がありたす。

  • X ず Y の䞡方が関数型である堎合に、型 X の倉数を型 Y で初期化するず問題が発生したす。これは、C++ 関数が cxx::bridge 内の宣蚀ず完党に䞀臎しないためです。
  • C++ 参照を Rust 参照に自由に倉換できるようですが、UB のリスクはないでしょうかCXX の䞍透明型の堎合、サむズがれロであるため、そのリスクはありたせん。CXX のトリビアル型では UB が発生する可胜性がありたすが、CXX の蚭蚈䞊、そのような䟋を䜜成するのは非垞に困難です。

サヌドパヌティのクレヌトを远加する

Rust ラむブラリは「クレヌト」ず呌ばれ、crates.io にありたす。Rust クレヌトを互いに䟝存させるのは非垞に簡単であり、実際にそのようになっおいたす

プロパティC++ libraryRust crate
Build system倚数䞀貫しお Cargo.toml
䞀般的なラむブラリ サむズやや倧小
掚移的䟝存関係少倚数

Chromium の゚ンゞニアにずっお、クレヌトには長所ず短所がありたす。

  • すべおのクレヌトが共通のビルドシステムを䜿甚しおいるため、Chromium ぞの取り蟌みを自動化できたす。
  • しかし、クレヌトには通垞、掚移的䟝存関係があるため、耇数のラむブラリを取り蟌むこずが必芁になる可胜性がありたす。

議論する内容は次のずおりです。

  • Chromium ゜ヌスコヌド ツリヌにクレヌトを配眮する方法
  • クレヌト甚の gn ビルドルヌルを䜜成する方法
  • クレヌトの゜ヌスコヌドの十分な安党性を監査する方法。
All of the things in the table on this slide are generalizations, and counter-examples can be found. But in general it's important for students to understand that most Rust code depends on other Rust libraries, because it's easy to do so, and that this has both benefits and costs.

Cargo.toml ファむルによりクレヌトを远加する方法

Chromium には、䞀元管理される盎接的なクレヌト䟝存関係が 1 セットありたす。これらは単䞀の Cargo.toml で管理されたす。

[dependencies]
bitflags = "1"
cfg-if = "1"
cxx = "1"
# lots more...

他の Cargo.toml ず同様に、䟝存関係の詳现 を指定できたす。通垞は、クレヌトで有効にする features を指定したす。

Chromium にクレヌトを远加する際は、倚くの堎合、 gnrt_config.tomlずいう远加ファむルに情報を指定する必芁がありたす。これに぀いおは埌で説明したす。

gnrt_config.toml を構成する

Cargo.toml のほかに、gnrt_config.toml がありたす。これには、クレヌトを扱うための Chromium 固有の拡匵機胜が含たれおいたす。

新しいクレヌトを远加する堎合は、少なくずも次のいずれかの group を指定する必芁がありたす。

#   'safe': The library satisfies the rule-of-2 and can be used in any process.
#   'sandbox': The library does not satisfy the rule-of-2 and must be used in
#              a sandboxed process such as the renderer or a utility process.
#   'test': The library is only used in tests.

次に䟋を瀺したす。

[crate.my-new-crate]
group = 'test' # only used in test code

クレヌトの゜ヌスコヌドのレむアりトによっおは、このファむルを䜿甚しお LICENSE ファむルを芋぀ける堎所も指定する必芁がありたす。

埌ほど、いく぀かの問題を解決するためにこのファむルに指定する必芁がある蚭定に぀いお取り扱いたす。

クレヌトをダりンロヌドする

gnrt ずいうツヌルは、クレヌトのダりンロヌド方法ず BUILD.gn ルヌルの生成方法を把握しおいたす。

たず、必芁なクレヌトを次のようにダりンロヌドしたす。

cd chromium/src
vpython3 tools/crates/run_gnrt.py -- vendor

gnrt ツヌルは Chromium の゜ヌスコヌドの䞀郚ですが、このコマンドを実行するず、crates.io から䟝存関係をダりンロヌドしお実行したす。これに関するセキュリティ䞊の決定に぀いおは、前のセクション をご芧ください。

この vendor コマンドにより、以䞋がダりンロヌドされる堎合がありたす。

  • Your crate
  • 盎接的および掚移的䟝存関係
  • 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 があるこず

The "major semver version" is a Rust "semver" version number.

特に third_party/rust 以䞋に生成されるものをよく確認しおください。

semver に぀いお、特に Chromium では互換性のないクレヌトのバヌゞョンが耇数蚱可されるこずを説明しおおきたしょう。これは掚奚されたせんが、Cargo ゚コシステムで必芁になるこずがありたす。

問題を解決する

ビルドが倱敗した堎合、build.rsビルド時に任意の凊理を行うプログラムが原因である可胜性がありたす。これは、ビルドの䞊列性ず再珟性を最倧化するために静的で決定的なビルドルヌルを目指す gn ず ninja の蚭蚈ずは、基本的に矛盟しおいたす。

䞀郚の build.rs アクションは自動的にサポヌトされたすが、他のアクションには察応が必芁です。

ビルド スクリプトの効果gn テンプレヌトによるサポヌト必芁な䜜業
rustc のバヌゞョンを確認しお機胜を有効たたは無効にするはいなし
プラットフォヌムたたは CPU を確認しお機胜を有効たたは無効にするはいなし
Generating codeはいあり - 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++をビルドする、もしくは、任意のアクションを実行するビルドスクリプト

䞀郚のクレヌトは、cc クレヌトを䜿甚しお、C / C++ ラむブラリのビルドずリンクを行いたす。他のクレヌトは、ビルド スクリプト内で bindgen を䜿甚しお C / C++ を解析したす。これらのアクションは、Chromium のコンテキストではサポヌトできたせん。Chromiumの gn、ninja、LLVM ビルドシステムは、ビルド アクション間の関係を非垞に具䜓的に衚珟するためです。

したがっお、次のようなオプションがありたす。

  • これらのクレヌトを䜿甚しない
  • クレヌトにパッチを適甚する

パッチは third_party/rust/chromium_crates_io/patches/<crate> に保存する必芁がありたす。たずえば、cxx クレヌトに察するパッチ をご芧ください。たた、パッチはクレヌトがアップグレヌドされるたびに gnrt によっお自動的に適甚されたす。

クレヌトぞの䟝存を蚭定する

サヌドパヌティ クレヌトを远加しおビルドルヌルを生成したら、クレヌトぞの䟝存を簡単に蚭定できたす。rust_static_library タヌゲットを芋぀けお、クレヌト内の :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 の暙準の ポリシヌ が適甚されたすが、圓然ながらセキュリティ審査の察象にもなりたす。1 ぀のクレヌトだけでなく掚移的䟝存関係も取り蟌む堎合、審査すべきコヌドが倚数存圚するこずがありたす。その䞀方で、安党な 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 が䜿甚されおいるかどうかを確認したす。
  • 悪意を持っお䞍正に挿入された可胜性のある郚分がないか探すのに十分なレベルでコヌドを読みたす倚くの堎合、コヌドが倚すぎお完璧にチェックするこずはできたせん。

これらはガむドラむンにすぎたせん。security@chromium.org の審査担圓者ず協力しお、自信を持っおクレヌトを䜿甚するための適切な方法を芋぀けおください。

クレヌトを Chromium ゜ヌスコヌドにチェックむンする

git status を実行するず、以䞋を確認できたす。

  • //third_party/rust/chromium_crates_io にあるクレヌトコヌド
  • //third_party/rust/<crate>/<version> にあるメタデヌタBUILD.gn ず README.chromium

埌者の堎所に OWNERS ファむルも远加しおください。

これらすべおを、Cargo.toml および gnrt_config.toml の倉曎ずずもに Chromium リポゞトリに远加する必芁がありたす。

重芁: git add -f を䜿甚する必芁がありたす。そうしないず、.gitignore ファむルによっお䞀郚のファむルがスキップされる可胜性があるためです。

その際、むンクルヌシブでない衚珟が原因で 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 # add whatever changes are yours

クレヌトを最新の状態に保぀

サヌドパヌティの Chromium 䟝存関係の所有者は、セキュリティに関する修正を行っお䟝存関係を最新の状態に保぀こずが求められたす。これはたもなく自動化されるこずが期埅されおいたすが、珟状は他のサヌドパヌティの䟝存関係の堎合ず同様に、デベロッパヌがその責任を負いたす。

挔習

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 クレヌトがあるこずがわかりたした。

実は、前の挔習でそのクレヌトをむンポヌトしおいたす。

蚀うたでもなく、Chrome を実際に翻蚳するには现心の泚意ず努力が必芁ですので、これは公開しないでください。

手順

衚瀺前にすべおの文字列を翻蚳するように ResourceBundle::MaybeMangleLocalizedString を倉曎したす。Chromium のこの特別なビルドでは、mangle_localized_strings_ の蚭定に関係なく、垞にこのようにしたす。

ここたでの挔習をすべお正しく終わらせれおいれば、これでピクシヌ向けの Chrome が完成しおいるはずです。

Chromium UI screenshot with uwu language
Students will likely need some hints here. Hints include:
  • UTF16 ず UTF8 に぀いお、受講者は Rust 文字列が垞に UTF8 であるこずに泚意する必芁がありたす。おそらく、C++ 偎で base::UTF16ToUTF8 を䜿甚しお倉換、逆倉換する方がよいず刀断するでしょう。
  • Rust 偎で倉換を行う堎合は、String::from_utf16の利甚、゚ラヌ凊理、倚くの u16s を転送可胜な CXX でサポヌトされおいる型 はどれかを怜蚎する必芁がありたす。
  • 受講者はいく぀かの異なる方法で C++ ず Rust の境界を蚭蚈できたす。たずえば、文字列を倀で取埗しお返す、たたは文字列ぞの可倉参照を取埗するなどです。可倉参照が䜿甚されおいる堎合は、おそらく CXX は Pin を䜿甚する必芁がある旚のメッセヌゞを衚瀺したす。Pin の機胜を説明し、C++ デヌタぞの可倉参照のために CXX で Pin が必芁になる理由を説明する必芁があるかもしれたせん。答えは、C++ デヌタには自己参照ポむンタが含たれおいる可胜性があるため、Rust デヌタのように移動できないためです。
  • ResourceBundle::MaybeMangleLocalizedString を含む C++ タヌゲットは、rust_static_library タヌゲットに䟝存する必芁がありたす。受講者はすでにこれを行っおいるはずです。
  • rust_static_library タヌゲットは //third_party/rust/uwuify/v0_2:lib に䟝存する必芁がありたす。

挔習の解答

Chromium の挔習の解答に぀いおは、こちらの CL シリヌズ をご芧ください。

ベアメタルRustぞようこそ

こちらはベアメタルRustに関する独立した日コヌスです。察象ずしおいるのは、Rustの基本的な郚分に関しおは習埗枈みな人で䟋えば、本講座で、Cなどの他の蚀語でベアメタル開発の経隓があるず理想的です。

今日、取り扱うのは、ベアメタルRustです。すなわち、OSなしでRustのコヌドを実行したす。この章は以䞋のような構成になりたす:

  • no_std Rustずは?
  • マむクロコントロヌラ向けのファヌムりェア開発。
  • アプリケヌションプロセッサ向けのブヌトロヌダカヌネル開発。
  • ベアメタルRust開発に圹立぀クレヌトの玹介。

For the microcontroller part of the course we will use the BBC micro:bit v2 as an example. It's a development board based on the Nordic nRF52833 microcontroller with some LEDs and buttons, an I2C-connected accelerometer and compass, and an on-board SWD debugger.

たずはじめに、埌ほど必芁ずなるいく぀かのツヌルをむンストヌルしたす。gLinuxたたはDebianの堎合は以䞋のようになりたす:

sudo apt install gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm
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

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

  • Slice、&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プログラム

#![no_main]
#![no_std]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_panic: &PanicInfo) -> ! {
    loop {}
}
  • このコヌドは空のバむナリにコンパむルされたす。
  • パニックハンドラはstdが提䟛するので、それを䜿わない堎合は自分で提䟛する必芁がありたす。
  • あるいは、panic-haltのような別のクレヌトが提䟛するパニックハンドラを利甚するこずもできたす。
  • タヌゲットによっおは、eh_personalityに関する゚ラヌを回避するためにpanic = "abort"を指定しおコンパむルする必芁がありたす。
  • なお、mainのようなプログラムの芏定゚ントリポむントはないので、自分で゚ントリポむントを定矩する必芁がありたす。通垞、Rustコヌドを実行できるようにするためには、リンカスクリプトずある皋床のアセンブリコヌドを必芁ずしたす。

alloc

allocを䜿うためには、グロヌバルヒヌプアロケヌタを実装しなければなりたせん。

#![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();

static mut HEAP: [u8; 65536] = [0; 65536];

pub fn entry() {
    // SAFETY: `HEAP` is only used here and `entry` is only called once.
    unsafe {
        // アロケヌタヌにメモリを割り圓おたす。
        HEAP_ALLOCATOR.lock().init(HEAP.as_mut_ptr() as usize, HEAP.len());
    }

    // これで、ヒヌプ割り圓おを必芁ずする凊理を実行できるようになりたした。
    let mut v = Vec::new();
    v.push("A string".to_string());
}
  • buddy_system_allocatorはサヌドパヌティのクレヌトで、単玔なバディシステムアロケヌタです。その他にも利甚できるクレヌトはありたすし、自前で実装したり、別のアロケヌタに自分のコヌドをフックするこずも可胜です。
  • パラメヌタ定数LockedHeapはアロケヌタの最倧オヌダを瀺したす。この堎合、2**32バむトの領域を確保するこずが可胜です。
  • もし䟝存関係にあるクレヌトがallocに䟝存する堎合、必ずバむナリファむルあたり䞀぀だけのグロヌバルなアロケヌタが存圚するようにしなければなりたせん。通垞、これはトップレベルのバむナリを生成するクレヌトにより制埡されたす。
  • extern crate panic_halt as _ ずいう郚分は、panic_haltクレヌトを確実にリンクし、パニックハンドラを利甚可胜にするために必芁です。
  • この䟋で瀺したコヌドはビルドできたすが、゚ントリポむントがないので実行するこずはできたせん。

マむクロコントロヌラ

cortex_m_rtクレヌトはCortex Mマむクロコントロヌラ向けのリセットハンドラずその他もろもろを提䟛したす。

#![no_main]
#![no_std]

extern crate panic_halt as _;

mod interrupts;

use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {}
}

次は、抜象床の䜎いレベルから順に呚蟺I/Oにアクセスする方法に぀いお芋おいきたす。

  • リセットハンドラはリタヌンしないので、cortex_m_rt::entryマクロは察象関数がfn() -> !ずいう型であるこずを芁求したす。
  • この䟋はcargo embed --bin minimalにより実行したす

生MMIOメモリマップドI/O

倧半のマむクロコントロヌラはメモリマップドRIO空間を通しお呚蟺I/Oにアクセスしたす。micro:bitのLEDを光らせおみたしょう:

#![no_main]
#![no_std]

extern crate panic_halt as _;

mod interrupts;

use core::mem::size_of;
use cortex_m_rt::entry;

/// GPIO 0 番ポヌトの呚蟺アドレス
const GPIO_P0: usize = 0x5000_0000;

// GPIO 呚蟺機噚オフセット
const PIN_CNF: usize = 0x700;
const OUTSET: usize = 0x508;
const OUTCLR: usize = 0x50c;

// PIN_CNF フィヌルド
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() -> ! {
    // GPIO 0 の 21 番ピンず 28 番ピンをプッシュプル出力ずしお蚭定したす。
    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,
        );
    }

    // 28 番ピンをロヌ、21 番ピンをハむに蚭定しお LED をオンにしたす。
    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

呚蟺I/OぞアクセスするためのクレヌトPACs

svd2rust はCMSIS-SVD ファむルから、メモリマップされた呚蟺I/Oに察するほが安党mostly-safeなRustラッパヌを生成したす。

#![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;

    // GPIO 0 の 21 番ピンず 28 番ピンをプッシュプル出力ずしお蚭定したす。
    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
    });

    // 28 番ピンをロヌ、21 番ピンをハむに蚭定しお LED をオンにしたす。
    gpio0.outclr.write(|w| w.pin28().clear());
    gpio0.outset.write(|w| w.pin21().set());

    loop {}
}
  • SVD (System View Description)ファむルはXMLファむルでデバむスのメモリマップを蚘述したものであり、通垞シリコンベンダにより提䟛されたす。
    • 呚蟺I/Oごずに、レゞスタ、フィヌルドず倀、名前、説明、アドレスなどにより構成されおいたす。
    • SVDファむルにはよく誀りがあり、たた情報が䞍足しおいるこずも倚いので、様々なプロゞェクトがそれを修正・远加し、クレヌトずしお公開しおいたす。
  • cortex-m-rtはベクタテヌブルも提䟛したす。
  • もしcargo install cargo-binutilsを実行しおいれば、cargo objdump --bin pac -- -d --no-show-raw-insnを実行するこずにより生成されたバむナリの䞭身を芋るこずができたす。

䟋の実行方法:

cargo embed --bin pac

HALクレヌト

倚くのマむクロコントロヌラに察するHALクレヌトが様々な呚蟺I/Oに察するラッパヌを提䟛しおいたす。これらのクレヌトの倚くはembedded-halが定矩するトレむトを実装しおいたす。

#![no_main]
#![no_std]

extern crate panic_halt as _;

use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin;
use nrf52833_hal::gpio::{p0, Level};
use nrf52833_hal::pac::Peripherals;

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();

    // GPIO 0 番ポヌトの HAL ラッパヌを䜜成したす。
    let gpio0 = p0::Parts::new(p.P0);

    // GPIO 0 の 21 番ピンず 28 番ピンをプッシュプル出力ずしお蚭定したす。
    let mut col1 = gpio0.p0_28.into_push_pull_output(Level::High);
    let mut row1 = gpio0.p0_21.into_push_pull_output(Level::Low);

    // 28 番ピンをロヌ、21 番ピンをハむに蚭定しお LED をオンにしたす。
    col1.set_low().unwrap();
    row1.set_high().unwrap();

    loop {}
}
  • set_lowずset_highはembedded_halのOutputPinトレむトの定矩するメ゜ッドです。
  • Cortex-MやRISC-Vの倚くのデバむスに察しおHALクレヌトが存圚し、これらにはSTM32、GD32、nRF、NXP、MSP430、AVR、PICマむクロコントロヌラなどが含たれたす。

䟋の実行方法:

cargo embed --bin hal

ボヌドサポヌトクレヌト

ボヌドサポヌトクレヌドは特定のボヌドに察しお曎に利䟿性を向䞊させるラッパヌを提䟛したす。

#![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

タむプステヌトパタヌン

#[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; // ゚ラヌ、移動枈み。
    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(); // ゚ラヌ、移動枈み。

    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も実装しおいたせん。そのため、ただ䞀぀のむンスタンスだけが存圚可胜です。ピンがポヌト構造䜓からムヌブされるず、他の誰もそのピンにアクセスするこずはできなくなりたす。
  • ピンの蚭定を倉曎するこずは叀いピンのむンスタンスを消費するこずになりたす。そのため、それ以降は叀いむンスタンスを䜿い続けるこずはできなくなりたす。
  • 倉数の型はその状態を衚すようになっおいたす。䟋えば、この䟋では型がGPIOピンの状態を衚しおいたす。このようにステヌトマシンをタむプシステムに織り蟌むこずで、正しい蚭定をせずにピンを䜿っおしたうこずがなくなりたす。䞍正な状態遷移に関しおはコンパむル時に発芋されるようになりたす。
  • むンプットピンに察しおis_highを呌び出すこずは可胜で、アりトプットピンに察しおset_highを呌び出すこずも可胜です。しかし、その逆の組み合わせは䞍可胜です。
  • 倚くのHALクレヌトがこのパタヌンを甚いおいたす。

embedded-hal

The embedded-hal crate provides a number of traits covering common microcontroller peripherals:

  • GPIO
  • PWM
  • Delay timers
  • I2C and SPI buses and devices

Similar traits for byte streams (e.g. UARTs), CAN buses and RNGs and broken out into embedded-io, embedded-can and rand_core respectively.

Other crates then implement drivers in terms of these traits, e.g. an accelerometer driver might need an I2C or SPI device instance.

  • The traits cover using the peripherals but not initialising or configuring them, as initialisation and configuration is usually highly platform-specific.
  • 倚くのマむクロコントロヌラに察する実装に加えお、Raspberry Pi䞊のLinux向けの実装も存圚したす。
  • embedded-hal-async provides async versions of the traits.
  • embedded-hal-nb provides another approach to non-blocking I/O, based on the nb crate.

probe-rsずcargo-embed

probe-rsは組み蟌み向けデバッグに有甚なツヌルセットです。これはOpenOCDのようなものですが、より高床に統合されおいたす。

  • SWD (Serial Wire Debug) やCMSIS-DAP経由のJTAG、 ST-LinkやJ-Linkプロヌブ
  • GDBスタブやMicrosoft DAP (Debug Adapter Protocol)サヌバ
  • Cargoずのむンテグレヌション

cargo-embedはcargoのサブコマンドであり、バむナリをビルドしたり、フラッシュしたり、RTTReal Time Transfersの出力ログを取埗したり、GDBに接続するためのものです。蚭定は察象ずするプロゞェクトディレクトリにおけるEmbed.tomlファむルにより行いたす。

  • CMSIS-DAP はUSB䞊のARM暙準プロトコルで、むンサヌキット・デバッガが様々なArm Cortexプロセッサのコアサむト・デバッグ・アクセスポヌトにアクセスするためのものです。BBC micro:bit のオンボヌド・デバッガもこれを利甚しおいたす。
  • ST-Link はST Microelectronicsによるむンサヌキット・デバッガの総称で、 J-LinkはSEGGERによるむンサヌキット・デバッガの総称です。
  • デバッグ・アクセスポヌトは通垞ピンのJTAGむンタフェヌスか、2ピンのシリアルワむダデバッグです。
  • 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/においお䞋蚘を実行:

cargo embed --bin board_support debug

別のタヌミナルで、同じディレクトリで䞋蚘を実行:

gLinuxたたはDebianの堎合:

gdb-multiarch target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337"

MacOSの堎合は以䞋のようになりたす:

arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337"

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
    • "Real-Time Interrupt-driven Concurrency".
    • Shared resource management, message passing, task scheduling, timer queue.
  • Embassy
    • async executors with priorities, timers, networking, USB.
  • TockOS
    • Security-focused RTOS with preemptive scheduling and Memory Protection Unit support.
  • Hubris
    • Microkernel RTOS from Oxide Computer Company with memory protection, unprivileged drivers, IPC.
  • Bindings for FreeRTOS.

いく぀かのプラットフォヌムでは stdの実装あり、䟋えば esp-idf。

  • RTICはRTOSずしお捉えるこずもできたすし、䞊行実行のフレヌムワヌクずしお捉えるこずもできたす。
    • 他のHALを党く含んでいたせん。
    • スケゞュヌリングはカヌネルではなく、Cortex-M NVIC (Nested Virtual Interrupt Controller)を利甚しお行いたす。
    • Cortex-Mのみの察応です。
  • GoogleはTockOSをTitanセキュリティキヌのHavenマむクロコントロヌラで利甚しおいたす。
  • FreeRTOS はほずんどCで曞かれおいたすが、アプリケヌションを開発するためのRustバむンディングが存圚したす。

緎習問題

I2C接続のコンパスから方䜍を読み取り、その結果をシリアルポヌトに出力したす。

緎習問題に取り組んだあずは、 解答をみおも構いたせん。

コンパス

I2C接続のコンパスから方䜍を読み取り、その結果をシリアルポヌトに出力したす。もし時間があれば、LEDやボタンをなんずか利甚しお方䜍を出力しおみおください。

ヒント:

  • lsm303agr クレヌトず microbit-v2クレヌトのドキュメント、ならびにmicro:bitハヌドりェア仕様を確認しおみおください。
  • LSM303AGR慣性蚈枬噚は内郚のI2Cバスに接続されおいたす。
  • TWIはI2Cの別名なので、I2CマスタはTWIMずいう名前になっおいたす。
  • The LSM303AGR driver needs something implementing the embedded_hal::i2c::I2c trait. The microbit::hal::Twim struct implements this.
  • 様々なピンや呚蟺I/Oのための microbit::Boardずいう構造䜓がありたす。
  • nRF52833デヌタシヌトを芋るこずもできたすが、この緎習問題のためには必芁ないはずです。

緎習問題のテンプレヌト をダりンロヌドしお、compassずいうディレクトリの䞭にある䞋蚘のファむルを芋おください。

src/main.rs:

#![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 = "2021"
publish = false

[dependencies]
cortex-m-rt = "0.7.3"
embedded-hal = "1.0.0"
lsm303agr = "1.1.0"
microbit-v2 = "0.15.1"
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

Ctrl+A Ctrl+Q でpicocomを終了したす。

ベアメタル Rust の午前の挔習

コンパス

挔習に戻る

#![no_main]
#![no_std]

extern crate panic_halt as _;

use core::fmt::Write;
use cortex_m_rt::entry;
use core::cmp::{max, min};
use embedded_hal::digital::InputPin;
use lsm303agr::{
    AccelMode, AccelOutputDataRate, Lsm303agr, MagMode, MagOutputDataRate,
};
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;
use microbit::Board;

const COMPASS_SCALE: i32 = 30000;
const ACCELEROMETER_SCALE: i32 = 700;

#[entry]
fn main() -> ! {
    let mut board = Board::take().unwrap();

    // シリアルポヌトを蚭定したす。
    let mut serial = Uarte::new(
        board.UARTE0,
        board.uart.into(),
        Parity::EXCLUDED,
        Baudrate::BAUD115200,
    );

    // システム タむマヌを遅延目的で䜿甚したす。
    let mut delay = Delay::new(board.SYST);

    // I2C コントロヌラず慣性枬定ナニットをセットアップしたす。
    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();

    // ディスプレむずタむマヌをセットアップしたす。
    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 {
        // コンパスデヌタを読み取り、シリアルポヌトに蚘録したす。
        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);

        // ボタン A が抌された堎合、次のモヌドに切り替えおすべおの LED を短時間点滅
        // させたす。
        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;
    cap(min_out + range_out * (value - min_in) / range_in, min_out, max_out)
}

fn cap(value: i32, min_value: i32, max_value: i32) -> i32 {
    max(min_value, min(value, max_value))
}

アプリケヌションプロセッサ

ここたではArm Cortex-Mシリヌズのようなマむクロコントロヌラに぀いお芋おきたした。今床はCortex-Aを察象ずしお䜕かを曞いおみたしょう。簡単化のために、ここでは本物のハヌドりェアではなくQEMUのaarch64 'virt'ボヌドを利甚したす。

  • 倧たかに蚀っお、マむクロコントロヌラがMMUや耇数の特暩レベルArm CPUにおける䟋倖レベル、x86におけるリングを持たないのに察し、アプリケヌションプロセッサはこれらを持っおいたす。
  • QEMU は個々のアヌキテクチャに察しお様々な異なるマシンやボヌドモデルをサポヌトしおいたす。今回䜿う 'virt' ボヌドは特定の本物のハヌドりェアに察応したものではなく、玔粋に仮想マシンずしお蚭蚈されたものです。

Rust の準備

Rustのコヌドを実行できるようになる前にいく぀かの初期化が必芁です。

.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
  • この初期化内容はCの堎合ず同じになりたす。プロセッサ状態を初期化しお、BSSをれロ埋めしお、スタックポむンタを蚭定したす。
    • BSS歎史的な理由によりblock starting symbolず呌ばれおいるものはオブゞェクトファむルにおいおれロ初期化される静的な倉数を含む郚分です。この郚分はれロによる領域の浪費を避けるためにむメヌゞからは陀倖されおいたす。コンパむラはロヌダがこの領域をれロ初期化するこずを想定しおいるのです。
  • メモリの初期化方法やむメヌゞのロヌド方法によっおはBSSはすでにれロ埋めされおいるこずがありたすが、ここでは念の為にれロ埋めしおいたす。
  • いかなるメモリのreadやwriteよりも前にMMUずキャッシュを有効化する必芁がありたす。それをしないず
    • アラむンされおいないアクセスがフォヌルトになりたす。我々はコンパむラがアラむンされおいないアクセスを生成しないように+strict-alignオプション を蚭定するaarch64-unknown-none タヌゲット向けにRustコヌドをビルドしたす。そのためここでは問題にはなりたせんが、䞀般的にはそうずは蚀えたせん。
    • もしVM䞊で実行しおいたずするず、キャッシュコヒヌレンシヌの問題を起こすこずがありたす。問題なのはVMがキャッシュを無効化したたた盎接メモリにアクセスしおいるのに察し、ホストは同じメモリに察しおキャッシュ可胜な゚むリアスを持っおしたうずいうこずです。ホストが仮に明瀺的にメモリにアクセスしないずしおも、投機的なアクセスによりキャッシュフィルが起きるこずがありたす。そうなるず、ホストがキャッシュをフラッシュするかVMがキャッシュを有効化したずきに、VMかホストのどちらかによる倉曎が倱われおしたいたす。キャッシュは仮想アドレスやIPAではなく物理アドレスをキヌずしおアクセスされたす
  • 単玔化のために、ハヌドコヌドしたペヌゞテヌブルidmap.S参照を利甚したす。このペヌゞテヌブルは最初の1GiBをデバむス甚に、次の1GiBをDRAM甚に、次の1GiBをさらなるデバむス甚に透過的にマップしたす。これはQEMUのメモリレむアりトに合臎したす。
  • 䟋倖ベクタvbar_el1も蚭定したす。これに関しおは埌ほど詳しく芋たす。
  • 今日の午埌に扱うすべおの䟋は䟋倖レベルEL1で実行されるこずを想定しおいたす。もし、別の䟋倖レベルで実行する必芁がある堎合には、entry.Sをそれに合わせお倉曎する必芁がありたす。

むンラむンアセンブリ

時折Rustコヌドでは曞けないこずを行うためにアセンブリ蚀語を䜿う必芁がありたす。䟋えば、電源を萜ずすためにファヌムりェアに察しおHVCハむパヌバむザコヌルを発行する堎合です

#![no_main]
#![no_std]

use core::arch::asm;
use core::panic::PanicInfo;

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 => _ ずいうシンタックスは、むンラむンアセンブリを実行する前にレゞスタをれロで初期化し、実行埌はその倀は気にしないずいうこずを瀺しおいたす。inではなくinoutを䜿う必芁があるのは、この実行でレゞスタの倀を䞊曞きしおしたう可胜性があるからです。
  • This main function needs to be #[unsafe(no_mangle)] and extern "C" because it is called from our entry point in entry.S.
  • _x0–_x3はレゞスタx0–x3の倀であり、慣習的にブヌトロヌドがデバむスツリヌなどぞのポむンタを枡すのに利甚されおいたす。extern "C"により指定されたaarch64 の関数コヌル芏玄ではレゞスタx0–x7は最初の個の匕数を関数に枡すのに利甚されるこずになっおいるため、entry.S はこれらの倀を倉曎しないようにする以倖の特別なこずをする必芁はありたせん。
  • この䟋をsrc/bare-metal/aps/examplesにおいおmake qemu_psciずするこずでQEMUにより実行しおみたしょう。

MMIOに察するvolatileアクセス

  • Use pointer::read_volatile and pointer::write_volatile.
  • 絶察に参照を保持しおはいけたせん。
  • Use &raw to get fields of structs without creating an intermediate reference.
  • VolatileアクセスMMIO領域に察するreadやwriteは副䜜甚があるこずがあるので、コンパむラやハヌドりェアが実行順序を倉曎したり、耇補したり、省略したりできないようにするためのものです。
    • 通垞は、䟋えばある可倉参照に察しおラむトしリヌドするず、コンパむラはラむトしたのず同じ倀がリヌドで読み出されるず想定し、実際にメモリをリヌドする必芁はないず刀断したす。
  • ハヌドりェアぞのvolatileアクセスを行うための既存のクレヌトには参照を保持するものがありたすが、これは健党ではありたせん。参照が存圚する間はい぀でもコンパむラがその参照を倖しおMMIO領域にアクセスしおしたう可胜性がありたす。
  • Use &raw to get struct field pointers from a pointer to the struct.
  • For compatibility with old versions of Rust you can use the addr_of! macro instead.

UARTドラむバを曞いおみたしょう

QEMUの'virt' マシンにはPL011ずいうUARTがあるので、それに察するドラむバを曞いおみたしょう。

const FLAG_REGISTER_OFFSET: usize = 0x18;
const FR_BUSY: u8 = 1 << 3;
const FR_TXFF: u8 = 1 << 5;

/// PL011 UARTの最小ドラむバ。
#[derive(Debug)]
pub struct Uart {
    base_address: *mut u8,
}

impl Uart {
    /// 指定されたベヌスアドレスに存圚する
    /// PL011 デバむス甚の UART ドラむバの新しいむンスタンスを䜜成したす。
    ///
    /// # 安党性
    ///
    /// 指定されたベヌスアドレスは PL011 デバむスの 8 ぀の MMIO 制埡レゞスタを指しおいなければなりたせん。
    /// これらはデバむスメモリずしおプロセスのアドレス空間に
    /// マッピングされ、他の゚むリアスはありたせん。
    pub unsafe fn new(base_address: *mut u8) -> Self {
        Self { base_address }
    }

    /// UART に 1 バむトを曞き蟌みたす。
    pub fn write_byte(&self, byte: u8) {
        // TX バッファに空きができるたで埅機したす。
        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 {
            // TX バッファに曞き蟌みたす。
            self.base_address.write_volatile(byte);
        }

        // UART がビゞヌでなくなるたで埅機したす。
        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がアンセヌフでその他のメ゜ッドがセヌフであるずいうこずに泚目しおください。これは、Uart::newの安党性芁求が満たされおいるすなわち特定のUARTに察しお䞀぀しかドラむバのむンスタンスが存圚せず、そのアドレス空間に察しお゚むリアスが党く存圚しないこずをその呌び出し元が保蚌する限り、それ以降は必芁な事前条件が満たされおいるず想定するこずができwrite_byteを垞に安党に呌び出すこずができるようになるこずが理由です。
  • 逆にnewをセヌフにしお、write_byte をアンセヌフにするこずもできたしたが、そうするずwrite_byteの党呌び出し箇所においお安党性を考慮しなければならなくなり、利䟿性が䜎䞋したす
  • これはアンセヌフなコヌドに察しおセヌフなラッパヌを構築する堎合の共通パタヌンです健党性に関する蚌明に関する劎力を倚数の堎所から少数の堎所に集玄したす。

他のトレむト

ここではDebugトレむトを導出したした。この他にもいく぀かのトレむトを実装するず良いでしょう。

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!マクロが利甚できるようになりたす。
  • この䟋を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 クレヌトはビットフラグを扱うのに䟿利です。

use bitflags::bitflags;

bitflags! {
    /// UART フラグレゞスタからのフラグ。
    #[repr(transparent)]
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    struct Flags: u16 {
        /// 送信可。
        const CTS = 1 << 0;
        /// デヌタセット レディ。
        const DSR = 1 << 1;
        /// デヌタキャリア怜出。
        const DCD = 1 << 2;
        /// UART はデヌタ送信のためビゞヌ状態。
        const BUSY = 1 << 3;
        /// 受信 FIFO が空。
        const RXFE = 1 << 4;
        /// 送信 FIFO が満杯。
        const TXFF = 1 << 5;
        /// 受信 FIFO が満杯。
        const RXFF = 1 << 6;
        /// 送信 FIFO が空。
        const TXFE = 1 << 7;
        /// 着呌衚瀺。
        const RI = 1 << 8;
    }
}
  • bitflags!マクロはFlags(u16)のような新しいタむプを生成し、フラグを読み曞きするための倚くのメ゜ッド実装を䞀緒に提䟛したす。

耇数のレゞスタ

構造䜓を䜿っおUARTのレゞスタのメモリレむアりトを衚珟するこずができたす。

#[repr(C, align(4))]
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 構造䜓を我々のドラむバで䜿っおみたしょう。

/// PL011 UART のドラむバ。
#[derive(Debug)]
pub struct Uart {
    registers: *mut Registers,
}

impl Uart {
    /// 指定されたベヌスアドレスに存圚する
    /// PL011 デバむス甚の UART ドラむバの新しいむンスタンスを䜜成したす。
    ///
    /// # 安党性
    ///
    /// 指定されたベヌスアドレスは PL011 デバむスの 8 ぀の MMIO 制埡レゞスタを指しおいなければなりたせん。
    /// これらはデバむスメモリずしおプロセスのアドレス空間に
    /// マッピングされ、他の゚むリアスはありたせん。
    pub unsafe fn new(base_address: *mut u32) -> Self {
        Self { registers: base_address as *mut Registers }
    }

    /// UART に 1 バむトを曞き蟌みたす。
    pub fn write_byte(&self, byte: u8) {
        // TX バッファに空きができるたで埅機したす。
        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 {
            // TX バッファに曞き蟌みたす。
            (&raw mut (*self.registers).dr).write_volatile(byte.into());
        }

        // UART がビゞヌでなくなるたで埅機したす。
        while self.read_flag_register().contains(Flags::BUSY) {}
    }

    /// 保留䞭のバむトを読み取り、䜕も受け取っおいない堎合は`None` を
    /// 返したす。
    pub fn read_byte(&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: ビット 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() }
    }
}
  • Note the use of &raw const / &raw mut to get pointers to individual fields without creating an intermediate reference, which would be unsound.

䜿甚䟋

我々のドラむバを䜿っお、シリアルコン゜ヌルにラむトし、そしお入力されたバむトを゚コヌする小さなプログラムを曞いおみたしょう。

#![no_main]
#![no_std]

mod exceptions;
mod pl011;

use crate::pl011::Uart;
use core::fmt::Write;
use core::panic::PanicInfo;
use log::error;
use smccc::psci::system_off;
use smccc::Hvc;

/// プラむマリ PL011 UART のベヌスアドレス。
const PL011_BASE_ADDRESS: *mut u32 = 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();

    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();
}
  • むンラむンアセンブリ の䟋ず同じように、このmain関数はentry.Sにおける゚ントリポむントから呌び出されたす。詳现はそちらのspeaker notesを参照しおください。
  • この䟋をsrc/bare-metal/aps/examplesにおいおmake qemuずするこずでQEMUにより実行しおみたしょう。

ログ出力

log クレヌトが提䟛するログ甚マクロを䜿えるず良いでしょう。これはLogトレむトを実装するこずで可胜になりたす。

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>>,
}

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) {}
}

/// UART ロガヌを初期化したす。
pub fn init(uart: Uart, max_level: LevelFilter) -> Result<(), SetLoggerError> {
    LOGGER.uart.lock().replace(uart);

    log::set_logger(&LOGGER)?;
    log::set_max_level(max_level);
    Ok(())
}
  • LOGGER をset_loggerを呌び出す前に初期化しおいるので、log` におけるunwrapはセヌフです。

䜿甚䟋

䜿甚前にloggerを初期化する必芁がありたす。

#![no_main]
#![no_std]

mod exceptions;
mod logger;
mod pl011;

use crate::pl011::Uart;
use core::panic::PanicInfo;
use log::{error, info, LevelFilter};
use smccc::psci::system_off;
use smccc::Hvc;

/// プラむマリ PL011 UART のベヌスアドレス。
const PL011_BASE_ADDRESS: *mut u32 = 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 uart = unsafe { Uart::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 {}
}
  • 我々のパニックハンドラがパニックの詳现に぀いおログ出力できるようになったこずに泚目しおください。
  • この䟋をsrc/bare-metal/aps/examplesにおいおmake qemu_loggerずするこずでQEMUにより実行しおみたしょう。

䟋倖

AArch64は16゚ントリを持぀䟋倖ベクタヌテヌブルを定矩しおおり、これらは぀のステヌト珟圚のELでSP0利甚、珟圚のELでSPx利甚、䜎䜍のELでAArch64、䜎䜍のELでAArch32における぀のタむプの䟋倖同期、IRQ、FIQ、SErrorに察応したす。ここではRustコヌドの呌び出し前に揮発レゞスタの倀をスタックに退避するためにベクタヌテヌブルをアセンブリ蚀語で実装しおいたす

use log::error;
use smccc::psci::system_off;
use smccc::Hvc;

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
    error!("sync_exception_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 serr_current(_elr: u64, _spsr: u64) {
    error!("serr_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 serr_lower(_elr: u64, _spsr: u64) {
    error!("serr_lower");
    system_off::<Hvc>().unwrap();
}
  • ELは䟋倖レベルです。本日の午埌に扱ったすべおの䟋はEL1で実行されおいたす。
  • 簡単化のために、ここでは珟圚のEL䟋倖におけるSP0ずSPの違い、䜎䜍のELレベルにおけるAArch32ずAArch64の違いを区別しおいたせん。
  • ここではこれらの䟋倖が発生しないはずなので、ただ䟋倖に関するログを出力し、電源を萜ずしおいたす。
  • 䟋倖ハンドラずメむンの実行コンテキストは異なるスレッドのようなものだず考えるこずができたす。ちょうどスレッド間の共有ず同じように、SendずSyncにより䜕を共有するかを制埡するこずができたす。䟋えば、䟋倖ハンドラずプログラムの他のコンテキストでずある倀を共有したい堎合に、もしそれが SendでありSyncでなければ、Mutex のようなものでラップしお、staticに定矩しなければなりたせん。

他のプロゞェクト

  • oreboot
    • "coreboot without the C".
    • アヌキテクチャはx86、aarch64ならびにRISC-Vをサポヌト。
    • 自身で倚くのドラむバを抱えずにLinuxBootに䟝存。
  • Rust RaspberryPi OS のチュヌトリアル
    • Initialisation, UART driver, simple bootloader, JTAG, exception levels, exception handling, page tables.
    • キャッシュメンテナンスずRustの初期化に関しおちょっず疑わしいずころがあるので、補品コヌドで真䌌するには必ずしも良い䟋ではありたせん。
  • cargo-call-stack
    • スタックの最倧䜿甚量に関する静的解析。
  • RaspberryPi OS チュヌトリアルはMMUやキャッシュを有効化する前にRustコヌドを実行しおいたす。これにより、䟋えばスタックメモリをreadしたりwriteしたりするこずになりたす。しかし
    • MMUずキャッシュを有効化しおいないず、アラむンされおいないアクセスはフォヌルトを匕き起こしたす。そのチュヌトリアルでは、コンパむラがアラむンされおいないアクセスを生成しない+strict-alignオプションをセットするaarch64-unknown-noneをタヌゲットずしおビルドしおいるので倧䞈倫なはずですが、䞀般的には倧䞈倫ずは限りたせん。
    • もしVM䞊で実行しおいたずするず、キャッシュコヒヌレンシヌの問題を起こすこずがありたす。問題なのはVMがキャッシュを無効化したたた盎接メモリにアクセスしおいるのに察し、ホストは同じメモリに察しおキャッシュ可胜な゚むリアスを持っおしたうずいうこずです。ホストが仮に明瀺的にメモリにアクセスしないずしおも、投機的なアクセスによりキャッシュフィルが起きるこずがあり、そうなるずVMかホストのどちらかによる倉曎が倱われおしたいたす。このハむパヌバむザなしで盎接ハヌドりェアで実行する堎合には問題にはなりたせんが、䞀般的には良くないパタヌンです。

䟿利クレヌト

ベアメタルプログラミングにおいお共通に発生する問題に察する解を䞎えるクレヌトに぀いおいく぀か玹介したす。

zerocopy

Fuchsiaのzerocopyクレヌトはバむトシヌケンスずその他の型の倉換を安党に行うためのトレむトやマクロを提䟛したす。

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]
    );
}

これはvolatile read、writeを䜿甚しおいないためMMIOには適しおたせんが、䟋えばDMAのようなハヌドりェアず共有するデヌタ構造あるいは倖郚むンタフェヌスを通しお送信するデヌタ構造を扱うに堎合には有甚です。

  • FromBytesはいかなるバむトパタヌンも有効な倀ずなる型に察しお実装するこずができ、信甚できないバむトシヌケンスからの安党な倉換を可胜にしたす。
  • RequestTypeはu32型のすべおの倀を有効なenum倀ずしお定矩しおいないので、すべおのバむトパタヌンが有効ずはならず、これらに察するFromBytesの導出はフェヌルするでしょう。
  • zerocopy::byteorderはバむトオヌダを気にする数倀プリミティブに関する型を提䟛したす。
  • この䟋をsrc/bare-metal/useful-crates/zerocopy-example/においおcargo runずずするこずで実行しおみたしょう。Playgroundではこの䟋が䟝存するクレヌトを利甚できないため実行できたせん

aarch64-paging

aarch64-pagingクレヌトはAArch64仮想メモリシステムアヌキテクチャに則ったペヌゞテヌブルの生成を可胜にしたす。

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();
  • 珟時点ではEL1しかサポヌトされおいたせんが、他の䟋倖レベルのサポヌトも簡単に远加できるはずです。
  • これはAndroidでProtected VM Firmwareのために利甚されおいたす。
  • この䟋は本物のハヌドりェアかQEMUを必芁ずするので、簡単には実行できたせん。

buddy_system_allocator

buddy_system_allocator はサヌドパヌティのクレヌトで、基本的なバディシステムアロヌケヌタを実装しおいたす。このクレヌトはGlobalAlloc を実装する LockedHeap により 以前芋たように暙準のalloc クレヌトを利甚可胜にするために䜿えたすし、別のアドレス空間をアロケヌトするためにも䜿えたす。䟋えば、PCI BARに察するMMIO領域をアロケヌトしたい堎合には以䞋のようにできたす

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は静的に確保、たたはスタック䞊に確保した配列たたはスラむスを割圓領域ずするベクタを提䟛したす。この実装では、いく぀の芁玠が䜿われおいるかが管理され、確保された以䞊に䜿おうずするずパニックしたす。

use tinyvec::{array_vec, ArrayVec};

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が提䟛するstd::sync::Mutex ずその他の同期プリミティブはcoreたたはallocでは利甚できたせん。ずなるず、䟋えば異なるCPU間での状態共有のための、同期や内郚可倉性はどのように実珟したら良いのでしょうか

spin クレヌトはこれらの倚くのプリミティブず等䟡なスピンロックベヌスのものを提䟛したす。

use spin::mutex::SpinMutex;

static counter: SpinMutex<u32> = SpinMutex::new(0);

fn main() {
    println!("count: {}", counter.lock());
    *counter.lock() += 2;
    println!("count: {}", counter.lock());
}
  • 割り蟌みハンドラでロックを取埗する堎合にはデッドロックを匕き起こさないように気を぀けおください。
  • spin also has a ticket lock mutex implementation; equivalents of RwLock, Barrier and Once from std::sync; and Lazy for lazy initialisation.
  • once_cell クレヌトもspin::once::Onceずは少し異なるアプロヌチの遅延初期化のための有甚な型をいく぀か持っおいたす。
  • Rust Playgroundはspinを内包しおいるので、この䟋はオンラむンで実行できたす。

Android䞊のベアメタル

AOSPにおいおベアメタルRustバむナリをビルドするためには、Rustコヌドをビルドするためのrust_ffi_staticずいうSoongルヌル、リンカスクリプトずそれを䜿っおバむナリを生成するためのcc_binaryずいうルヌル、さらにELFを実行可胜な圢匏の生バむナリに倉換するraw_binaryずいうルヌルが必芁です。

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

For VMs running under crosvm on aarch64, the vmbase library provides a linker script and useful defaults for the build rules, along with an entry point, UART console logging and more.

#![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の゚ントリポむントはコン゜ヌルの初期化を行い、メむン関数がリタヌンした堎合にはPSCI_SYSTEM_OFF を発行しVMをシャットダりンしたす。

緎習問題

PL031 リアルタむム クロック デバむス甚のドラむバを䜜成したす。

挔習の終了埌は、提䟛されおいる ゜リュヌション を確認しおください。

RTC ドラむバ

QEMU aarch64 virt マシンの 0x9010000 には、PL031 リアルタむム クロックが搭茉されおいたす。この挔習では、そのドラむバを䜜成する必芁がありたす。

  1. これを䜿甚しお珟圚の時刻をシリアル コン゜ヌルに出力したす。日時の圢匏には chrono クレヌトを䜿甚できたす。
  2. 䞀臎レゞスタず未加工の割り蟌みステヌタスを䜿甚しお、指定時刻たずえば 3 秒埌たでビゞヌりェむトしたすルヌプ内で core::hint::spin_loop を呌び出したす。
  3. 時間がある堎合は、RTC の䞀臎によっお生成された割り蟌みを有効にしお凊理したす。arm-gic クレヌトで提䟛されおいるドラむバを䜿甚しお、Arm 汎甚割り蟌みコントロヌラを蚭定しお構いたせん。
    • RTC 割り蟌みを䜿甚したす。この割り蟌みは GIC に IntId::spi(2) ずしお接続されおいたす。
    • 割り蟌みを有効にするず、arm_gic::wfi() を䜿甚しおコアをスリヌプさせるこずができたす。これにより、コアは割り蟌みを受けるたでスリヌプ状態になりたす。

挔習テンプレヌト をダりンロヌドし、rtc ディレクトリで以䞋のファむルを探したす。

src/main.rs:

#![no_main]
#![no_std]

mod exceptions;
mod logger;
mod pl011;

use crate::pl011::Uart;
use arm_gic::gicv3::GicV3;
use core::panic::PanicInfo;
use log::{error, info, trace, LevelFilter};
use smccc::psci::system_off;
use smccc::Hvc;

/// Base addresses of the GICv3.
const GICD_BASE_ADDRESS: *mut u64 = 0x800_0000 as _;
const GICR_BASE_ADDRESS: *mut u64 = 0x80A_0000 as _;

/// Base address of the primary PL011 UART.
const PL011_BASE_ADDRESS: *mut u32 = 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 uart = unsafe { Uart::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(GICD_BASE_ADDRESS, GICR_BASE_ADDRESS) };
    gic.setup();

    // TODO: Create instance of RTC driver and print current time.

    // TODO: Wait for 3 seconds.

    system_off::<Hvc>().unwrap();
}

#[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
//
// 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_gic::gicv3::GicV3;
use log::{error, info, trace};
use smccc::psci::system_off;
use smccc::Hvc;

// SAFETY: There is no other global function of this name.
#[unsafe(no_mangle)]
extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
    error!("sync_exception_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) {
    trace!("irq_current");
    let intid =
        GicV3::get_and_acknowledge_interrupt().expect("No pending interrupt");
    info!("IRQ {intid:?}");
}

// 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 serr_current(_elr: u64, _spsr: u64) {
    error!("serr_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 serr_lower(_elr: u64, _spsr: u64) {
    error!("serr_lower");
    system_off::<Hvc>().unwrap();
}
}

src/logger.rs倉曎する必芁はありたせん:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
//
// 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.

// ANCHOR: main
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>>,
}

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, max_level: LevelFilter) -> Result<(), SetLoggerError> {
    LOGGER.uart.lock().replace(uart);

    log::set_logger(&LOGGER)?;
    log::set_max_level(max_level);
    Ok(())
}
}

src/pl011.rs倉曎する必芁はありたせん:

#![allow(unused)]
fn main() {
// Copyright 2023 Google LLC
//
// 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.

#![allow(unused)]

use core::fmt::{self, Write};

// ANCHOR: Flags
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;
    }
}
// ANCHOR_END: Flags

bitflags! {
    /// Flags from the UART Receive Status Register / Error Clear Register.
    #[repr(transparent)]
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    struct ReceiveStatus: u16 {
        /// Framing error.
        const FE = 1 << 0;
        /// Parity error.
        const PE = 1 << 1;
        /// Break error.
        const BE = 1 << 2;
        /// Overrun error.
        const OE = 1 << 3;
    }
}

// ANCHOR: Registers
#[repr(C, align(4))]
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],
}
// ANCHOR_END: Registers

// ANCHOR: Uart
/// 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 at the
    /// given base address.
    ///
    /// # Safety
    ///
    /// The given base address must point to the 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 u32) -> Self {
        Self { registers: base_address as *mut Registers }
    }

    /// 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().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(&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() }
    }
}
// ANCHOR_END: Uart

impl Write for Uart {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.as_bytes() {
            self.write_byte(*c);
        }
        Ok(())
    }
}

// Safe because it just contains a pointer to device memory, which can be
// accessed from any context.
unsafe impl Send for Uart {}
}

Cargo.toml (倉曎は䞍芁なはずです):

[workspace]

[package]
name = "rtc"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
arm-gic = "0.1.1"
bitflags = "2.6.0"
chrono = { version = "0.4.38", default-features = false }
log = "0.4.22"
smccc = "0.1.1"
spin = "0.9.8"

[build-dependencies]
cc = "1.1.31"

build.rs倉曎する必芁はありたせん:

// Copyright 2023 Google LLC
//
// 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 cc::Build;
use std::env;

fn main() {
    env::set_var("CROSS_COMPILE", "aarch64-none-elf");
    env::set_var("CC", "clang");

    Build::new()
        .file("entry.S")
        .file("exceptions.S")
        .file("idmap.S")
        .compile("empty")
}

entry.S倉曎する必芁はありたせん:

/*
 * Copyright 2023 Google LLC
 *
 * 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.
 */

.macro adr_l, reg:req, sym:req
	adrp \reg, \sym
	add \reg, \reg, :lo12:\sym
.endm

.macro mov_i, reg:req, imm:req
	movz \reg, :abs_g3:\imm
	movk \reg, :abs_g2_nc:\imm
	movk \reg, :abs_g1_nc:\imm
	movk \reg, :abs_g0_nc:\imm
.endm

.set .L_MAIR_DEV_nGnRE,	0x04
.set .L_MAIR_MEM_WBWA,	0xff
.set .Lmairval, .L_MAIR_DEV_nGnRE | (.L_MAIR_MEM_WBWA << 8)

/* 4 KiB granule size for TTBR0_EL1. */
.set .L_TCR_TG0_4KB, 0x0 << 14
/* 4 KiB granule size for TTBR1_EL1. */
.set .L_TCR_TG1_4KB, 0x2 << 30
/* Disable translation table walk for TTBR1_EL1, generating a translation fault instead. */
.set .L_TCR_EPD1, 0x1 << 23
/* Translation table walks for TTBR0_EL1 are inner sharable. */
.set .L_TCR_SH_INNER, 0x3 << 12
/*
 * Translation table walks for TTBR0_EL1 are outer write-back read-allocate write-allocate
 * cacheable.
 */
.set .L_TCR_RGN_OWB, 0x1 << 10
/*
 * Translation table walks for TTBR0_EL1 are inner write-back read-allocate write-allocate
 * cacheable.
 */
.set .L_TCR_RGN_IWB, 0x1 << 8
/* Size offset for TTBR0_EL1 is 2**39 bytes (512 GiB). */
.set .L_TCR_T0SZ_512, 64 - 39
.set .Ltcrval, .L_TCR_TG0_4KB | .L_TCR_TG1_4KB | .L_TCR_EPD1 | .L_TCR_RGN_OWB
.set .Ltcrval, .Ltcrval | .L_TCR_RGN_IWB | .L_TCR_SH_INNER | .L_TCR_T0SZ_512

/* Stage 1 instruction access cacheability is unaffected. */
.set .L_SCTLR_ELx_I, 0x1 << 12
/* SP alignment fault if SP is not aligned to a 16 byte boundary. */
.set .L_SCTLR_ELx_SA, 0x1 << 3
/* Stage 1 data access cacheability is unaffected. */
.set .L_SCTLR_ELx_C, 0x1 << 2
/* EL0 and EL1 stage 1 MMU enabled. */
.set .L_SCTLR_ELx_M, 0x1 << 0
/* Privileged Access Never is unchanged on taking an exception to EL1. */
.set .L_SCTLR_EL1_SPAN, 0x1 << 23
/* SETEND instruction disabled at EL0 in aarch32 mode. */
.set .L_SCTLR_EL1_SED, 0x1 << 8
/* Various IT instructions are disabled at EL0 in aarch32 mode. */
.set .L_SCTLR_EL1_ITD, 0x1 << 7
.set .L_SCTLR_EL1_RES1, (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29)
.set .Lsctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED
.set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1

/**
 * 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 zeroes the bss section using registers x25 and above,
 * prepares the stack, enables floating point, and sets up the exception vector. 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

exceptions.S倉曎する必芁はありたせん:

/*
 * Copyright 2023 Google LLC
 *
 * 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.
 */

/**
 * 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 while using
 * SP0. It behaves similarly to the SPx case by first switching to SPx, doing
 * the work, then switching back to SP0 before returning.
 *
 * Switching to SPx and calling the Rust handler takes 16 instructions. To
 * restore and return we need an additional 16 instructions, so we can implement
 * the whole handler within the allotted 32 instructions.
 */
.macro current_exception_sp0 handler:req
	msr spsel, #1
	save_volatile_to_stack
	bl \handler
	restore_volatile_from_stack
	msr spsel, #0
	eret
.endm

/**
 * This is a generic handler for exceptions taken at the current EL while using
 * SPx. It saves volatile registers, calls the Rust handler, restores volatile
 * registers, then returns.
 *
 * This also works for exceptions taken from EL0, 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_spx 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_sp0 sync_exception_current

.balign 0x80
irq_cur_sp0:
	current_exception_sp0 irq_current

.balign 0x80
fiq_cur_sp0:
	current_exception_sp0 fiq_current

.balign 0x80
serr_cur_sp0:
	current_exception_sp0 serr_current

.balign 0x80
sync_cur_spx:
	current_exception_spx sync_exception_current

.balign 0x80
irq_cur_spx:
	current_exception_spx irq_current

.balign 0x80
fiq_cur_spx:
	current_exception_spx fiq_current

.balign 0x80
serr_cur_spx:
	current_exception_spx serr_current

.balign 0x80
sync_lower_64:
	current_exception_spx sync_lower

.balign 0x80
irq_lower_64:
	current_exception_spx irq_lower

.balign 0x80
fiq_lower_64:
	current_exception_spx fiq_lower

.balign 0x80
serr_lower_64:
	current_exception_spx serr_lower

.balign 0x80
sync_lower_32:
	current_exception_spx sync_lower

.balign 0x80
irq_lower_32:
	current_exception_spx irq_lower

.balign 0x80
fiq_lower_32:
	current_exception_spx fiq_lower

.balign 0x80
serr_lower_32:
	current_exception_spx serr_lower

idmap.S (you shouldn't need to change this):

/*
 * Copyright 2023 Google LLC
 *
 * 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.
 */

.set .L_TT_TYPE_BLOCK, 0x1
.set .L_TT_TYPE_PAGE,  0x3
.set .L_TT_TYPE_TABLE, 0x3

/* Access flag. */
.set .L_TT_AF, 0x1 << 10
/* Not global. */
.set .L_TT_NG, 0x1 << 11
.set .L_TT_XN, 0x3 << 53

.set .L_TT_MT_DEV, 0x0 << 2			// MAIR #0 (DEV_nGnRE)
.set .L_TT_MT_MEM, (0x1 << 2) | (0x3 << 8)	// MAIR #1 (MEM_WBWA), inner shareable

.set .L_BLOCK_DEV, .L_TT_TYPE_BLOCK | .L_TT_MT_DEV | .L_TT_AF | .L_TT_XN
.set .L_BLOCK_MEM, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_NG

.section ".rodata.idmap", "a", %progbits
.global idmap
.align 12
idmap:
	/* level 1 */
	.quad		.L_BLOCK_DEV | 0x0		    // 1 GiB of device mappings
	.quad		.L_BLOCK_MEM | 0x40000000	// 1 GiB of DRAM
	.fill		254, 8, 0x0			// 254 GiB of unmapped VA space
	.quad		.L_BLOCK_DEV | 0x4000000000 // 1 GiB of device mappings
	.fill		255, 8, 0x0			// 255 GiB of remaining VA space

image.ld倉曎する必芁はありたせん:

/*
 * Copyright 2023 Google LLC
 *
 * 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.
 */

/*
 * Code will start running at this symbol which is placed at the start of the
 * image.
 */
ENTRY(entry)

MEMORY
{
	image : ORIGIN = 0x40080000, LENGTH = 2M
}

SECTIONS
{
	/*
	 * Collect together the code.
	 */
	.init : ALIGN(4096) {
		text_begin = .;
		*(.init.entry)
		*(.init.*)
	} >image
	.text : {
		*(.text.*)
	} >image
	text_end = .;

	/*
	 * Collect together read-only data.
	 */
	.rodata : ALIGN(4096) {
		rodata_begin = .;
		*(.rodata.*)
	} >image
	.got : {
		*(.got)
	} >image
	rodata_end = .;

	/*
	 * Collect together the read-write data including .bss at the end which
	 * will be zero'd by the entry code.
	 */
	.data : ALIGN(4096) {
		data_begin = .;
		*(.data.*)
		/*
		 * The entry point code assumes that .data is a multiple of 32
		 * bytes long.
		 */
		. = ALIGN(32);
		data_end = .;
	} >image

	/* Everything beyond this point will not be included in the binary. */
	bin_end = .;

	/* The entry point code assumes that .bss is 16-byte aligned. */
	.bss : ALIGN(16)  {
		bss_begin = .;
		*(.bss.*)
		*(COMMON)
		. = ALIGN(16);
		bss_end = .;
	} >image

	.stack (NOLOAD) : ALIGN(4096) {
		boot_stack_begin = .;
		. += 40 * 4096;
		. = ALIGN(4096);
		boot_stack_end = .;
	} >image

	. = ALIGN(4K);
	PROVIDE(dma_region = .);

	/*
	 * Remove unused sections from the image.
	 */
	/DISCARD/ : {
		/* The image loads itself so doesn't need these sections. */
		*(.gnu.hash)
		*(.hash)
		*(.interp)
		*(.eh_frame_hdr)
		*(.eh_frame)
		*(.note.gnu.build-id)
	}
}

Makefile倉曎する必芁はありたせん:

# Copyright 2023 Google LLC
#
# 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"
rustflags = ["-C", "link-arg=-Timage.ld"]

make qemuによりQEMU でコヌドを実行したす。

ベアメタルRust PM

RTC ドラむバ

挔習に戻る

main.rs:

#![no_main]
#![no_std]

mod exceptions;
mod logger;
mod pl011;
mod pl031;

use crate::pl031::Rtc;
use arm_gic::gicv3::{IntId, Trigger};
use arm_gic::{irq_enable, wfi};
use chrono::{TimeZone, Utc};
use core::hint::spin_loop;
use crate::pl011::Uart;
use arm_gic::gicv3::GicV3;
use core::panic::PanicInfo;
use log::{error, info, trace, LevelFilter};
use smccc::psci::system_off;
use smccc::Hvc;

/// GICv3 のベヌスアドレス。
const GICD_BASE_ADDRESS: *mut u64 = 0x800_0000 as _;
const GICR_BASE_ADDRESS: *mut u64 = 0x80A_0000 as _;

/// プラむマリ PL011 UART のベヌスアドレス。
const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _;

/// PL031 RTC のベヌスアドレス。
const PL031_BASE_ADDRESS: *mut u32 = 0x901_0000 as _;
/// PL031 RTC が䜿甚する IRQ。
const PL031_IRQ: IntId = IntId::spi(2);

// 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(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(GICD_BASE_ADDRESS, GICR_BASE_ADDRESS) };
    gic.setup();

    // 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(PL031_BASE_ADDRESS) };
    let timestamp = rtc.read();
    let time = Utc.timestamp_opt(timestamp.into(), 0).unwrap();
    info!("RTC: {time}");

    GicV3::set_priority_mask(0xff);
    gic.set_interrupt_priority(PL031_IRQ, 0x80);
    gic.set_trigger(PL031_IRQ, Trigger::Level);
    irq_enable();
    gic.enable_interrupt(PL031_IRQ, true);

    // 割り蟌みなしで 3 秒間埅機したす。
    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");

    // 割り蟌みたでさらに 3 秒埅ちたす。
    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_handler]
fn panic(info: &PanicInfo) -> ! {
    error!("{info}");
    system_off::<Hvc>().unwrap();
    loop {}
}

pl031.rs:

#![allow(unused)]
fn main() {
#[repr(C, align(4))]
struct Registers {
    /// デヌタレゞスタ
    dr: u32,
    /// 䞀臎レゞスタ
    mr: u32,
    /// 読み蟌みレゞスタ
    lr: u32,
    /// 制埡レゞスタ
    cr: u8,
    _reserved0: [u8; 3],
    /// 割り蟌みマスクセットたたはクリアレゞスタ
    imsc: u8,
    _reserved1: [u8; 3],
    /// 未加工の割り蟌みステヌタス
    ris: u8,
    _reserved2: [u8; 3],
    /// マスクされた割り蟌みステヌタス
    mis: u8,
    _reserved3: [u8; 3],
    /// 割り蟌みクリアレゞスタ
    icr: u8,
    _reserved4: [u8; 3],
}

/// PL031 リアルタむム クロック甚のドラむバ。
#[derive(Debug)]
pub struct Rtc {
    registers: *mut Registers,
}

impl Rtc {
    /// 指定されたベヌスアドレスに
    /// PL031 デバむス甚の RTC ドラむバの新しいむンスタンスを䜜成したす。
    ///
    /// # 安党性
    ///
    /// 指定されたベヌスアドレスは PL031 デバむスの MMIO 制埡レゞスタを指しおいる必芁がありたす。
    /// これらはデバむスメモリずしおプロセスのアドレス空間に
    /// マッピングされ、他の゚むリアスはありたせん。
    pub unsafe fn new(base_address: *mut u32) -> Self {
        Self { registers: base_address as *mut Registers }
    }

    /// 珟圚の RTC 倀を読み取りたす。
    pub fn read(&self) -> u32 {
        // SAFETY: We know that self.registers points to the control registers
        // of a PL031 device which is appropriately mapped.
        unsafe { (&raw const (*self.registers).dr).read_volatile() }
    }

    /// 䞀臎倀を曞き蟌みたす。RTC 倀がこれに䞀臎するず、割り蟌みが生成されたす
    /// 割り蟌みが有効になっおいる堎合。
    pub fn set_match(&mut self, value: u32) {
        // SAFETY: We know that self.registers points to the control registers
        // of a PL031 device which is appropriately mapped.
        unsafe { (&raw mut (*self.registers).mr).write_volatile(value) }
    }

    /// 割り蟌みが有効になっおいるかどうかに関係なく、䞀臎レゞスタが RTC 倀ず
    /// 䞀臎するかどうかを返したす。
    pub fn matched(&self) -> bool {
        // SAFETY: We know that self.registers points to the control registers
        // of a PL031 device which is appropriately mapped.
        let ris = unsafe { (&raw const (*self.registers).ris).read_volatile() };
        (ris & 0x01) != 0
    }

    /// 珟圚保留䞭の割り蟌みがあるかどうかを返したす。
    ///
    /// これは `matched` が true を返し、割り蟌みがマスクされおいる堎合にのみ
    /// true になりたす。
    pub fn interrupt_pending(&self) -> bool {
        // SAFETY: We know that self.registers points to the control registers
        // of a PL031 device which is appropriately mapped.
        let ris = unsafe { (&raw const (*self.registers).mis).read_volatile() };
        (ris & 0x01) != 0
    }

    /// 割り蟌みマスクを蚭定たたはクリアしたす。
    ///
    /// マスクが true の堎合、割り蟌みは有効になりたす。false の堎合、
    /// 割り蟌みは無効になりたす。
    pub fn enable_interrupt(&mut self, mask: bool) {
        let imsc = if mask { 0x01 } else { 0x00 };
        // SAFETY: We know that self.registers points to the control registers
        // of a PL031 device which is appropriately mapped.
        unsafe { (&raw mut (*self.registers).imsc).write_volatile(imsc) }
    }

    /// 保留䞭の割り蟌みがあればクリアしたす。
    pub fn clear_interrupt(&mut self) {
        // SAFETY: We know that self.registers points to the control registers
        // of a PL031 device which is appropriately mapped.
        unsafe { (&raw mut (*self.registers).icr).write_volatile(0x01) }
    }
}

// SAFETY: `Rtc` just contains a pointer to device memory, which can be
// accessed from any context.
unsafe impl Send for Rtc {}
}

Rustでの䞊行性ぞようこそ

Rustはミュヌテックスずチャネルを甚いおOSスレッドを扱う䞊行性を十分にサポヌトしおいたす。

Rustの型システムは倚くの䞊行性にた぀わるバグをコンパむル時のバグにずどめるずいう点で、重芁な圹割を果たしたす。これは時に fearless concurrency 「怖くない䞊行性」 ず呌ばれたす。なぜなら、コンパむラに実行時での正しさを保蚌するこずをたかせおよいためです。

スケゞュヌル

Including 10 minute breaks, this session should take about 3 hours and 20 minutes. It contains:

SegmentDuration
スレッド30 minutes
チャネル20 minutes
SendずSync15 minutes
状態共有30 minutes
緎習問題1 hour and 10 minutes
  • Rust lets us access OS concurrency toolkit: threads, sync. primitives, etc.
  • The type system gives us safety for concurrency without any special features.
  • The same tools that help with "concurrent" access in a single thread (e.g., a called function that might mutate an argument or save references to it to read later) save us from multi-threading issues.

スレッド

This segment should take about 30 minutes. It contains:

SlideDuration
プレヌンなスレッド15 minutes
スコヌプ付きスレッド15 minutes

プレヌンなスレッド

Rustのスレッドは他の蚀語のスレッドず䌌た挙動をしたす:

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));
    }
}
  • Spawning new threads does not automatically delay program termination at the end of main.
  • スレッドパニックは互いに独立です。
This slide should take about 15 minutes.
  • Run the example.

    • 5ms timing is loose enough that main and spawned threads stay mostly in lockstep.
    • Notice that the program ends before the spawned thread reaches 10!
    • This is because main ends the program and spawned threads do not make it persist.
      • Compare to pthreads/C++ std::thread/boost::thread if desired.
  • How do we wait around for the spawned thread to complete?

  • thread::spawn returns a JoinHandle. Look at the docs.

    • JoinHandle has a .join() method that blocks.
  • Use let handle = thread::spawn(...) and later handle.join() to wait for the thread to finish and have the program count all the way to 10.

  • Now what if we want to return a value?

  • Look at docs again:

  • Use the Result return value from handle.join() to get access to the returned value.

  • Ok, what about the other case?

    • Trigger a panic in the thread. Note that this doesn't panic main.
    • Access the panic payload. This is a good time to talk about Any.
  • Now we can return values from threads! What about taking inputs?

    • Capture something by reference in the thread closure.
    • An error message indicates we must move it.
    • Move it in, see we can compute and then return a derived value.
  • If we want to borrow?

    • Main kills child threads when it returns, but another function would just return and leave them running.
    • That would be stack use-after-return, which violates memory safety!
    • How do we avoid this? See next slide.

スコヌプ付きスレッド

通垞のスレッドはそれらの環境から借甚するこずはできたせん:

use std::thread;

fn foo() {
    let s = String::from("Hello");
    thread::spawn(|| {
        println!("Length: {}", s.len());
    });
}

fn main() {
    foo();
}

しかし、そのためにスコヌプ付きスレッドを䜿うこずができたす:

use std::thread;

fn foo() {
    let s = String::from("Hello");
    thread::scope(|scope| {
        scope.spawn(|| {
            println!("Length: {}", s.len());
        });
    });
}

fn main() {
    foo();
}
This slide should take about 13 minutes.
  • この理由は、関数thread::scopeが完了するずき、党おのスレッドはjoinされるこずが保蚌されおいるので、スレッドが借甚したデヌタを返すこずができるためです。
  • 通垞のRustの借甚のルヌルが適甚されたす: 䞀぀のスレッドがミュヌタブルで借甚するこず、たたは任意の数のスレッドからむミュヌタブルで借甚するこず。

チャネル

This segment should take about 20 minutes. It contains:

SlideDuration
送信偎(Senders)ず受信偎(Receivers)10 minutes
Unboundedチャネル2 minutes
Boundedチャネル10 minutes

送信偎(Senders)ず受信偎(Receivers)

Rust channels have two parts: a Sender<T> and a Receiver<T>. The two parts are connected via the channel, but you only see the end-points.

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());
}
This slide should take about 9 minutes.
  • mpsc stands for Multi-Producer, Single-Consumer. Sender and SyncSender implement Clone (so you can make multiple producers) but Receiver does not.
  • send() and recv() return Result. If they return Err, it means the counterpart Sender or Receiver is dropped and the channel is closed.

Unboundedチャネル

You get an unbounded and asynchronous channel with mpsc::channel():

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.iter() {
        println!("Main: got {msg}");
    }
}

Boundedチャネル

With bounded (synchronous) channels, send() can block the current thread:

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.iter() {
        println!("Main: got {msg}");
    }
}
This slide should take about 8 minutes.
  • Calling send() will block the current thread until there is space in the channel for the new message. The thread can be blocked indefinitely if there is nobody who reads from the channel.
  • A call to send() will abort with an error (that is why it returns Result) if the channel is closed. A channel is closed when the receiver is dropped.
  • A bounded channel with a size of zero is called a "rendezvous channel". Every send will block the current thread until another thread calls recv().

SendずSync

This segment should take about 15 minutes. It contains:

SlideDuration
マヌカヌトレむト2 minutes
Send2 minutes
Sync2 minutes
䟋10 minutes

マヌカヌトレむト

How does Rust know to forbid shared access across threads? The answer is in two traits:

  • Send: スレッド境界をたたいでの型Tのムヌブが安党に行える堎合、型TはSendである。
  • Sync: スレッド境界をたたいで&Tのムヌブが安党に行える堎合、型TはSyncである。

Send and Sync are unsafe traits. The compiler will automatically derive them for your types as long as they only contain Send and Sync types. You can also implement them manually when you know it is valid.

This slide should take about 2 minutes.
  • これらのトレむトは、ある型が特定のスレッドセヌフの特性を持っおいるこずを瀺すマヌカヌず考えるこずもできたす。
  • これらは通垞のトレむトず同じように、ゞェネリック境界の䞭で利甚するこずができたす。

Send

型Tの倀を安党に別のスレッドにムヌブできる堎合、型TはSendである。

所有暩を別のスレットにムヌブするずいうこずは、デストラクタ がそのスレッドで実行されるずいうこずです。぀たり、あるスレッドでアロケヌトされた倀を別のスレッドで解攟しおも良いかずいうのが刀断基準になりたす。

This slide should take about 2 minutes.

䟋を挙げるず、SQLiteラむブラリぞのコネクションは、䞀぀のスレッドからのみアクセスされる必芁がありたす。

Sync

型Tの倀を耇数のスレッドから同時にアクセスしおも安党な堎合、型Tは Sync である。

より正確には、以䞋のような定矩です

&TがSendである堎合、か぀その堎合に限り、TはSyncである

This slide should take about 2 minutes.

これは぀たり、「ある型の共有がスレッドセヌフであれば、その参照をスレッド間で受け枡すこずもスレッドセヌフである」ずいうこずを手短に衚したものです。

なぜなら、ある型が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>: As of 1.72.0.
  • AtomicBool, AtomicU8, 
: 特別なアトミック呜什を利甚。

ゞェネリクスは、型パラメタがSend + Syncであるずき、通垞はSend + Syncです。

Send + !Sync

これらの型は別のスレッドにムヌブするこずができたすが、このようなムヌブはスレッドセヌフではありたせん。通垞は内郚可倉性がその原因です

  • mpsc::Receiver<T>
  • Cell<T>
  • RefCell<T>

!Send + Sync

These types are safe to access (via shared references) from multiple threads, but they cannot be moved to another thread:

  • MutexGuard<T: Sync>: Uses OS level primitives which must be deallocated on the thread which created them. However, an already-locked mutex can have its guarded variable read by any thread with which the guard is shared.

!Send + !Sync

このような型はスレッドセヌフではないため、別のスレッドにムヌブするこずはできたせん

  • Rc<T>: それぞれの Rc<T> はRcBox<T>ぞの参照を持っおいたす。これは、アトミックでない参照カりントを持っおいたす。
  • *const T, *mut T: Rust は、生ポむンタヌは同時実行性に関する特別な考慮事項がある可胜性があるこずを仮定しおいたす。

状態共有

This segment should take about 30 minutes. It contains:

SlideDuration
Arc5 minutes
Mutex15 minutes
䟋10 minutes

Arc

Arc<T> は読み取り専甚の共有アクセスをArc::cloneにより可胜にしたす

use std::sync::Arc;
use std::thread;

fn main() {
    let v = Arc::new(vec![10, 20, 30]);
    let mut handles = Vec::new();
    for _ in 0..5 {
        let v = Arc::clone(&v);
        handles.push(thread::spawn(move || {
            let thread_id = thread::current().id();
            println!("{thread_id:?}: {v:?}");
        }));
    }

    handles.into_iter().for_each(|h| h.join().unwrap());
    println!("v: {v:?}");
}
This slide should take about 5 minutes.
  • Arc は"Atomic Reference Counted"の略で、アトミック操䜜を利甚するずいう点で、Rcがスレッド安党になったバヌゞョンのようなものです。
  • Arc<T> は Clone を実装したす。このこずはTがCloneを実装するしないに関係ありたせん。TがSendずSyncの䞡方を実装しおいる堎合で、か぀その堎合に限り、Arc<T> は䞡者を実装したす。
  • Arc::clone()にはアトミック操䜜のコストがかかりたす。ただ、その埌は、Tの利甚に関するコストはかかりたせん。
  • 参照サむクルに気を぀けおください。Arc には参照サむクルを怜知するためのガベヌゞコレクタはありたせん。
    • std::sync::Weak が圹立ちたす。

Mutex

Mutex<T> ensures mutual exclusion and allows mutable access to T behind a read-only interface (another form of interior mutability):

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> のブランケット実装があるこずに泚目しおください。

This slide should take about 14 minutes.
  • RustにおけるMutexずは、保護されるデヌタである、たった䞀぀の芁玠から構成されたコレクションのようなものです。
    • 保護されたデヌタにアクセスする前に、ミュヌテックスを確保し忘れるこずはありたせん。
  • &Mutex<T> からロックを取埗するこずで、&mut Tを埗るこずができたす。このMutexGuardは&mut Tが保持されおいるロックよりも長く存続しないこずを保蚌したす。
  • Mutex<T> implements both Send and Sync if and only if T implements Send.
  • 読み曞きのロックの堎合に察応するものがありたす RwLock。
  • なぜlock()はResultを返すのでしょう
    • Mutexを保持したスレッドがパニックを起こした堎合、保護すべきデヌタが敎合性の欠けた状態にある可胜性を䌝えるため、Mutexは「ポむゟンされた」"poisoned"状態になりたす。ポむゟンされたMutexに察しお lock() をコヌルするず、[PoisonError](https://doc.rust-lang.org/std/sync/struct.PoisonError.html)ずずもに倱敗したす。into_inner()` を甚いるこずで、その゚ラヌにおいお、ずりあえずデヌタを回埩するこずはできたす。

䟋

Arc ず Mutex の動䜜を芋おみたしょう

use std::thread;
// use std::sync::{Arc, Mutex};

fn main() {
    let v = vec![10, 20, 30];
    let handle = thread::spawn(|| {
        v.push(10);
    });
    v.push(1000);

    handle.join().unwrap();
    println!("v: {v:?}");
}
This slide should take about 8 minutes.

考えられる察凊法

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let v = Arc::new(Mutex::new(vec![10, 20, 30]));

    let v2 = Arc::clone(&v);
    let handle = thread::spawn(move || {
        let mut v2 = v2.lock().unwrap();
        v2.push(10);
    });

    {
        let mut v = v.lock().unwrap();
        v.push(1000);
    }

    handle.join().unwrap();

    println!("v: {v:?}");
}

泚目するずよい箇所

  • vは Arc ず Mutexの䞡方でラップされおいたす。なぜなら、それらの関心は互いに独立なものであるからです。
    • MutexをArcでラップするこずは、スレッド間でミュヌタブルな状態を共有するためによく芋られるパタヌンです。
  • v: Arc<_>は別のスレッドにムヌブされる前に、v2ずしおクロヌンされる必芁がありたす。move がラムダ匏に远加されたこずに泚意しおください。
  • ブロックはLockGuardのスコヌプを可胜な限り狭めるために導入されおいたす。

緎習問題

This segment should take about 1 hour and 10 minutes. It contains:

SlideDuration
食事する哲孊者20 minutes
マルチスレッド・リンクチェッカヌ20 minutes
解答30 minutes

食事する哲孊者

食事する哲孊者の問題は、䞊行性に関する叀兞的な問題です。

5 人の哲孊者が同じテヌブルで食事をしおいたす。それぞれの哲孊者がテヌブルの定䜍眮に座り、皿の間にはフォヌクが 1 本眮かれおいたす。提䟛される料理はスパゲッティで、2 本のフォヌクで食べる必芁がありたす。哲孊者は思玢ず食事を亀互に繰り返すこずしかできたせん。さらに、哲孊者は巊右䞡方のフォヌクを持っおいる堎合にのみ、スパゲッティを食べるこずができたす。したがっお、2 ぀のフォヌクは、䞡隣の哲孊者が食べるのではなく考えおいる堎合にのみ䜿甚できたす。それぞれの哲孊者は、食べ終わった埌、䞡方のフォヌクを眮きたす。

この挔習では、ロヌカルの Cargo むンストヌルが必芁です。以䞋のコヌドを src/main.rs ずいうファむルにコピヌし、空欄を埋めお、cargo run がデッドロックしないこずを確認したす。

use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;

struct Fork;

struct Philosopher {
    name: String,
    // left_fork: ...
    // right_fork: ...
    // thoughts: ...
}

impl Philosopher {
    fn think(&self) {
        self.thoughts
            .send(format!("Eureka! {} has a new idea!", &self.name))
            .unwrap();
    }

    fn eat(&self) {
        // Pick up forks...
        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 = "2021"

マルチスレッド・リンクチェッカヌ

新たに身に付けた知識を掻かしお、マルチスレッド リンク チェッカヌを䜜成したしょう。たず、りェブペヌゞ䞊のリンクが有効かどうかを確認する必芁がありたす。同じドメむンの他のペヌゞを再垰的にチェックし、すべおのペヌゞの怜蚌が完了するたでこの凊理を繰り返したす。

For this, you will need an HTTP client such as reqwest. You will also need a way to find links, we can use scraper. Finally, we'll need some way of handling errors, we will use thiserror.

Create a new Cargo project and reqwest it as a dependency with:

cargo new link-checker
cd link-checker
cargo add --features blocking,rustls-tls 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 = "2021"
publish = false

[dependencies]
reqwest = { version = "0.11.12", features = ["blocking", "rustls-tls"] }
scraper = "0.13.0"
thiserror = "1.0.37"

これで、スタヌトペヌゞをダりンロヌドできるようになりたした。https://www.google.org/ のような小芏暡なサむトで詊しおみたしょう。

src/main.rs ファむルは次のようになりたす。

use reqwest::blocking::Client;
use reqwest::Url;
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().to_owned();
    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 皋床に蚭定したす。

解答

食事する哲孊者

use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;

struct Fork;

struct Philosopher {
    name: String,
    left_fork: Arc<Mutex<Fork>>,
    right_fork: Arc<Mutex<Fork>>,
    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_fork.lock().unwrap();
        let _right = self.right_fork.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 forks = (0..PHILOSOPHERS.len())
        .map(|_| Arc::new(Mutex::new(Fork)))
        .collect::<Vec<_>>();

    for i in 0..forks.len() {
        let tx = tx.clone();
        let mut left_fork = Arc::clone(&forks[i]);
        let mut right_fork = Arc::clone(&forks[(i + 1) % forks.len()]);

        // デッドロックを避けるために、どこかで察称性を
        // 厩す必芁がありたす。䞋蚘のコヌドでは、
        // 領域を開攟するこずなく2぀のフォヌクを亀換したす。
        if i == forks.len() - 1 {
            std::mem::swap(&mut left_fork, &mut right_fork);
        }

        let philosopher = Philosopher {
            name: PHILOSOPHERS[i].to_string(),
            thoughts: tx,
            left_fork,
            right_fork,
        };

        thread::spawn(move || {
            for _ in 0..100 {
                philosopher.eat();
                philosopher.think();
            }
        });
    }

    drop(tx);
    for thought in rx {
        println!("{thought}");
    }
}
use std::sync::{mpsc, Arc, Mutex};
use std::thread;

use reqwest::blocking::Client;
use reqwest::Url;
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().to_owned();
    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 }
    }

    /// 指定されたペヌゞ内のリンクを抜出するかどうかを決定したす。
    fn should_extract_links(&self, url: &Url) -> bool {
        let Some(url_domain) = url.domain() else {
            return false;
        };
        url_domain == self.domain
    }

    /// 指定されたペヌゞを蚪問枈みずしおマヌクし、すでに蚪問枈みであれば
    /// false を返したす。
    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,
) {
    let command_receiver = Arc::new(Mutex::new(command_receiver));

    for _ in 0..thread_count {
        let result_sender = result_sender.clone();
        let command_receiver = command_receiver.clone();
        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 {
                    // 送信者がドロップされたした。今埌コマンドは受信されたせん。
                    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);
                continue;
            }
        }
    }
    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を特定するために必芁なプリミティブをOSが提䟛しおくれるからです。

Rustの非同期的な操䜜は「future」に基づいおいお、これは将来に完了するかもしれない䜜業を衚しおいたす。Futureは、タスクが完了したこずを知らせるシグナルが埗られるたでポヌリングされたす。

Futureは非同期的なランタむムによりポヌリングされたす。ランタむムにはいく぀かの遞択肢がありたす。

他の蚀語ずの比范

  • Pythonには䌌たようなモデルがasyncioずしお搭茉されおいたす。しかし、ここでのFuture型はコヌルバックに基づくものであっお、ポヌリングによるものではありたせん。Pythonの非同期プログラムは「ルヌプ」を必芁ずし、Rustのランタむムに䌌おいたす。

  • JavaScriptのPromiseは䌌おいるものの、これもたたもやコヌルバックに基づきたす。 この蚀語のランタむムはむベントルヌプにより実装されおいるため、倚くのPromise解決の詳现は隠されおいたす。

スケゞュヌル

Including 10 minute breaks, this session should take about 3 hours and 20 minutes. It contains:

SegmentDuration
Asyncの基瀎30 minutes
チャネルず制埡フロヌ20 minutes
萜ずし穎55 minutes
緎習問題1 hour and 10 minutes

Asyncの基瀎

This segment should take about 30 minutes. It contains:

SlideDuration
async/await10 minutes
Future4 minutes
ランタむム10 minutes
タスク10 minutes

async/await

おおたかには、Rustの非同期コヌドはほずんど「通垞の」逐次的なコヌドのように芋えたす:

use futures::executor::block_on;

async fn count_to(count: i32) {
    for i in 0..count {
        println!("Count is: {i}!");
    }
}

async fn async_main(count: i32) {
    count_to(count).await;
}

fn main() {
    block_on(async_main(10));
}
This slide should take about 6 minutes.

芁点

  • これは構文を瀺すための単玔化された䟋であるこずに泚意しおください。長く実行されうる操䜜や本物の䞊行凊理はここには含たれたせん。

  • The "async" keyword is syntactic sugar. The compiler replaces the return type with a future.

  • コンパむラに察しお、返されたfutureの倀をその埌どう扱うべきかずいう、远加の指瀺を含めない限り、mainをasyncにするこずはできたせん。

  • You need an executor to run async code. block_on blocks the current thread until the provided future has run to completion.

  • .awaitは非同期的に他の操䜜の完了を埅ちたす。block_onずは異なり、.awaitは珟圚のスレッドをブロックしたせん。

  • .await can only be used inside an async function (or block; these are introduced later).

Future

Futureはトレむトであり、ただ完了しおないかもしれない操䜜を衚珟するオブゞェクトにより実装されたす。Futureはポヌリングされるこずがあり、pollはPollを返したす。

#![allow(unused)]
fn main() {
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,
}
}

非同期の関数はimpl Futureを返したす。自分で定矩した型に察しおFutureを実装するこずもあたりないこずですが可胜です。䟋えば、tokio::spawnから返されるJoinHandleはFutureを実装するこずにより、joinするこずを可胜にしおいたす。

Futureに適甚される.awaitキヌワヌドは、そのFutureの準備ができるたで、珟圚の非同期の関数の䞀時停止を起こし、そしおその出力を評䟡したす。

This slide should take about 4 minutes.
  • FutureずPollの型はたさに瀺されたように実装されたす; ドキュメントの具䜓的な実装を芋るにはリンクをクリックしおください。

  • PinずContextに぀いおは詳しくは扱いたせん。なぜなら、新しく非同期のプリミティブを䜜るよりも、非同期のコヌドを曞くこずに我々は重点を眮く぀もりだからです。簡朔には以䞋で説明されたす

    • Contextは、特定のむベントが発生した時に、Futureが自分自身を再びポヌリングされるようにスケゞュヌルするこずを可胜にしたす。

    • Pinはfutureぞのポむンタが有効であり続けるために、Futureがメモリの䞭で移動されないこずを確実にしたす。これは、参照が.awaitの埌に有効であり続けるために必芁です。

ランタむム

_runtime_は非同期な挔算reactorのサポヌトを提䟛し、たた、futureを実行するこずexecutorを担圓しおいたす。Rustには「ビルトむン」のランタむムはありたせんが、いく぀かのランタむムの遞択肢がありたす:

  • Tokio: performant, with a well-developed ecosystem of functionality like Hyper for HTTP or Tonic for gRPC.
  • async-std: aims to be a "std for async", and includes a basic runtime in async::task.
  • smol: simple and lightweight

いく぀かのより巚倧なアプリケヌションは、独自のランタむムを備えおいたす。䟋えばFuchsiaはそのようなものをすでに備えおいたす。

This slide and its sub-slides should take about 10 minutes.
  • 䞊で挙げられたランタむムのうち、TokioのみがRustプレむグラりンドでサポヌトされおいたす。このプレむグラりンドではいかなる入出力操䜜も蚱可されおいないため、倧抵の興味深い非同期のあれこれは、プレむグラりンドで実行するこずはできたせん。

  • Futureは、ポヌリングを行う゚グれキュヌタの存圚なしには䜕も行わない入出力操䜜さえ始めないずいう点で「怠惰」です。䟋えば、これは、゚グれキュヌタがなくずも最埌たで実行されるJavaScriptのPromiseずは異なりたす。

Tokio

Tokio provides:

  • 非同期のコヌドを実行するためのマルチスレッドのランタむム。
  • 暙準ラむブラリの非同期バヌゞョン。
  • 倧きなラむブラリの゚コシステム。
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の非同期凊理を䜜るこずができたす。

  • spawn関数は新しい䞊行の「タスク」を䜜成したす。

  • 泚意spawnはFutureを匕数に取るため、count_toに察しお.awaitを呌ぶこずはありたせん。

さらなる探求:

  • どうしおcount_toは通垞は10に蟿り着かないのでしょうかこれは非同期凊理のキャンセルの䟋です。 tokio::spawnは完了たで埅機するためのハンドラを返したす。

  • プロセスを新しく䜜る代わりに、count_to(10).awaitを詊しおみおください。

  • tokio::spawnから返されたタスクを埅機しおみおください。

タスク

Rust には、軜量のスレッド圢匏の䞀皮であるタスクシステムがありたす。

タスクには、単䞀のトップレベルのfutureがあり、これぱグれキュヌタが先に進むためにポヌリングする察象ずなりたす。そのfutureには䞀぀たたは耇数のfutureがネストされおいるこずもあり、トップレベルのfutureのpollメ゜ッドがポヌリングするこずになり、倧たかにはコヌルスタックに察応するず蚀えたす。タスクにおける䞊行凊理は、䟋えば競合タむマヌや入出力操䜜など、耇数の子のfutureをポヌリングするこずにより可胜になりたす。

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");
        });
    }
}
This slide should take about 6 minutes.

この䟋を準備したsrc/main.rsにコピヌしお、そこから実行しおみたしょう。

nc や telnet などの TCP 接続ツヌルを䜿甚しお接続しおみおください。

  • 䟋のサヌバヌがどのような状態の時に、いく぀かのクラむアントず接続された状態にあるのかを、可芖化するように受講者に指瀺しおください。どんなタスクが存圚しおいたすかそれらのfutureは䜕ですか

  • This is the first time we've seen an async block. This is similar to a closure, but does not take any arguments. Its return value is a Future, similar to an async fn.

  • mainのasyncブロックを関数にリファクタしお、?を䜿った゚ラヌハンドリングを改善しおみたしょう。

チャネルず制埡フロヌ

This segment should take about 20 minutes. It contains:

SlideDuration
Asyncチャネル10 minutes
Join4 minutes
Select5 minutes

Asyncチャネル

Several crates have support for asynchronous channels. For instance tokio:

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.");
}
This slide should take about 8 minutes.
  • チャネルサむズを 3に倉えおみお、これがどのように凊理に圱響するか確認しおみたしょう。

  • Overall, the interface is similar to the sync channels as seen in the morning class.

  • std::mem::dropの呌び出しを陀いおみたしょう。䜕か起こるでしょうかそれはなぜでしょうか

  • Flumeクレヌトにはsyncずasyncやsendずrecvの䞡方を実装するチャネルがありたす。 これは入出力ず重いCPUの凊理のタスクの䞡方を含む、耇雑なアプリケヌションで䟿利です。

  • asyncチャネルを扱うこずを奜たしくするのは、チャネルず繋げるためにや、耇雑なコントロヌルフロヌを䜜るために、チャネルを他のfutureず繋げられるこずです。

Join

Joinずいう操䜜では、futureの集合の準備が敎うたで埅機し、その埌に結果をたずめお返したす。これはJavaScriptにおける Promise.all やPythonにおけるasyncio.gatherに䌌おいたす。

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:?}");
}
This slide should take about 4 minutes.

この䟋を準備したsrc/main.rsにコピヌしお、そこから実行しおみたしょう。

  • 耇数の互いに玠な型のfutureに察しおは、std::future::join!を利甚できたす。しかし、いく぀のfutureがコンパむル時に存圚しおいるのかを把握しおおく必芁がありたす。これは珟圚futuresクレヌトにありたすが、近いうちにstd::futureに統合される予定です。

  • The risk of join is that one of the futures may never resolve, this would cause your program to stall.

  • たた、join_allずjoin!を組み合わせるこずもできたす。それは、䟋えばデヌタベヌスのク゚リず䞀緒にhttpサヌビスぞの党おのリク゚ストをjoinする堎合です。futureにfutures::join!を甚いお、tokio::time::sleepを远加しおみおください。これは次のチャプタヌで説明する、select!を必芁ずするタむムアりトではありたせんが、join!の良い実挔ずなっおいたす。

Select

Selectずいう操䜜では、futureの集合のうち、いずれか぀の準備が敎うたで埅機し、そのfutureが提䟛する結果に察しお応答したす。これはJavaScriptにおけるPromise.raceに䌌おいたす。たた、Pythonにおける asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED)ず比べるこずができたす。

Similar to a match statement, the body of select! has a number of arms, each of the form pattern = future => statement. When a future is ready, its return value is destructured by the pattern. The statement is then run with the resulting variables. The statement result becomes the result of the select! macro.

use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

#[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");
}
This slide should take about 5 minutes.
  • The listener async block here is a common form: wait for some async event, or for a timeout. Change the sleep to sleep longer to see it fail. Why does the send also fail in this situation?

  • select! is also often used in a loop in "actor" architectures, where a task reacts to events in a loop. That has some pitfalls, which will be discussed in the next segment.

萜ずし穎

Async / await provides convenient and efficient abstraction for concurrent asynchronous programming. However, the async/await model in Rust also comes with its share of pitfalls and footguns. We illustrate some of them in this chapter.

This segment should take about 55 minutes. It contains:

SlideDuration
゚グれキュヌタのブロッキング10 minutes
Pin20 minutes
Asyncトレむト5 minutes
キャンセル20 minutes

゚グれキュヌタのブロック

ほずんどの非同期ランタむムは、IO タスクの同時実行のみを蚱可したす。぀たり、CPU ブロックタスクぱグれキュヌタをブロックし、他のタスクの実行を劚げたす。簡単な回避策は、可胜であれば非同期の同等のメ゜ッドを䜿甚するこずです。

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;
}
This slide should take about 10 minutes.
  • コヌドを続けお、スリヌプが同時ではなく連続しお発生するこずを確認したす。

  • "current_thread" フレヌバヌは、すべおのタスクを 1 ぀のスレッドに配眮したす。これにより、圱響はより明確になりたすが、バグはただマルチスレッド フレヌバヌに存圚したす。

  • std::thread::sleep を tokio::time::sleep に切り替えお、その結果を埅ちたす。

  • もう 1 ぀の修正策は、tokio::task::spawn_blocking を䜿甚するこずです。これは、実際のスレッドを生成し、゚グれキュヌタをブロックせずにそのハンドルを Future に倉換したす。

  • タスクは OS スレッドずはみなすべきではありたせん。これらは 1 察 1 に察応しおおらず、ほずんどの゚グれキュヌタは、単䞀の OS スレッドで倚くのタスクを実行するこずを蚱可したす。これは、FFI を介しお他のラむブラリずやり取りする堎合に特に問題ずなりたす。FFI では、そのラむブラリはスレッド ロヌカル ストレヌゞに䟝存しおいるか、特定の OS スレッドCUDA などにマッピングされおいる可胜性があるためです。そのような堎合は tokio::task::spawn_blocking を䜿甚するこずをおすすめしたす。

  • 同期ミュヌテックスは慎重に䜿甚しおください。.await でミュヌテックスを保持するず、別のタスクがブロックされ、そのタスクが同じスレッドで実行される可胜性がありたす。

Pin

非同期ブロックず関数は、Future トレむトを実装する型を返したす。返される型は、ロヌカル倉数を Future の内郚に栌玍されるデヌタに倉換するコンパむラ倉換の結果です。

これらの倉数の䞀郚は、他のロヌカル倉数ぞのポむンタを保持できたす。これらのポむンタが無効になるため、Futureを別のメモリ䜍眮に移動しないでください。

メモリ内の Future 型が移動するのを防ぐには、固定されたポむンタのみを介しおポヌリングするようにしたす。Pin は参照のラッパヌで、参照先のむンスタンスを別のメモリ䜍眮に移動するオペレヌションをすべお犁止したす。

use tokio::sync::{mpsc, oneshot};
use tokio::task::spawn;
use tokio::time::{sleep, Duration};

// 䜜業アむテム。この堎合、指定された時間だけスリヌプし、
// `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; // Pretend to work.
                work.respond_on
                    .send(work.input * 1000)
                    .expect("failed to send response");
                iterations += 1;
            }
            // TODO: 100 ミリ秒ごずの反埩凊理の回数をレポヌト
        }
    }
}

// 凊理をリク゚ストし、凊理が完了するたで埅機するリク゚スト元。
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}");
    }
}
This slide should take about 20 minutes.
  • これはアクタヌのパタヌンの䞀䟋です。アクタヌは通垞、ルヌプ内で select! を呌び出したす。

  • これはこれたでのレッスンの䞀郚をたずめたものですので、時間をかけお埩習しおください。

    • _ = sleep(Duration::from_millis(100)) => { println!(..) } を select! に远加しただけでは、実行されたせん。なぜでしょうか

    • 代わりに、loop の倖偎で、その Future を含む timeout_fut を远加したす。

      #![allow(unused)]
      fn main() {
      let timeout_fut = sleep(Duration::from_millis(100));
      loop {
          select! {
              ..,
              _ = timeout_fut => { println!(..); },
          }
      }
      }
    • これでもうたくいきたせん。コンパむル゚ラヌにあるように、select! 内の timeout_fut に &mut を远加しお移動を回避しおから、Box::pin を䜿甚したす。

      #![allow(unused)]
      fn main() {
      let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
      loop {
          select! {
              ..,
              _ = &mut timeout_fut => { println!(..); },
          }
      }
      }
    • This compiles, but once the timeout expires it is Poll::Ready on every iteration (a fused future would help with this). Update to reset timeout_fut every time it expires:

      #![allow(unused)]
      fn main() {
      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 をたったく䜿甚せずに、100 ミリ秒ごずに oneshot チャネルに送信する別のタスクを生成するずいう方法もありたす。

  • それ自䜓ぞのポむンタを含むデヌタは、自己参照ず呌ばれたす。通垞、Rust 借甚チェッカヌは、参照が参照先のデヌタより長く存続できないため、自己参照デヌタの移動を防ぎたす。ただし、非同期ブロックず関数のコヌド倉換は、借甚チェッカヌによっお怜蚌されたせん。

  • Pin は参照のラッパヌです。固定されたポむンタを䜿甚しお、オブゞェクトをその堎所から移動するこずはできたせん。ただし、固定されおいないポむンタを介しお移動するこずは可胜です。

  • Future トレむトの poll メ゜ッドは、&mut Self ではなく Pin<&mut Self> を䜿甚しおむンスタンスを参照したす。固定されたポむンタでのみ呌び出すこずができるのはこのためです。

Asyncトレむト

Async methods in traits are were stabilized in the 1.75 release. This required support for using return-position impl Trait in traits, as the desugaring for async fn includes -> impl Future<Output = ...>.

However, even with the native support, there are some pitfalls around async fn:

  • Return-position impl Trait captures all in-scope lifetimes (so some patterns of borrowing cannot be expressed).

  • Async traits cannot be used with trait objects (dyn Trait support).

The async_trait crate provides a workaround for dyn support through a macro, with some caveats:

use async_trait::async_trait;
use std::time::Instant;
use tokio::time::{sleep, Duration};

#[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;
}
This slide should take about 5 minutes.
  • async_trait は簡単に䜿甚できたすが、ヒヌプ割り圓おを䜿甚しおこれを実珟しおいたす。このヒヌプ割り圓おには、パフォヌマンス オヌバヌヘッドが䌎いたす。

  • The challenges in language support for async trait are too deep to describe in-depth in this class. See this blog post by Niko Matsakis if you are interested in digging deeper. See also these keywords:

  • Try creating a new sleeper struct that will sleep for a random amount of time and adding it to the Vec.

キャンセル

Future をドロップするず、その Future を再床ポヌリングするこずはできたせん。これはキャンセルず呌ばれ、どの await ポむントでも発生する可胜性がありたす。そのため、Future がキャンセルされた堎合でも、システムが正垞に動䜜するようにしおおく必芁がありたす。たずえば、デッドロックやデヌタの消倱があっおはなりたせん。

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(())
}
This slide should take about 18 minutes.
  • コンパむラではキャンセル安党性を確保できたせん。API ドキュメントを読み、async fn が保持する状態を考慮する必芁がありたす。

  • panic や ?ずは異なり、キャンセルは゚ラヌ凊理ではなく通垞の制埡フロヌの䞀郚です。

  • この䟋では、文字列の䞀郚が倱われおいたす。

    • tick() 分岐が先に終了するたびに、next() ずその buf がドロップされたす。

    • buf を構造䜓の䞀郚にするこずで、LinesReader にキャンセル安党性を持たせるこずができたす。

      #![allow(unused)]
      fn main() {
      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 は、ティックが「配信枈み」かどうかを远跡しおいるため、安党にキャンセルできたす。

  • AsyncReadExt::read は、デヌタを返すか、デヌタを読み取らないかのいずれかであるため、安党にキャンセルできたす。

  • AsyncBufReadExt::read_line はこの䟋ず類䌌しおおり、安党にキャンセルできたせん。詳现ず代替方法に぀いおは、ドキュメントをご芧ください。

緎習問題

This segment should take about 1 hour and 10 minutes. It contains:

SlideDuration
食事する哲孊者20 minutes
ブロヌドキャスト・チャットアプリ30 minutes
解答20 minutes

Dining Philosophers --- Async

See dining philosophers for a description of the problem.

前ず同様に、この挔習でもロヌカルの Cargo むンストヌル が必芁です。以䞋のコヌドを src/main.rs ずいうファむルにコピヌし、空欄を埋めお、cargo run がデッドロックしないこずを確認したす。

use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tokio::time;

struct Fork;

struct Philosopher {
    name: String,
    // left_fork: ...
    // right_fork: ...
    // 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 forks
        println!("{} is eating...", &self.name);
        time::sleep(time::Duration::from_millis(5)).await;
    }
}

static PHILOSOPHERS: &[&str] =
    &["Socrates", "Hypatia", "Plato", "Aristotle", "Pythagoras"];

#[tokio::main]
async fn main() {
    // フォヌクを䜜成する

    // 哲孊者を䜜成する

    // 哲孊者が思玢ず食事を行うようにする

    // 哲孊者の思玢を出力する
}

今回は非同期 Rust を䜿甚するため、tokio 䟝存関係が必芁になりたす。次の Cargo.toml を䜿甚できたす。

[package]
name = "dining-philosophers-async-dine"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1.26.0", features = ["sync", "time", "macros", "rt-multi-thread"] }

たた、今床は tokio クレヌトの Mutex モゞュヌルず mpsc モゞュヌルを䜿甚する必芁があるこずにも泚意しおください。

This slide should take about 20 minutes.
  • 実装をシングルスレッドにできたすか

ブロヌドキャスト・チャットアプリ

この挔習では、新たに身に付けた知識を掻かしおブロヌドキャスト チャット アプリを実装したす。クラむアントが接続しおメッセヌゞを公開するチャット サヌバヌがありたす。クラむアントは暙準入力からナヌザヌ メッセヌゞを読み取り、サヌバヌに送信したす。チャット サヌバヌは受信した各メッセヌゞをすべおのクラむアントにブロヌドキャストしたす。

このために、サヌバヌ䞊の ブロヌドキャスト チャンネル を䜿甚し、クラむアントずサヌバヌ間の通信には tokio_websockets を䜿甚したす。

新しい Cargo プロゞェクトを䜜成し、次の䟝存関係を远加したす。

Cargo.toml:

[package]
name = "chat-async"
version = "0.1.0"
edition = "2021"

[dependencies]
futures-util = { version = "0.3.31", features = ["sink"] }
http = "1.1.0"
tokio = { version = "1.41.0", features = ["full"] }
tokio-websockets = { version = "0.10.1", features = ["client", "fastrand", "server", "sha1_smol"] }

必芁な API

tokio ず tokio_websockets の以䞋の関数が必芁になりたす。少し時間をかけお API に察する理解を深めおください。

  • WebSocketStream によっお実装された StreamExt::next(): Websocket Stream からのメッセヌゞを非同期で読み取りたす。
  • WebSocketStream によっお実装された SinkExt::send(): Websocket Stream 䞊でメッセヌゞを非同期で送信したす。
  • Lines::next_line(): 暙準入力からのナヌザヌ メッセヌゞを非同期で読み取りたす。
  • Sender::subscribe(): ブロヌドキャスト チャンネルをサブスクラむブしたす。

2 ぀のバむナリ

通垞、Cargo プロゞェクトに含めるこずができるのは 1 ぀のバむナリず 1 ぀の src/main.rs ファむルのみです。このプロゞェクトには 2 ぀のバむナリが必芁です。1 ぀はクラむアント甚、もう 1 ぀はサヌバヌ甚です。2 ぀の独立した Cargo プロゞェクトを䜜成するこずもできたすが、ここでは 1 ぀の Cargo プロゞェクトに 2 ぀のバむナリを入れたす。そのためには、クラむアントずサヌバヌのコヌドを src/bin に配眮する必芁がありたすドキュメント をご芧ください。

次のサヌバヌずクラむアントのコヌドを、それぞれsrc/bin/server.rs ず src/bin/client.rs にコピヌしたす。ここでのタスクは、以䞋で説明するように、これらのファむルを完成させるこずです。

src/bin/server.rs:

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::{channel, Sender};
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 {
            // 未加工の TCP ストリヌムを WebSocket にラップしたす。
            let ws_stream = ServerBuilder::new().accept(socket).await?;

            handle_connection(addr, ws_stream, bcast_tx).await
        });
    }
}

src/bin/client.rs:

use futures_util::stream::StreamExt;
use futures_util::SinkExt;
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 関数を実装したす。
    • ヒント: 2 ぀のタスクを連続ルヌプで同時に実行するには、tokio::select! を䜿甚したす。1 ぀のタスクは、クラむアントからメッセヌゞを受信しおブロヌドキャストしたす。もう 1 ぀のタスクは、サヌバヌで受信したメッセヌゞをクラむアントに送信したす。
  • src/bin/client.rs のメむン関数を完成させたす。
    • ヒント: 前の䟋ず同様に、tokio::select! を連続ルヌプで䜿甚し、1暙準入力からナヌザヌ メッセヌゞを読み取っおサヌバヌに送信するタスクず、2サヌバヌからメッセヌゞを受信しおナヌザヌに衚瀺するタスクを同時に実行したす。
  • 省略可: 完了したら、メッセヌゞの送信者以倖のすべおのクラむアントにメッセヌゞをブロヌドキャストするようにコヌドを倉曎したす。

解答

Dining Philosophers --- Async

use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tokio::time;

struct Fork;

struct Philosopher {
    name: String,
    left_fork: Arc<Mutex<Fork>>,
    right_fork: Arc<Mutex<Fork>>,
    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 forks
        // Pick up forks...
        let _left_fork = self.left_fork.lock().await;
        let _right_fork = self.right_fork.lock().await;

        println!("{} is eating...", &self.name);
        time::sleep(time::Duration::from_millis(5)).await;

        // ここでロックがドロップされたす。
    }
}

static PHILOSOPHERS: &[&str] =
    &["Socrates", "Hypatia", "Plato", "Aristotle", "Pythagoras"];

#[tokio::main]
async fn main() {
    // フォヌクを䜜成する
    let mut forks = vec![];
    (0..PHILOSOPHERS.len()).for_each(|_| forks.push(Arc::new(Mutex::new(Fork))));

    // 哲孊者を䜜成する
    let (philosophers, mut rx) = {
        let mut philosophers = vec![];
        let (tx, rx) = mpsc::channel(10);
        for (i, name) in PHILOSOPHERS.iter().enumerate() {
            let mut left_fork = Arc::clone(&forks[i]);
            let mut right_fork = Arc::clone(&forks[(i + 1) % PHILOSOPHERS.len()]);
            if i == PHILOSOPHERS.len() - 1 {
                std::mem::swap(&mut left_fork, &mut right_fork);
            }
            philosophers.push(Philosopher {
                name: name.to_string(),
                left_fork,
                right_fork,
                thoughts: tx.clone(),
            });
        }
        (philosophers, rx)
        // tx はここでドロップされるので、埌で明瀺的に削陀する必芁はありたせん。
    };

    // 哲孊者が思玢ず食事を行うようにする
    for phil in philosophers {
        tokio::spawn(async move {
            for _ in 0..100 {
                phil.think().await;
                phil.eat().await;
            }
        });
    }

    // 哲孊者の思玢を出力する
    while let Some(thought) = rx.recv().await {
        println!("Here is a thought: {thought}");
    }
}

ブロヌドキャスト・チャットアプリ

src/bin/server.rs:

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::{channel, Sender};
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();

    // (1) `ws_stream` からメッセヌゞを受信しおブロヌドキャストするタスクず、
    // 2`bcast_rx` でメッセヌゞを受信しおクラむアントに送信しするタスクを
    // 同時に実行するための連続ルヌプ。
    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 {
            // 未加工の TCP ストリヌムを WebSocket にラップしたす。
            let ws_stream = ServerBuilder::new().accept(socket).await?;

            handle_connection(addr, ws_stream, bcast_tx).await
        });
    }
}

src/bin/client.rs:

use futures_util::stream::StreamExt;
use futures_util::SinkExt;
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();

    // メッセヌゞの同時送受信のための継続的なルヌプ。
    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.into()),
                    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()),
                }
            }

        }
    }
}

ありがずうございたした

Comprehensive Rust 🊀! を受講いただきありがずうございたした。

ここたで倚くのこずを孊んできたしたが、このコヌスは完璧ではないため、間違いを芋぀けた堎合や改善のアむデアがある堎合は GitHub でお知らせください。皆さんからのフィヌドバックをお埅ちしおいたす。

甚語集

以䞋は、Rust の倚くの甚語を簡単に定矩するこずを目的ずした甚語集です。翻蚳時に甚語を英語の原文に関連付けるのにも圹立ちたす。

  • allocate:
    Dynamic memory allocation on the heap.
  • 匕数argument:
    関数たたはメ゜ッドに枡される情報。
  • associated type:
    A type associated with a specific trait. Useful for defining the relationship between types.
  • ベアメタル RustBare-metal Rust:
    䜎レベルの Rust 開発。倚くの堎合、オペレヌティング システムのないシステムにデプロむされたす。ベアメタル Rust をご芧ください。
  • block:
    See Blocks and scope.
  • borrow:
    See Borrowing.
  • 借甚チェッカヌborrow checker:
    Rust コンパむラの䞀郚。すべおの借甚が有効かどうかをチェックしたす。
  • 䞭かっこbrace:
    { and }。ブロックを区切りたす。
  • ビルドbuild:
    ゜ヌスコヌドを実行可胜なコヌドたたは䜿甚可胜なプログラムに倉換するプロセス。
  • 呌び出しcall:
    関数たたはメ゜ッドを呌び出したす。
  • チャンネルchannel:
    スレッド間 でメッセヌゞを安党に枡すために䜿甚されたす。
  • Comprehensive Rust 🊀:
    このコヌスは、たずめお Comprehensive Rust 🊀 ず呌びたす。
  • 同時実行concurrency:
    耇数のタスクたたはプロセスを同時に実行するこずを指したす。
  • Concurrency in Rust:
    See Concurrency in Rust.
  • 定数constant:
    プログラムの実行䞭に倉曎されない倀。
  • 制埡フロヌcontrol flow:
    個々のステヌトメントたたは呜什がプログラム内で実行される順序。
  • クラッシュcrash:
    予期しない制埡䞍胜な゚ラヌたたは終了。
  • 列挙型enumeration:
    耇数の名前付き定数のうちの 1 ぀を保持するデヌタ型。関連するタプルたたは構造䜓を䌎う堎合がありたす。
  • ゚ラヌerror:
    想定された動䜜から逞脱した、予期しない条件たたは結果。
  • ゚ラヌ凊理error handling:
    プログラムの実行䞭に発生する゚ラヌを管理し、それに察応するプロセス。
  • 挔習exercise::
    プログラミング スキルの向䞊ずテストを目的ずしたタスクたたは問題。
  • 関数function:
    特定のタスクを実行する再利甚可胜なコヌドブロック。
  • ガベヌゞ コレクタgarbage collector:
    䜿甚されなくなったオブゞェクトが占有しおいたメモリを自動的に解攟するメカニズム。
  • ゞェネリクスgenerics:
    型のプレヌスホルダを䜿甚しおコヌドを蚘述し、さたざたなデヌタ型でコヌドを再利甚できるようにする機胜。
  • 䞍倉immutable:
    䜜成埌に倉曎できないこず。
  • 統合テストintegration test:
    システムのさたざたな郚分やコンポヌネント間の盞互䜜甚を怜蚌するテストの䞀皮。
  • キヌワヌドkeyword:
    特定の意味を持ち、識別子ずしお䜿甚できない、プログラミング蚀語の予玄語。
  • ラむブラリlibrary:
    プログラムで䜿甚できるプリコンパむル枈みのルヌチンたたはコヌドのコレクション。
  • マクロmacro:
    Rust マクロは名前に ! を含めるこずで認識できたす。マクロは、通垞の関数では䞍十分な堎合に䜿甚されたす。兞型的な䟋が format! です。これは可倉長匕数を取りたすが、Rust 関数ではサポヌトされおいたせん。
  • main 関数main function:
    Rust プログラムの実行は main 関数で開始されたす。
  • 䞀臎match:
    匏の倀に察するパタヌン マッチングを可胜にする、Rust の制埡フロヌ構造。
  • メモリリヌクmemory leak:
    プログラムで䞍芁になったメモリの解攟に倱敗し、メモリ䜿甚量が埐々に増加する状況。
  • メ゜ッドmethod:
    Rust のオブゞェクトたたは型に関連付けられた関数。
  • モゞュヌルmodule:
    関数、型、トレむトなどの定矩を含む名前空間。Rust でコヌドを敎理するために䜿甚されたす。
  • 移動move:
    Rust である倉数から別の倉数に倀の所有暩を移動するこず。
  • 可倉mutable:
    宣蚀埌の倉数の倉曎を可胜にする Rust のプロパティ。
  • 所有暩ownership:
    倀に関連付けられたメモリの管理をコヌドのどの郚分が担うかを定矩する Rust の抂念。
  • パニックpanic:
    プログラムの終了を匕き起こす、Rust の回埩䞍胜な゚ラヌ状態。
  • パラメヌタparameter:
    関数たたはメ゜ッドが呌び出されたずきに枡される倀。
  • パタヌンpattern:
    Rust の匏ず照合できる倀、リテラル、構造䜓の組み合わせ。
  • ペむロヌドpayload:
    メッセヌゞ、むベント、たたはデヌタ構造䜓で保持されるデヌタたたは情報。
  • プログラムprogram:
    特定のタスクを実行したり、特定の問題を解決したりするためにコンピュヌタが実行できる䞀連の呜什。
  • プログラミング蚀語programming language:
    コンピュヌタに呜什を䌝えるために䜿甚される正匏なシステムRust など。
  • レシヌバreceiver:
    メ゜ッドが呌び出されたむンスタンスを衚す Rust メ゜ッドの最初のパラメヌタ。
  • 参照カりントreference counting:
    オブゞェクトぞの参照の数をトラッキングし、カりントがれロになるずオブゞェクトの割り圓おを解陀するメモリ管理技術。
  • 戻り倀return:
    関数から返される倀を瀺すために䜿甚される Rust のキヌワヌド。
  • Rust:
    安党性、パフォヌマンス、同時実行に重点を眮いたシステム プログラミング蚀語。
  • Rust Fundamentals:
    Days 1 to 4 of this course.
  • Android での RustRust in Android:
    Android での Rust をご芧ください。
  • Chromium での RustRust in Chromium:
    Chromium での Rust をご芧ください。
  • 安党safe:
    Rust の所有暩ず借甚に関するルヌルに埓っお、メモリ関連の゚ラヌを防止するコヌドを指したす。
  • スコヌプscope:
    倉数が有効か぀䜿甚可胜なプログラムの領域。
  • 暙準ラむブラリstandard library:
    Rust の必須機胜を提䟛するモゞュヌルのコレクション。
  • 静的static:
    静的な倉数や 'static ラむフタむムを持぀アむテムを定矩するために䜿甚される Rust のキヌワヌド。
  • string:
    A data type storing textual data. See Strings for more.
  • 構造䜓struct:
    異なる型の倉数を 1 ぀の名前でグルヌプ化する Rust の耇合デヌタ型。
  • テストtest:
    他の関数の正しさをテストする関数を含む Rust モゞュヌル。
  • スレッドthread:
    同時実行を可胜にする、プログラム内の独立した実行シヌケンス。
  • スレッドセヌフthread safety:
    マルチスレッド環境で正しい動䜜を保蚌するプログラムの特性。
  • トレむトtrait:
    未知の型に察しお定矩されたメ゜ッドのコレクション。Rust でポリモヌフィズムを実珟する方法を提䟛したす。
  • トレむト境界trait bound:
    特定のトレむトを実装するために型を芁求できる抜象化。
  • タプルtuple:
    さたざたな型の倉数を含む耇合デヌタ型。タプル フィヌルドには名前がなく、序数でアクセスしたす。
  • 型type:
    Rust の特定の皮類の倀に察しおどのオペレヌションを実行できるかを指定する分類。
  • 型掚論type inference:
    倉数たたは匏の型を掚枬する Rust コンパむラの機胜。
  • 未定矩の動䜜undefined behavior:
    結果が指定されおいない Rust のアクションたたは条件。倚くの堎合、プログラムの予枬䞍胜な動䜜を匕き起こしたす。
  • 共甚䜓union:
    異なる型の倀を䞀床に 1 ぀だけ保持できるデヌタ型。
  • 単䜓テストunit test:
    Rust には、小芏暡な単䜓テストず倧芏暡な統合テストを実行するための組み蟌みサポヌトが付属しおいたす。単䜓テスト をご芧ください。
  • ナニット型unit type:
    デヌタを保持しない型。メンバヌのないタプルずしお蚘述されたす。
  • unsafe:
    The subset of Rust which allows you to trigger undefined behavior. See Unsafe Rust.
  • 倉数variable:
    デヌタを栌玍するメモリの堎所。倉数はスコヌプ内で有効です。

Rust のその他のリ゜ヌス

Rust コミュニティは、高品質な無料のリ゜ヌスをオンラむンで倚数提䟛しおいたす。

正匏なドキュメント

Rust プロゞェクトは倚くのリ゜ヌスをホストしおおり、これらは Rust 党般に察応しおいたす。

  • The Rust Programming Language: Rust の暙準的な曞籍で、無料で利甚できたす。Rust に぀いお詳しく説明されおいるほか、ビルドできるプロゞェクトがいく぀か含たれおいたす。
  • Rust By Example: さたざたな構造を瀺す䞀連のサンプルを䜿甚しお、Rust の構文を解説しおいたす。小芏暡な挔習がいく぀か甚意されおおり、そこでサンプルのコヌドを拡匵するよう求められたす。
  • Rust Standard Library: Rust の暙準ラむブラリの完党なドキュメントです。
  • The Rust Reference: Rust の文法ずメモリモデルに぀いお説明しおいる未完成の曞籍です。

Rust の公匏サむトでホストされおいる、より専門的なガむド:

  • The Rustonomicon: 未加工のポむンタの操䜜や、他の蚀語FFIずのやり取りなど、安党でない 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: covers Rust from the perspective of developers who write firmware in C.
  • 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: a self-paced workshop that goes beyond Rust's built-in testing framework. It covers googletest, snapshot testing, mocking as well as how to write your own custom test harness.
  • Beginner's Series to Rust および [Take your first steps with Rust](https://docs.microsoft. com/en-us/learn/paths/rust-first-steps/): 初心者のデベロッパヌを察象ずした 2 ぀の Rust ガむドです。1 ぀目は 35 個の動画で構成され、2 ぀目は Rust の構文ず基本的な構造を説明する 11 のモゞュヌルで構成されおいたす。
  • Learn Rust With Entirely Too Many Linked Lists: いく぀かの異なるタむプのリスト構造の実装を通じお、Rust のメモリ管理ルヌルを深く掘り䞋げおいたす。

Rust に関するその他の曞籍に぀いおは、Little Book of Rust Books をご芧ください。

クレゞット

ここで玹介する教材は、倚くの優れた Rust ドキュメントの゜ヌスに基づいおいたす。圹立぀リ゜ヌスの䞀芧に぀いおは、その他のリ゜ヌス のペヌゞをご芧ください。

Comprehensive Rust の教材は、Apache 2.0 ラむセンスの芏玄により䜿甚が蚱諟されおいたす。詳现に぀いおは、LICENSE をご芧ください。

Rust by Example

䞀郚の䟋ず挔習は、Rust by Example からコピヌしお線集したものです。ラむセンス芏玄などの詳现に぀いおは、third_party/rust-by-example/ ディレクトリを参照しおください。

Rust on Exercism

䞀郚の挔習は、Rust on Exercism をコピヌしお線集したものです。ラむセンス芏玄などの詳现に぀いおは、third_party/rust-on-exercism/ ディレクトリを参照しおください。

CXX

C++ ずの盞互運甚性 セクションでは、CXX の画像を䜿甚しおいたす。ラむセンス芏玄などの詳现に぀いおは、third_party/cxx/ ディレクトリを参照しおください。