Upgrade to Pro — share decks privately, control downloads, hide ads and more …

カーネルランド Rust 勉強会 #5: ライフタイムと borrow checker が、組...

カーネルランド Rust 勉強会 #5: ライフタイムと borrow checker が、組込み開発分野でどう役立つのか

2026/10/8 に開催した勉強会で使ったスライドの、改訂増補版。改訂版のあとに、当日使ったものを連結しています。

Avatar for Shin-ya Koga

Shin-ya Koga

October 21, 2026

More Decks by Shin-ya Koga

Other Decks in Technology

Transcript

  1. 主題:組込み開発にどう役立つか 数学的に証明されたメモリ安全性の基盤 言語による安全性の担保(※バグフリーじゃないよ!) 数学的に証明した二つの論文 "RustBelt: Securing the Foundations of the

    Future of Systems Programming" (2018) "Stacked Borrows: An Aliasing Model for Rust" (2020) 所有権、借用、ライフタイム、Shared XOR Mutable 、 および Send & Sync trait に基づく Rust の型システムが 完全にメモリ安全かつスレッド安全であることを、Coq で数学的に証明。 Shared XOR Mutable の制約動作が、実際のコンピュータのメモリでどう振舞う かを、スタックの概念を使って形式化・証明。コンパイラの最適化基盤を定義。 Rust コンパイラは、定義されたメモリモデルを前提に最適化を実施する。
  2. 主題:組込み開発にどう役立つか 数学的に証明されたメモリ安全性の基盤 言語による安全性の担保(※バグフリーじゃないよ!) 数学的に証明した二つの論文 "RustBelt: Securing the Foundations of the

    Future of Systems Programming" (2018) "Stacked Borrows: An Aliasing Model for Rust" (2020) そのための制約は unsafe でも変わらない #2 のスライド参照(「unsafe だと許されること」) https://speakerdeck.com/shkoga/kanerurando-rust-mian-qiang-huinumber-2-rust-no-unsafe?slide=9 “Shared XOR Mutable” という強過ぎる強い制約
  3. 主題:組込み開発にどう役立つか 数学的に証明されたメモリ安全性の基盤 そのための制約は unsafe でも変わらない 制約を(できるだけ安全に)迂回する仕組みも提供 この後の部分の本題 結論:言語が課す制約によって、 C/C++ よりも

    安全なコードを書かされる書きやすい 大事なことなので、もう一度: バグフリーってことじゃないよ! 論理バグ(設計や仕様の間違い)は使う側の責任 https://speakerdeck.com/shkoga/kanerurando-rust-mian-qiang-hui-number-3-bing-xing-chu-li-niokeruan-quan-xing?slide=14
  4. やりたいこととジレンマ やりたいこと: ベアメタルの定石(?)を Rust で実現 割り込みハンドラ (top half) から、ロックフリーのリングバッファや ダブルバッファで(bottom

    half に)データを受け渡す。 https://speakerdeck.com/shkoga/2026-09-03?slide=12 "pure safe Rust" だとできないこと ロックフリーで共有データを非同期アクセス
  5. やりたいこととジレンマ やりたいこと: ベアメタルの定石(?)を Rust で実現 割り込みハンドラ (top half) から、ロックフリーのリングバッファや ダブルバッファで(bottom

    half に)データを受け渡す。 https://speakerdeck.com/shkoga/2026-09-03?slide=12 "pure safe Rust" だとできないこと ロックフリーで共有データを非同期アクセス リングバッファの場合:reader/consumer の読み出しと writer/producer の 書き込みは、バッファ上のそれぞれ異なる「スロット」に対して行う。 ただし、それぞれが使う「スロット」が実行中に変わる。 読み出し用のものと書き出し用のものがコンパイル時に決まらない!
  6. やりたいこととジレンマ やりたいこと: ベアメタルの定石(?)を Rust で実現 割り込みハンドラ (top half) から、ロックフリーのリングバッファや ダブルバッファで(bottom

    half に)データを受け渡す。 https://speakerdeck.com/shkoga/2026-09-03?slide=12 “Shared XOR Mutable” の 制約と矛盾する! "pure safe Rust" だとできないこと ロックフリーで共有データを非同期アクセス リングバッファの場合:reader/consumer の読み出しと writer/producer の 書き込みは、バッファ上のそれぞれ異なる「スロット」に対して行う。 ただし、それぞれが使う「スロット」が実行中に変わる。 読み出し用のものと書き出し用のものがコンパイル時に決まらない!
  7. Rust の「内部可変性」型と unsafe 内部可変性とは 不変参照を通して内部の値を変更できること UnsafeCell<T> と sync::atomic が基本 get()

    で T のポインタを取得できる。 ←unsafe ブロックでポインタを逆参照して 読み出し/書き込み。 単純型の値に対するアトミック操作: Read-Modify-Write
  8. Rust の「内部可変性」型と unsafe 内部可変性とは 不変参照を通して内部の値を変更できること UnsafeCell<T> と sync::atomic が基本 get()

    で T のポインタを取得できる。 ←unsafe ブロックでポインタを逆参照して 読み出し/書き込み。 cell::Cell や cell::RefCell、および sync::Mutex も内部可変性を持つ。 単純型の値に対するアトミック操作: Read-Modify-Write 内部可変性を持つ型は、全て UnsafeCell<T> を内包する! sync::atomic::Atomic<T> も(※理由は後述)
  9. Rust の「内部可変性」型と unsafe 内部可変性とは 不変参照を通して内部の値を変更できること UnsafeCell<T> と sync::atomic が基本 複数アクティビティの同時アクセス方法

    UnsafeCell<T> + Sync & Send 素直なリングバッファの実装例 [UnsafeCell<MaybeUninit<T>>] https://github.com/aodr3w/llt-rs/blob/main/src/ring_buffer/mod.rs
  10. Rust の「内部可変性」型と unsafe 内部可変性とは 不変参照を通して内部の値を変更できること UnsafeCell<T> と sync::atomic が基本 複数アクティビティの同時アクセス方法

    UnsafeCell<T> + Sync & Send 素直なリングバッファの実装例 [UnsafeCell<MaybeUninit<T>>] https://github.com/aodr3w/llt-rs/blob/main/src/ring_buffer/mod.rs バッファ領域を UnsafeCell の配列にする。 読み出しスロットと書き込みスロットのインデックスは sync::atomic。(※理由は後述)
  11. Rust の「内部可変性」型と unsafe 内部可変性とは 不変参照を通して内部の値を変更できること UnsafeCell<T> と sync::atomic が基本 複数アクティビティの同時アクセス方法

    UnsafeCell<T> + Sync & Send 素直なリングバッファの実装例 [UnsafeCell<MaybeUninit<T>>] https://github.com/aodr3w/llt-rs/blob/main/src/ring_buffer/mod.rs UnsafeCell<T> + Sync & Send + raw pointer(!) C 言語とのハイブリッドでも使えるリングバッファ [UnsafeCell<MaybeUninit<T>>] -> *mut MaybeUninit<T> https://github.com/debasishg/ringmpsc-rs/blob/main/crates/ringmpsc/src/ring.rs
  12. Rust の「内部可変性」型と unsafe 内部可変性とは これも必要!(次で説明) 不変参照を通して内部の値を変更できること UnsafeCell<T> と sync::atomic が基本

    複数アクティビティの同時アクセス方法 UnsafeCell<T> + Sync & Send 素直なリングバッファの実装例 [UnsafeCell<MaybeUninit<T>>] https://github.com/aodr3w/llt-rs/blob/main/src/ring_buffer/mod.rs UnsafeCell<T> + Sync & Send + raw pointer(!) C 言語とのハイブリッドでも使えるリングバッファ [UnsafeCell<MaybeUninit<T>>] -> *mut MaybeUninit<T> https://github.com/debasishg/ringmpsc-rs/blob/main/crates/ringmpsc/src/ring.rs
  13. Rust の「内部可変性」型と unsafe UnsafeCell<T> は内部可変だけじゃない 安全な内部可変性を実現するプリミティブ - UnsafeCell<T> struct の定義に付けられた

    #[lang = “unsafe_cell”] 属性 - この struct が包む T へのアクセスは、get() が返す *mut T 経由のみ UnsafeCell<T> および、それを内包する型の 不変参照に対して、コンパイラの最適化を 抑制する! 注意:UnsafeCell<T>.get() で得た ポインタ (*mut T) やポインタから 得た不変参照を、もとの UnsafeCell<T> から離れたブロッ ク(関数)に渡すべきでない!
  14. Rust の「内部可変性」型と unsafe UnsafeCell<T> は内部可変だけじゃない 安全な内部可変性を実現するプリミティブ - UnsafeCell<T> struct の定義に付けられた

    #[lang = “unsafe_cell”] 属性 - この struct が包む T へのアクセスは、get() が返す *mut T 経由のみ sync::atomic の働き UnsafeCell<T> および、それを内包する型の 不変参照に対して、コンパイラの最適化を 抑制する! UnsafeCell<T> を補うもの - 単純型の値に対するアトミック操作 ← Sync trait を実装 (※unsafe) - Ordering enum によるメモリバリア 注意:UnsafeCell<T>.get() で得た ポインタ (*mut T) やポインタから 得た不変参照を、もとの UnsafeCell<T> から離れたブロッ ク(関数)に渡すべきでない!
  15. Rust の「内部可変性」型と unsafe UnsafeCell<T> は内部可変だけじゃない 安全な内部可変性を実現するプリミティブ - UnsafeCell<T> struct の定義に付けられた

    #[lang = “unsafe_cell”] 属性 - この struct が包む T へのアクセスは、get() が返す *mut T 経由のみ sync::atomic の働き UnsafeCell<T> および、それを内包する型の 不変参照に対して、コンパイラの最適化を 抑制する! UnsafeCell<T> を補うもの - 単純型の値に対するアトミック操作 ← Sync trait を実装 (※unsafe) - Ordering enum によるメモリバリア Atomic<T> は UnsafeCell<T> を内包しているが、 CPU 命令を使って補強:Sync な性質を達成 注意:UnsafeCell<T>.get() で得た ポインタ (*mut T) やポインタから 得た不変参照を、もとの UnsafeCell<T> から離れたブロッ ク(関数)に渡すべきでない!
  16. Rust の「内部可変性」型と unsafe UnsafeCell<T> は内部可変だけじゃない 安全な内部可変性を実現するプリミティブ - UnsafeCell<T> struct の定義に付けられた

    #[lang = “unsafe_cell”] 属性 - この struct が包む T へのアクセスは、get() が返す *mut T 経由のみ sync::atomic の働き UnsafeCell<T> を補うもの - 単純型の値に対するアトミック操作 ← Sync trait を実装 (※unsafe) - Ordering enum によるメモリバリア これこそが、ロックフリーなリングバッファの 実装に必要な理由!
  17. Rust の「内部可変性」型と unsafe UnsafeCell<T> は内部可変だけじゃない 安全な内部可変性を実現するプリミティブ - UnsafeCell<T> struct の定義に付けられた

    #[lang = “unsafe_cell”] 属性 - この struct が包む T へのアクセスは、get() が返す *mut T 経由のみ sync::atomic の働き UnsafeCell<T> を補うもの - 単純型の値に対するアトミック操作 ← Sync trait を実装 (※unsafe) - Ordering enum によるメモリバリア 書き込みスロットと読み出しスロットを 指すインデックスに Atomic<T> を使い、 メモリバリア付きでインデックス値の読 み出しと更新を行うことで、CPU のアウ トオブオーダー実行による害を防ぐ。 スロット内容の読み出しや 書き込みが実行されるより も先にインデックスが更新 されてしまうことを防止! これこそが、ロックフリーなリングバッファの 実装に必要な理由!
  18. Rust の Marker trait と注意点 Sync と Send って何? どちらも、Marker

    trait(後述) 型 T が、これらの trait を満たす要件 Send : T の値をスレッド間で移動可能 Sync: T の不変参照をスレッド間で共有可能 つまり、T の不変参照が Send を満たす。
  19. Rust の Marker trait と注意点 Sync と Send って何? どちらも、Marker

    trait(後述) 型 T が、これらの trait を満たす要件 Send : T の値をスレッド間で移動可能 Sync: T の不変参照をスレッド間で共有可能 つまり、T の不変参照が Send を満たす。 Rust の「普通の」型は、Sync と Send を満たす。 ←”Shared XOR Mutable” 規則のおかげ。 例外(の一つ)が内部可変型。Why? :-)
  20. Rust の Marker trait と注意点 Sync と Send って何? どちらも、Marker

    trait(後述) 「内部可変性とは:不変参照を通して内部の値を変更できること。」 型←不変参照を共有したスレッド同士が同時に値を変更するコードが T が、これらの trait を満たす要件 許される。つまり、データ競合のリスクを持つ! Send : T の値をスレッド間で移動可能 Sync: T の不変参照をスレッド間で共有可能 つまり、T の不変参照が Send を満たす。 Rust の「普通の」型は、Sync と Send を満たす。 ←”Shared XOR Mutable” 規則のおかげ。 例外(の一つ)が内部可変型。Why? :-) 排他制御動作を備えない内部可変型は、Sync を満たさない (: !Sync)
  21. Rust の Marker trait と注意点 Sync と Send って何? どちらも、

    Marker trait(後述) 型 T が、これらの trait を満たす要件 Send : T の値をスレッド間で移動可 Sync: T の不変参照をスレッド間で共有可能 リングバッファでの使用例 UnsafeCell<T> を内包したものを Sync と宣言 unsafe impl<T: Send> Sync for RingBuffer<T> {} unsafe impl<T: Send> Send for RingBuffer<T> {} Marker trait は、関連型や操作を持たない。実装は、impl を宣言するだけ。
  22. Rust の Marker trait と注意点 Sync と Send って何? どちらも、

    Marker trait(後述) Marker trait を実装する側は、trait が要求する制約/要件を 型 T が、これらの trait を満たす要件 正しく実装していることを保証する責任を負う。 Send : T の値をスレッド間で移動可 Sync: T の不変参照をスレッド間で共有可能 Sync と Send を満たす通常の型は、これらの trait が 自動的に実装されている (Auto Traits)。 リングバッファでの使用例 UnsafeCell<T> を内包したものを Sync と宣言 unsafe impl<T: Send> Sync for RingBuffer<T> {} unsafe impl<T: Send> Send for RingBuffer<T> {} Marker trait は、関連型や操作を持たない。実装は、impl を宣言するだけ。
  23. Rust の Marker trait と注意点 Sync と Send って何? どちらも、

    Marker trait(後述) Marker trait を実装する側は、trait が要求する制約/要件を 型 T が、これらの trait を満たす要件 正しく実装していることを保証する責任を負う。 Send : T の値をスレッド間で移動可 Sync: T の不変参照をスレッド間で共有可能 Sync と Send は、unsafe な Marker trait。 unsafe な trait は、きわめて少ない。 Sync と Send を満たす通常の型は、これらの trait が 自動的に実装されている (Auto Traits)。 リングバッファでの使用例 Marker trait が unsafe かどうかは、実装が正しくないときに メモリ安全性を壊すかどうかで決まる。 UnsafeCell<T> を内包したものを Sync と宣言 unsafe impl<T: Send> Sync for RingBuffer<T> {} unsafe impl<T: Send> Send for RingBuffer<T> {} Marker trait は、関連型や操作を持たない。実装は、impl を宣言するだけ。
  24. Rust の Marker trait と注意点 そもそも、trait って何だっけ 満たすべき振る舞いや性質の定義・制約 (≠型) メソッドを持つ

    trait: 振舞いの定義・制約 Marker trait: 性質の定義・制約 (※関連型だけを持つ trait は、それ自体だと関係の定義) 型は struct。trait は制約(※dyn trait は型)。 全ての trait は、generics の型パラメータに対する trait 境界に 使うことで制約として作用する。
  25. Rust の Marker trait と注意点 そもそも、trait って何だっけ 満たすべき振る舞いや性質の定義・制約 (≠型) メソッドを持つ

    trait: 振舞いの定義・制約 Marker trait: 性質の定義・制約 (※関連型だけを持つ trait は、それ自体だと関係の定義) メソッドを持つ trait に対しては、各メソッドの事前条件・事後条件・不変条件で要件を 形式的に記述できる。しかし、Marker trait は、それが困難(試みる研究は進んでいる)。 型は struct。trait は制約(※dyn trait は型)。 全ての trait は、generics の型パラメータに対する trait 境界に 使うことで制約として作用する。
  26. Rust の Marker trait と注意点 そもそも、trait って何だっけ 満たすべき振る舞いや性質の定義・制約 (≠型) メソッドを持つ

    trait: 振舞いの定義・制約 Marker trait: 性質の定義・制約 (※関連型だけを持つ trait は、それ自体だと関係の定義) Marker trait を、安易に 自分で定義すべきでない。 メソッドを持つ trait に対しては、各メソッドの事前条件・事後条件・不変条件で要件を 形式的に記述できる。しかし、Marker trait は、それが困難(試みる研究は進んでいる)。 型は struct。trait は制約(※dyn trait は型)。 全ての trait は、generics の型パラメータに対する trait 境界に 使うことで制約として作用する。
  27. Rust の Marker trait と注意点 そもそも、trait って何だっけ 満たすべき振る舞いや性質の定義・制約 (≠型) メソッドを持つ

    trait: 振舞いの定義・制約 Marker trait: 性質の定義・制約 (※関連型だけを持つ trait は、それ自体だと関係の定義) Marker trait を、安易に 自分で定義すべきでない。 メソッドを持つ trait に対しては、各メソッドの事前条件・事後条件・不変条件で要件を 形式的に記述できる。しかし、Marker trait は、それが困難(試みる研究は進んでいる)。 型は struct。trait は制約(※dyn trait は型)。 全ての trait は、generics の型パラメータに対する trait 境界に 使うことで制約として作用する。 ところで、Rust の多相 (polymorphism) は・・・ generics + trait 境界 (: パラメータ多相 + アドホック多相) による静的ディスパッチが基本。 dyn trait (: 部分型多相) による動的ディスパッチとのハイブリッド。
  28. Rust の Marker trait と注意点 そもそも、trait って何だっけ 満たすべき振る舞いや性質の定義・制約 (≠型) メソッドを持つ

    trait: 振舞いの定義・制約 Marker trait: 性質の定義・制約 (※関連型だけを持つ trait は、それ自体だと関係の定義) Marker trait を、安易に 自分で定義すべきでない。 メソッドを持つ trait に対しては、各メソッドの事前条件・事後条件・不変条件で要件を 形式的に記述できる。しかし、Marker trait は、それが困難(試みる研究は進んでいる)。 型は struct。trait は制約(※dyn trait は型)。 実行時コスト(静的ディスパッチはゼロ)と、 実行ファイルサイズのトレードオフを考慮して 全ての trait は、generics の型パラメータに対する trait 境界に 選択 使うことで制約として作用する。 ところで、Rust の多相 (polymorphism) は・・・ generics + trait 境界 (: パラメータ多相 + アドホック多相) による静的ディスパッチが基本。 dyn trait (: 部分型多相) による動的ディスパッチとのハイブリッド。
  29. 静的解析以外の検証ツール 公式の動的検証ツール: Miri Rust のコードの中間表現をインタプリタ実行 未定義動作やメモリ安全性の問題を検出 部分のコードによって起きる問題 から使える nightlyunsafe チャンネルで提供、cargo

    https://github.com/rust-lang/miri メモリアクセス違反(範囲外アクセス、解放済み領域アクセス) 未初期化メモリのアクセス アラインメント違反、型不変条件の違反 データ競合 エイリアシング違反 メモリリーク(※pure safe Rust でも発生し得る; 循環参照など)
  30. 静的解析以外の検証ツール 公式の動的検証ツール: Miri Rust のコードの中間表現をインタプリタ実行 未定義動作やメモリ安全性の問題を検出 部分のコードによって起きる問題 から使える nightlyunsafe チャンネルで提供、cargo

    ロックフリーのリングバッファで unsafe に起因するバグが、 https://github.com/rust-lang/miri Miri のスレッド切り替えタイミング設定機能を使って見つ メモリアクセス違反(範囲外アクセス、解放済み領域アクセス) かった、という例(※TLA+ モデルのモデル検証では見つか 未初期化メモリのアクセス らなかった): アラインメント違反、型不変条件の違反 https://debasishg.github.io/blog/data-race-ring-buffer/ データ競合 エイリアシング違反 メモリリーク(※pure safe Rust でも発生し得る; 循環参照など) ロックフリーのリングバッファの実装例で挙げた 二つのうちの二番目です。
  31. borrow checker と組み込み開発 GC に頼らないことで得られる利点は? 実行速度 実行ファイルサイズ、メモリ使用量 Rust のやり方が GC

    に勝っているところ ↑の利点 データ競合の完全な防止 メモリ以外のリソース解放に糖衣構文不要 Drop trait がライフタイム管理と素直に結合
  32. borrow checker と組み込み開発 復習:ライフタイムと borrow checker 値の参照が、値の生存期間よりも長く使われ ることを防ぐ(dangling pointer を排除)

    エイリアシングによる多重書き込みの防止: 意図しない副作用によるデータ破壊 "Aliasing XOR Mutability"(エイリアスと可変性の排他) 読み出しのみの複数の参照 or 書き込み可能な単一の参照、 という制約を利用したコンパイル時最適化 排他制御の必要性をコンパイル時にチェック
  33. borrow checker と組み込み開発 Rust で割り込みハンドラ 課題:共有データに対する非同期/同時アクセス (A): ライフタイムを `static にして排他制御

    排他制御の対象は、内部可変でなければならない (B): unsafe とロックフリーで実装 「unsafe でできること」を使う borrow checker による制約は変わらない!