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

Rust で作ったネイティブを Ruby から使う

Avatar for yo_waka yo_waka
August 26, 2026

Rust で作ったネイティブを Ruby から使う

2026/08/26 gotanda.rb での発表資料です

Avatar for yo_waka

yo_waka

August 26, 2026

More Decks by yo_waka

Other Decks in Technology

Transcript

  1. HTML to PDF をやるやつを作っています • https://github.com/waka/sghtmltopdf • wkhtmltopdf という QtWebkit

    を組み込んだ HTML を PDF に変換するプログラムと、 それを ActionView の DSL から使える wicked_pdf という gemを使っていた • wkhtmltopdf は QtWebkit のバージョンアップに追随できず、2023 年に残念ながらアーカイブされてし まった • ⾃分は ActionView の DSL で PDF を出⼒できるというのがとても気に⼊っていたので、より⾼速で使い勝 ⼿を⾼めたものを作ろうと思った 3
  2. Rust製 のネイティブビルドを Ruby から使いたい • sghtmltopdf は HTML to PDF

    をやるためのライブラリが優秀だったため、 Rust で書かれている ◦ HTML/CSS のパースは Servo プロジェクト(※) が提供している crate を活⽤ ▪ • html5ever, css-parser ◦ PDF の書き出しは pdf-writer crate を活⽤ ◦ パース結果を PDF の レイアウトに変換するエンジンやフォント表⽰周りが⾃作部分 wicked_pdf 同様に Ruby から使えるようにしたい = Rust のネイティブビルドを呼び出す必要がある ※ Mozilla 製の Firefox の HTML/CSS レンダリングエンジン。今は Linux Foundation に移管されている 4
  3. Rust で Ruby のネイティブ拡張を書くためのライブラリ その1 rb-sys • Ruby C API

    の⽣バインディング + ビルド基盤を提供 ◦ Ruby オブジェクトへの参照を表す型として VALUE が提供される ▪ 実体はポインタ幅の符号なし整数(詳細は次の次のページ) • bindgen で⽣成した 「rb_sys::rb_funcall」など⽣の関数‧型定義を提供 • 「rb_sys/mkmf」を提供して、extconf.rb から Cargo プロジェクトをビルド可能にする ◦ mkmf = make makefile ◦ gem インストール時に RubyGems が拡張ディレクトリの extconf.rb を実⾏する ◦ create_rust_makefile で cargo build して .so ファイルを作る ◦ .so ファイルを Ruby から読めるディレクトリに配置する Makefile を作成して Rubygems が make する 5
  4. Rust で Ruby のネイティブ拡張を書くためのライブラリ その2 magnus • rb-sys を抽象化して、いい感じに使えるようにしてくれるライブラリ ◦

    • Rust の型と Ruby の型の⾃動変換 ◦ • 「RString」「RArray」「RHash」「Symbol」など型付きの Ruby オブジェクトとして扱える fn(u64) -> String みたいな関数をそのまま Ruby 側のメソッドとして登録できる #[magnus::wrap] マクロで Rust の struct をRuby オブジェクトとして公開できたり 6
  5. rb-sys と magnus の関係 • rb-sys が提供する VALUE とは ◦

    ◦ • pub type VALUE = usize; で定義される ▪ 下位 1bit が 1 => Fixnum ▪ 下位 8bit が 0x0e => Symbol ▪ 0x08 => nil 型システムの助けが⼀切なくなるので、このまま扱うのはきつい>< ▪ let a: VALUE = /* String */ ▪ let b: VALUE = /* Array */ ▪ let c = a + b; // これはコンパイル通るけど実⾏時に Segfault になる この VALUE を型付きで扱えるようにしてくれるのが magnus ◦ RString / RArray などの型で扱えるようにしてくれる ◦ rb-sys に直接渡せるように、 ⽣のVALUE を取り出す as_rb_value() も使える 7
  6. Global VM Lock(GVL)に注意 • GVL = 1 プロセス内で Ruby のコードを実⾏できるスレッドを常に

    1 つに制限するロック ◦ • Webアプリのようなマルチスレッド環境だと他のスレッドが⽌まっちゃう 普段意識することはあまりない ◦ IO#read / write, Socket 等の標準 IO は Ruby 内で GVL を⾃動で解放してくれる ◦ pg gem, mysql2 gem なんかは 内部で「rb_thread_call_without_gvl 」という GVL 解放メソッドを 呼んでGVL を解放してくれている • C 拡張や ネイティブを呼び出す、且つ IO が絡む gem を作る場合は自分で意識しないといけない 8
  7. magnus (rb-sys)でGVLを外す場合 • magnus は GVL 解放する API を提供していない •

    ⾃前で「rb_thread_call_without_gvl 」を呼ぶしかない ◦ デザインパターンみたいなのがある ◦ 多分 => の実装にだいたいなるはず ◦ 気になる⼈は sghtmltopdf のリポジトリ⾒てみて ください! 10
  8. Ruby <-> Rust の⼝はシンプルにしよう • GVL 解放している境界は分かりやすくした⽅がいい ◦ 解放前: Ruby

    オブジェクトを Rust オブジェクトに変換 ◦ 解放後: Rust オブジェクトしか触らない ▪ ◦ 型で Rust オブジェクトのみ制限できる安心感 右のコード例では Ruby からは RStringを 受け取り、Rust の Vec に変換して、gvl 解放している • ↑ によって GC の影響をほぼ意識しなくてよくなります ◦ Ruby オブジェクトを持っちゃうと、 GC で棄てられるリスクがある 11