Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
M

magnus

> 编程语言
Open source

Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby from Rust.

896 stars0 likes1 views
WebsiteGitHub

About

Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby from Rust.

Magnus

High level Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby code from a Rust binary.

API Docs | GitHub | crates.io

Getting Started | Type Conversions | Safety | Compatibility

Examples

Defining Methods

Using Magnus, regular Rust functions can be bound to Ruby as methods with automatic type conversion. Callers passing the wrong arguments or incompatible types will get the same kind of ArgumentError or TypeError they are used to seeing from Ruby's built in methods.

Defining a function (with no Ruby self argument):

rust
fn fib(n: usize) -> usize {
    match n {
        0 => 0,
        1 | 2 => 1,
        _ => fib(n - 1) + fib(n - 2),
    }
}

#[magnus::init]
fn init(ruby: &magnus::Ruby) -> Result {
    ruby.define_global_function("fib", magnus::function!(fib, 1));
    Ok(())
}

Defining a method (with a Ruby self argument):

rust
fn is_blank(rb_self: String) -> bool {
    !rb_self.contains(|c: char| !c.is_whitespace())
}

#[magnus::init]
fn init(ruby: &magnus::Ruby) -> Result {
    // returns the existing class if already defined
    let class = ruby.define_class("String", ruby.class_object())?;
    // 0 as self doesn't count against the number of arguments
    class.define_method("blank?", magnus::method!(is_blank, 0))?;
    Ok(())
}

Calling Ruby Methods

Some Ruby methods have direct counterparts in Ruby's C API and therefore in Magnus. Ruby's Object#frozen? method is available as magnus::ReprValue::check_frozen, or Array#[] becomes magnus::RArray::aref.

Other Ruby methods that are defined only in Ruby must be called with magnus::ReprValue::funcall. All of Magnus' Ruby wrapper types implement the ReprValue trait, so funcall can be used on all of them.

rust
let s: String = value.funcall("test", ())?; // 0 arguments
let x: bool = value.funcall("example", ("foo",))?; // 1 argument
let i: i64 = value.funcall("other", (42, false))?; // 2 arguments, etc

funcall will convert return types, returning Err(magnus::Error) if the type conversion fails or the method call raised an error. To skip type conversion make sure the return type is magnus::Value.

Wrapping Rust Types in Ruby Objects

Magnus allows you to wrap Rust structs and enums as Ruby objects, enabling seamless interaction between Rust and Ruby. This functionality is ideal for exposing Rust logic to Ruby modules.

Use one of the following approaches to expose a Rust type to Ruby:

  • A convenience macro #[magnus::wrap].
  • More customised approach by implementing the magnus::TypedData trait.

Then this Rust type can be:

  • Returned to Ruby as a wrapped object.
  • Passed back to Rust and automatically unwrapped to a native Rust reference.

Basic Usage

Here’s how you can wrap a simple Rust struct and expose its methods to Ruby:

…

Handling Mutability

Because Ruby's GC manages the memory where your Rust type is stored, Magnus can't bind functions with mutable references. To allow mutable fields in wrapped Rust structs, you can use the newtype pattern with RefCell:

rust
use std::cell::RefCell;

struct Point {
    x: isize,
    y: isize,
}

#[magnus::wrap(class = "Point")]
struct MutPoint(RefCell);

impl MutPoint {
    fn set_x(&self, i: isize) {
        self.0.borrow_mut().x = i;
    }
}

See examples/mut_point.rs for the complete example.

Supporting Subclassing

To enable Ruby subclassing for wrapped Rust types, the type must:

  • Implement the Default trait.
  • Define an allocator.
  • Define an initialiser.
rust
#[derive(Default)]
struct Point {
    x: isize,
    y: isize,
}

#[derive(Default)]
#[wrap(class = "Point")]
struct MutPoint(RefCell);

impl MutPoint {
    fn initialize(&self, x: isize, y: isize) {
        let mut this = self.0.borrow_mut();
        this.x = x;
        this.y = y;
    }
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result {
    let class = ruby.define_class("Point", ruby.class_object()).unwrap();
    class.define_alloc_func::();
    class.define_method("initialize", method!(MutPoint::initialize, 2))?;
    Ok(())
}

Error Handling

Use magnus::Error to propagate errors to Ruby from Rust:

rust
#[magnus::wrap(class = "Point")]
struct MutPoint(RefCell);

impl MutPoint {
    fn add_x(ruby: &Ruby, rb_self: &Self, val: isize) -> Result {
        if let Some(sum) = rb_self.0.borrow().x.checked_add(val) {
            rb_self.0.borrow_mut().x = sum;
            Ok(sum)
        } else {
            return Err(Error::new(ruby.exception_range_error(), "result out of range"));
        }
    }
}

Getting Started

Writing an extension gem (calling Rust from Ruby)

Ruby extensions must be built as dynamic system libraries, this can be done by setting the crate-type attribute in your Cargo.toml.

Cargo.toml

toml
[lib]
crate-type = ["cdylib"]

[dependencies]
magnus = "0.8"

When Ruby loads your extension it calls an 'init' function defined in your extension. In this function you will need to define your Ruby classes and bind Rust functions to Ruby methods. Use the #[magnus::init] attribute to mark your init function so it can be correctly exposed to Ruby.

src/lib.rs

rust
use magnus::{function, Error, Ruby};

fn distance(a: (f64, f64), b: (f64, f64)) -> f64 {
    ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt()
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result {
    ruby.define_global_function("distance", function!(distance, 2));
}

If you wish to package your extension as a Gem, we recommend using the rb_sys gem to build along with rake-compiler. These tools will automatically build your Rust extension as a dynamic library, and then package it as a gem.

Note: The newest version of rubygems does have beta support for compiling Rust, so in the future the rb_sys gem won't be necessary.

my_example_gem.gemspec

ruby
spec.extensions = ["ext/my_example_gem/extconf.rb"]

# needed until rubygems supports Rust support is out of beta
spec.add_dependency "rb_sys", "~> 0.9.39"

# only needed when developing or packaging your gem
spec.add_development_dependency "rake-compiler", "~> 1.2.0"

Then, we add an extconf.rb file to the ext directory. Ruby will execute this file during the compilation process, and it will generate a Makefile in the ext directory. See the rb_sys gem for more information.

ext/my_example_gem/extconf.rb

ruby
require "mkmf"
require "rb_sys/mkmf"

create_rust_makefile("my_example_gem/my_example_gem")

See the rust_blank example for examples of extconf.rb and Rakefile. Running rake compile will place the extension at lib/my_example_gem/my_example_gem.so (or .bundle on macOS), which you'd load from Ruby like so:

lib/my_example_gem.rb

ruby
require_relative "my_example_gem/my_example_gem"

For a more detailed example (including cross-compilation and more), see the rb-sys example project. Although the code in lib.rs does not feature magnus, but it will compile and run properly.

Embedding Ruby in Rust

To call Ruby from a Rust program, enable the embed feature:

Cargo.toml

toml
[dependencies]
magnus = { version = "0.8", features = ["embed"] }

This enables linking to Ruby and gives access to the embed module. magnus::embed::init must be called before calling Ruby and the value it returns must not be dropped until you are done with Ruby. init can not be called more than once.

src/main.rs

rust
use magnus::eval;

fn main() {
    magnus::Ruby::init(|ruby| {
        let val: f64 = eval!(ruby, "a + rand", a = 1)?;

        println!("{}", val);

        Ok(())
    }).unwrap();
}

Type Conversions

Magnus will automatically convert between Rust and Ruby types, including converting Ruby exceptions to Rust Results and vice versa.

These conversions follow the pattern set by Ruby's core and standard libraries, where many conversions will delegate to a #to_ method if the object is not of the requested type, but does implement the #to_ method.

Below are tables outlining many common conversions. See the Magnus api documentation for the full list of types.

Rust functions accepting values from Ruby

See magnus::TryConvert for more details.

Rust function argument accepted from Ruby
i8,i16,i32,i64,isize, magnus::Integer Integer, #to_int
u8,u16,u32,u64,usize Integer, #to_int
f32,f64, magnus::Float Float, Numeric
String, PathBuf, char, magnus::RString, bytes::Bytes‡ String, #to_str
magnus::Symbol Symbol, #to_sym
bool any object
magnus::Range Range
magnus::Encoding, magnus::RbEncoding Encoding, encoding name as a string
Option T or nil
(T, U), (T, U, V), etc [T, U], [T, U, V], etc, #to_ary
[T; N] [T], #to_ary
magnus::RArray Array, #to_ary
magnus::RHash Hash, #to_hash
std::time::SystemTime, magnus::Time, chrono::DateTime§ Time
magnus::Value any object
Vec* [T], #to_ary
HashMap* {K => V}, #to_hash
&T, typed_data::Obj where T: TypedData† instance of ::class()

* when converting to Vec and HashMap the types of T/K,V must be native Rust types.

† see the wrap macro.

‡ when the bytes feature is enabled

§ when the chrono feature is enabled; T can be Utc or FixedOffset.

Rust returning / passing values to Ruby

See magnus::IntoValue for more details, plus magnus::method::ReturnValue and magnus::ArgList for some additional details.

returned from Rust / calling Ruby from Rust received in Ruby

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Rustffirubyrust

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言