xberg-io /
crawlberg
High-performance web crawling engine with bindings for 11 languages
85/100 healthLoading repository data…
matsadler / repository
Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby from Rust.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
High level Ruby bindings for Rust. Write Ruby extension gems in Rust, or call Ruby code from a Rust binary.
Getting Started | Type Conversions | Safety | Compatibility
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):
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<(), Error> {
ruby.define_global_function("fib", magnus::function!(fib, 1));
Ok(())
}
Defining a method (with a Ruby self argument):
fn is_blank(rb_self: String) -> bool {
!rb_self.contains(|c: char| !c.is_whitespace())
}
#[magnus::init]
fn init(ruby: &magnus::Ruby) -> Result<(), Error> {
// 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(())
}
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.
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.
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:
#[magnus::wrap].magnus::TypedData trait.Then this Rust type can be:
Here’s how you can wrap a simple Rust struct and expose its methods to Ruby:
use magnus::{function, method, prelude::*, Error, Ruby};
#[magnus::wrap(class = "Point")]
struct Point {
x: isize,
y: isize,
}
impl Point {
fn new(x: isize, y: isize) -> Self {
Self { x, y }
}
fn x(&self) -> isize {
self.x
}
fn y(&self) -> isize {
self.y
}
fn distance(&self, other: &Point) -> f64 {
(((other.x - self.x).pow(2) + (other.y - self.y).pow(2)) as f64).sqrt()
}
}
#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
let class = ruby.define_class("Point", ruby.class_object())?;
class.define_singleton_method("new", function!(Point::new, 2))?;
class.define_method("x", method!(Point::x, 0))?;
class.define_method("y", method!(Point::y, 0))?;
class.define_method("distance", method!(Point::distance, 1))?;
Ok(())
}
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:
use std::cell::RefCell;
struct Point {
x: isize,
y: isize,
}
#[magnus::wrap(class = "Point")]
struct MutPoint(RefCell<Point>);
impl MutPoint {
fn set_x(&self, i: isize) {
self.0.borrow_mut().x = i;
}
}
See examples/mut_point.rs for the complete example.
To enable Ruby subclassing for wrapped Rust types, the type must:
Default trait.#[derive(Default)]
struct Point {
x: isize,
y: isize,
}
#[derive(Default)]
#[wrap(class = "Point")]
struct MutPoint(RefCell<Point>);
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<(), Error> {
let class = ruby.define_class("Point", ruby.class_object()).unwrap();
class.define_alloc_func::<MutPoint>();
class.define_method("initialize", method!(MutPoint::initialize, 2))?;
Ok(())
}
Use magnus::Error to propagate errors to Ruby from Rust:
#[magnus::wrap(class = "Point")]
struct MutPoint(RefCell<Point>);
impl MutPoint {
fn add_x(ruby: &Ruby, rb_self: &Self, val: isize) -> Result<isize, Error> {
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"));
}
}
}
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
[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
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<(), Error> {
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
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
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
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.
To call Ruby from a Rust program, enable the embed feature:
Cargo.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
use magnus::eval;
fn main() {
magnus::Ruby::init(|ruby| {
let val: f64 = eval!(ruby, "a + rand", a = 1)?;
println!("{}", val);
Ok(())
}).unwrap();
}
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_<type> method if the object is
not of the requested type, but does implement the #to_<type> method.
Below are tables outlining many common conversions. See the Magnus api documentation for the full list of types.
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> | 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<T>§ | Time |
magnus::Value |
Selected from shared topics, language and repository description—not editorial ratings.
xberg-io /
High-performance web crawling engine with bindings for 11 languages
85/100 healthxberg-io /
Generate fully-typed, lint-clean language bindings for Rust libraries across 16 languages
81/100 healthkonstin /
A framework for generating bindings from Rust to python, helix and javascript
godaddy /
Application-layer envelope encryption with automatic key rotation. Rust core with bindings for Node.js, Python, .NET, Java, Ruby, and Go.
63/100 healthjacksmithinsulander /
Rust C API study: one Rust core, stable C ABI, bindings for C, Go, Python, Ruby, TypeScript, Rust FFI, and WASM.
49/100 health