🌉

magnusについて調べてみた

に公開

どのようなOSSか

Magnus は、Rust で ruby 拡張を書く、または、Ruby のコードをrust上で動作させることを可能にするライブラリです。
Ruby と Rust の間のバインディングを提供するオープンソースライブラリです。
https://github.com/matsadler/magnus

主な特徴は以下の通り:

  • Ruby 拡張の gem を Rust で書けるようにする。Rust 側で書いた関数やクラスを Ruby から呼び出すことができる。
  • 逆に、Rust プログラムの中から Ruby を呼び出したり、Ruby のオブジェクト・メソッドを使ったりすることも可能(embed 機能)
  • 型変換が自動的に行われる。Ruby と Rust の間でよく使われる型(文字列、数値、配列、ハッシュなど)をスムーズにやりとりできる。
  • Ruby の GC(ガーベジコレクション)やメモリ管理と、Rust の安全性・所有権モデルとの共存を考慮して設計されている。ユーザー側でいくつか注意すべき「安全に使うルール」もある。
  • 対応 Ruby バージョンは少なくとも 3.2、3.3、3.4。それ以前の 3.1 はテスト対象外で、将来的なサポートは保証されていない。

実際に使ってみた

セットアップ

Rust 環境(Rust >= 1.85)を用意。

Cargo.toml に magnus = "0.8" を入れて依存関係を追加。

[dependencies]
magnus = "0.8"

Ruby の拡張 gem としてビルドするか、embed 機能を使うなら Rust バイナリから Ruby を初期化可能な設定を行う。

簡単な例

Rust で HelloMagnusモジュール・greet メソッドを定義し、Ruby 側から HelloMagnus.greet を呼べるようにします。

use magnus::{function, prelude::*};

#[magnus::init]
fn init(ruby: &magnus::Ruby) -> Result<(), magnus::Error> {
    // Ruby 側で HelloMagnus モジュールを作成
    let module = ruby.define_module("HelloMagnus")?;

    // モジュールに Rust 関数を公開
    module.define_singleton_method("greet", function!(greet, 0))?;
    Ok(())
}

// Rust 側で定義する関数
fn greet() -> String {
    "Hello from Rust with magnus!".to_string()
}

Ruby 側ではHelloMagnus.greetを呼び出す:

require_relative "target/release/magnus_example.so"

puts HelloMagnus.greet

※本来は gem としてまとめて使用しますが、今回は学習用のため直接 .so ファイルを読み込んでいます

実行する

まず、ruby のコンテナを作成、諸々セットアップしていきます。

docker run -itd -v $(pwd):/app ruby:3.3 bash
docker exec -it <container_id> bash

# 以下コンテナ内で実行
apt-get update -qq && apt-get install -y clang llvm-dev libclang-dev
# rust install
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

その後、以下のコマンドで build し、 .so ファイルを作成します。

cd /app
cargo build --release

target/release/ 配下に .so ファイルが作成されていることを確認します。
main.rb を実行します。

ruby /app/main.rb
# => Hello from Rust with magnus!

rust で定義した greet メソッドが機能していることが確認できました。

感じたこと・メリット・注意点

メリット

  • 型変換やエラー処理が比較的自然。Rust の Result<_, magnus::Error> を使えば、Ruby 側に例外を投げる形で伝わる。
  • ボイラープレートが少ない。Rutie を以前使っていた人の報告によれば、「引数チェック・型変換・例外ラベル付け」などが Magnus のほうがスマートにできる。
  • Ruby の最新バージョンとの互換性に気を配っている点。Ruby 3.2+ のサポート。

注意点 / デメリット

  • Ruby オブジェクトを Rust 側でヒープ上(例: Vec, Box, HashMap など)に保存すると、Ruby の GC によって原則として安全性が保証されない。使用者が注意して避ける必要あり。
  • ドキュメントやサンプルは充実しているが、それでもマイナーなケース(非常に複雑な型や Ruby の内部モデルを使うようなもの)では予期せぬ問題が出る可能性。
  • ビルド環境の依存性、特に libclang 等の C ライブラリとの連携、クロスコンパイルなど環境構築のハードルが存在。

どのようなケースで有用か

Magnus を使うと力を発揮しそうなシーンを以下に挙げます:

  • Ruby アプリケーションがあり、特定の処理を高速化したい/Rust の性能・安全性を活かしたい部分がある場合。たとえば暗号処理、数値計算、データ変換、大きなバイト列操作など。
  • Ruby 用に gem を作っていて、その中に Rust のライブラリを組み込みたい場合。Rust でロジックを書いて、Ruby ユーザーに gem の形で提供するようなケース。
  • 既存の Rust ライブラリを再利用したいが、Ruby との間で橋渡しをしたい場合。Rust を主に書いていて、Ruby を呼び出す embed の使い方もできるので、両言語混在のアーキテクチャで使える。
  • 型安全性やエラー処理をしっかりさせたいプロジェクトで、Rust 側で可能な限りの検査を行い、Ruby 側のエラーを最小化したいとき。

逆にあまり向かない/デメリットが強くなるケース:

  • 小さくてシンプルな処理のみで、Rust を導入するオーバーヘッド(ビルド、依存、資源管理など)が見合わない場合。
  • Ruby の内部 API やメタプログラミング・動的型性を多用するようなコードで、Rust との間の境界が頻繁に呼ばれたり型変換が多く発生するものでは、その都度コストがかかる。
  • チームに Rust の知識があまりない場合。Rust 側安全性ルールを誤るとクラッシュや予期せぬ挙動を起こすリスクがある。

他の類似 OSS との比較

参考記事:「Rutie と Magnus」 を中心に、いくつかの比較ポイントを整理します。

比較対象 Rutie Magnus
型変換の取り扱い 基本的な型変換はできるが、エラー/型不一致の扱いがユーザ側でのチェックや VM 操作を伴うことがある。
型変換・引数チェックがより「Rust 的なシグネチャ」で書けるようになっていて、 Result<..., magnus::Error> を返すことで Ruby 側に例外を投げる形が自然。

エラー処理 Ruby VM に直接例外を上げたり、型/範囲チェック等で自前にコードを書く必要があることが多い。
エラーを Rust 側で扱いやすくし、例外を発生させるインターフェイスが整理されており、ボイラープレートが減る。

安定性・ Ruby バージョンのサポート 一部新しい Ruby バージョンで Rutie のサポートや互換性に問題が報告されたことがある。
Magnus は Ruby 3.2~3.4 を公式にサポート。

開発体験(Ergonomics) コードを書くときに、Rust/Ruby の境界で「橋渡し」の手間やボイラープレートが比較的多い。
マクロ/ラップ機構(wrap マクロや function!, method!)が整備されていて、構造体のラッピングやメソッド定義が比較的シンプル。

安全性(メモリ管理、GC) 利用者に注意を要する部分があり、GC やライフタイム/所有権の管理で落とし穴がある。 Magnus はそうした怖い部分を可能な限りライブラリでカバーする設計がなされており、安全ガイドラインが文書化されている。とはいえ完全に自動ではない。

コミュニティ/成熟度 Rutie は比較的古くからあり、使っているプロジェクトも複数ある。 Magnus は新しいがアクティブで、スター数も高く、ドキュメント・例が充実していて、開発のペースも速い。

他にも似たライブラリとしては、Helix(Ruby と Rust の連携)などがあるが、Helix はメタプログラミング重視だったり、Ruby / Rust の間のビルド周り・gem 化の観点で異なる設計トレードオフがある。

まとめ

Magnus は、Ruby と Rust を結びつけたい開発者にとって非常に有用なライブラリであり、特に「パフォーマンスを要求する処理」「安全性を確保したい処理」「Rust の既存コードを Ruby から使いたい」ケースでそのメリットが大きいです。
一方で、「単純な拡張」「動的な Ruby の特徴をフル活用しているコード」「Rust の環境構築に慣れていないチーム」などでは導入コストやメンテナンスの難しさが出てくるかもしれません。

参考文献

https://github.com/matsadler/magnus
https://www.hezmatt.org/~mpalmer/blog/2023/04/18/rutie-magnus-rust-extensions-for-ruby.html?utm_source=chatgpt.com

Discussion