【ゼロからのOS自作入門】MikanOSをRustに移植する 4章

前回

ymzkmtfm.hatenablog.com

続きです。

https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E3%81%AEOS%E8%87%AA%E4%BD%9C%E5%85%A5%E9%96%80-%E5%86%85%E7%94%B0-%E5%85%AC%E5%A4%AA/dp/4839975868/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E3%81%AE&qid=1619090804&sr=8-1

↑の4章をrustに移植しました。 3章で頭を悩ませた内容の解決策とかが書いてあったりしてなるほどなぁという感じでした。

成果物

github.com

本物と違うところ

C++の機能を使ってない

Rustで作ってるのでそりゃそうです。
本物のMikanOSでは配置newと継承を使って実行時に RGBResv8bitPerColorPixelWriter と BGRResv8bitPerColorPixelWriter の生成を切り替えています。
しかし、Rust には配置newも継承も(たぶん)ありません。
(そもそもクラスがないので比較しずらいのですが…)
そのため以下の様な実装は怒られます。

let v = match 式 {
    パターン1 => A::new(),  //A型を返す
    パターン2 => B::new(),  //B型を返す
}  //エラー:vの型が決定できない

これを避けるために一番最初に思いつく実装は以下の様な感じでしょう。
(後述の理由でこの実装は避けました)

struct Hoge<T: some_trait> {
  fuga: Box<T>,
}

Tは trait という Rust 特有の機能、Box は T を実装した構造体へのスマートポインタです。
traitはほぼ Interface ですが、Rust には構造体と構造体のふるまいしか定義できません。
(クラスがないので Interface と呼ぶのは多分不適切)
そのため構造体に共通のふるまいは trait を使って抽象化します。
上記の実装は T というふるまいを持った複数の構造体を Hoge という単一の型で実装できます。
このような trait を使った構造体はトレイトオブジェクトと呼ばれます。
ただし、トレイトオブジェクトにはメモリの動的確保が必須です。
(ふるまいしか書いてないので構造体のサイズが静的にわからない)
(Rustでは動的に型を解決する手法を動的ディスパッチと呼びます。たぶん)
現状メモリの動的確保ができないのでトレイトオブジェクトは使えません。
そこで今回実装したのは以下の様な実装です。

pub enum Writer {
    Rgb(RGBWriter),
    Bgr(BGRWriter),
}

impl Writer {
    pub fn vertical_resolution(&self) -> usize {
        match self {
            Writer::Rgb(w) => w.vertical_resolution(),
            Writer::Bgr(w) => w.vertical_resolution(),
        }
    }

    pub fn horizontal_resolution(&self) -> usize {
        match self {
            Writer::Rgb(w) => w.horizontal_resolution(),
            Writer::Bgr(w) => w.horizontal_resolution(),
        }
    }

    pub fn write(&self, x: usize, y: usize, c: PixelColor) {
        match self {
            //安全性はframe_buffer_base依存
            Writer::Rgb(w) => unsafe { w.write(x, y, c) },
            Writer::Bgr(w) => unsafe { w.write(x, y, c) },
        }
    }
}

この実装は Enum を使っています。 Rust の Enum はとても柔軟なデータ構造になっていて、構造体を列挙することができます。
(この柔軟性の由来は関数型言語にあるとかなんとか)
しかも Enum にメソッドや trait を定義することすら可能です。
(詳細は Enumを定義する - The Rust Programming Language 日本語版 )
しかもこの実装は静的に可能です。(列挙しているので構造体のサイズは全て把握できる)
もちろん使うたびにパターンマッチングしなければいけないので条件分岐の数はあまり減っていませんが、それを差し引いても現状に照らし合わせると良いこと尽くめです。
(さらに、本当かどうかはわかりませんが trait の動的ディスパッチより Enum の方が早いという噂もあります。ただ理由はよくわかりませんでした。メモリの動的確保が遅いんでしょうか?)
Rust でトレイトオブジェクトと enum のディスパッチ速度比較 - Qiita
enum_dispatch - Speed up your dynamic dispatched trait method calls by up to 10x : rust
というわけでメモリの動的確保ができるまでちょっとした(列挙できる程度の)多相性は Enum でやっていこうと思います。

感想

・そろそろC++とRustの違いが大きくなってくるのかな?
・機能が増えていくの楽しい