MCPcopy Index your code
hub / github.com/danielpclark/rutie

github.com/danielpclark/rutie @v0.9.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.9.0 ↗ · + Follow
616 symbols 1,368 edges 80 files 203 documented · 33%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Rutie

GitHub Actions Status Ruby 2 Compatible Maintenance GitHub contributors license crates.io version

Rutie — /ro͞oˈˌtī/rOOˈˌtI/rüˈˌtaI/

Integrate Ruby with your Rust application. Or integrate Rust with your Ruby application. This project allows you to do either with relative ease.

You are highly encouraged to read the source code for this project. Every method that has been mapped from Ruby for public use in src/class/* is very well documented with example code. This is the best way to take off running with Rutie. There are also integration examples in the examples directory which are based off of this README.

This project is a continuation of: * ruru (licensed MIT) * ruby-sys (licensed MIT)

Index

Using Ruby in Rust

First add the dependency to your Cargo.toml file.

[dependencies]
rutie = "0.8.2"

Then in your Rust program add VM::init() to the beginning of its code execution path and begin to use Rutie.

extern crate rutie;

use rutie::{Object, RString, VM};

fn try_it(s: &str) -> String {
    let a = RString::new_utf8(s);

    // The `send` method returns an AnyObject type.
    let b = unsafe { a.send("reverse", &[]) };

    // We must try to convert the AnyObject
    // type back to our usable type.
    match b.try_convert_to::<RString>() {
        Ok(ruby_string) => ruby_string.to_string(),
        Err(_) => "Fail!".to_string(),
    }
}

#[test]
fn it_works() {

    // Rust projects must start the Ruby VM
    VM::init();

    assert_eq!("selppa", try_it("apples"));
}

fn main() {}

NOTE: Currently in Linux you need to set LD_LIBRARY_PATH to point at the directory of your current Ruby library and in Mac you need to set DYLD_LIBRARY_PATH with that info. You can get the path information with the following command:

ruby -e "puts RbConfig::CONFIG['libdir']"

This should let you run cargo test and cargo run.

Running cargo test should have this test pass.

Using Rust in Ruby

You can start a Ruby project with bundle gem rutie_ruby_example and then once you change into that directory run cargo init --lib. Remove the TODOs from the gemspec file. Add Rutie to the Cargo.toml file and define the lib type.

[dependencies]
rutie = {version="xxx"}

[lib]
name = "rutie_ruby_example"
crate-type = ["cdylib"]

Then edit your src/lib.rs file for your Rutie code.

#[macro_use]
extern crate rutie;

use rutie::{Class, Object, RString, VM};

class!(RutieExample);

methods!(
    RutieExample,
    _rtself,

    fn pub_reverse(input: RString) -> RString {
        let ruby_string = input.
          map_err(|e| VM::raise_ex(e) ).
          unwrap();

        RString::new_utf8(
          &ruby_string.
          to_string().
          chars().
          rev().
          collect::<String>()
        )
    }
);

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Init_rutie_ruby_example() {
    Class::new("RutieExample", None).define(|klass| {
        klass.def_self("reverse", pub_reverse);
    });
}

And that's it for the Rust side. When using the methods! macro or extern functions make sure the method name won't clash with any others. This is why this example is prefixed with pub_.

Now you just need to load the library in Ruby. Add the rutie gem to your gemspec or Gemfile.

# gemspec
spec.add_dependency 'rutie', '~> 0.0.3'

# Gemfile
gem 'rutie', '~> 0.0.3'

And then load the library in your main project file lib/rutie_ruby_example.rb.

require 'rutie_ruby_example/version'
require 'rutie'

module RutieRubyExample
  Rutie.new(:rutie_ruby_example).init 'Init_rutie_ruby_example', __dir__
end

That's all you need to load your Ruby things from Rust. Now to write the test in test/rutie_ruby_example_test.rb:

require "test_helper"

class RutieRubyExampleTest < Minitest::Test
  def test_it_reverses
    assert_equal "selppa", RutieExample.reverse("apples")
  end
end

And to properly test it you will always need to run cargo build --release whenever you make any changes to the Rust code. Run the test with:

cargo build --release; rake test

Or better yet change your Rakefile to always run the cargo build --release before every test suite run. Feel free to change the test input to prove it fails because the above test works as is.

Custom Ruby Objects in Rust

To create a Ruby object in Rust that can be returned directly to Ruby it needs just a few simple things.

Here's an example excerpt of code from FasterPath.

use rutie::types::{ Value, ValueType };
use rutie::{ RString, AnyObject, Object, Class, VerifiedObject };

pub struct Pathname {
    value: Value
}

impl Pathname {
    pub fn new(path: &str) -> Pathname {
        let arguments = [RString::new_utf8(path).to_any_object()];
        let instance = Class::from_existing("Pathname").new_instance(Some(&arguments));

        Pathname { value: instance.value() }
    }

    pub fn to_any_object(&self) -> AnyObject {
        AnyObject::from(self.value())
    }
}

impl From<Value> for Pathname {
    fn from(value: Value) -> Self {
        Pathname { value }
    }
}

impl Object for Pathname {
    #[inline]
    fn value(&self) -> Value {
        self.value
    }
}

impl VerifiedObject for Pathname {
    fn is_correct_type<T: Object>(object: &T) -> bool {
        Class::from_existing("Pathname").case_equals(object)
    }

    fn error_message() -> &'static str {
        "Error converting to Pathname"
    }
}

If the class does not yet exist in Ruby you'll need to account for creating it before generating a new instance of it. This object is now compatible to be returned into Ruby directly from Rust/Rutie. Note that this definition is merely a Rust compatible representation of the Ruby object and doesn't define any Ruby methods which can be used from Ruby.

Variadic Functions / Splat Operator

A preferred way to integrate a dynamic amount of parameters has not yet been implemented in Rutie, but you can still manage to get it done in the following way.

use rutie::{AnyObject, Array};
use rutie::types::{Argc, Value};
use rutie::util::str_to_cstring;
use rutie::rubysys::class;
use std::mem;

pub extern fn example_method(argc: Argc, argv: *const AnyObject, _rtself: AnyObject) -> AnyObject {
    let args = Value::from(0);

    unsafe {
        let p_argv: *const Value = mem::transmute(argv);

        class::rb_scan_args(
            argc,
            p_argv,
            str_to_cstring("*").as_ptr(),
            &args
        )
    };

    let arguments = Array::from(args);

    let output = // YOUR CODE HERE.  Use arguments as you see fit.

    output.to_any_object()
}

This style of code is meant to be used outside of the methods! macro for now. You may place this method on a class or module as you normally would from a methods! macro definition.

#[macro_use]
extern crate rutie;

use rutie::{Class, Object, VM};

class!(Example);

// Code from above

fn main() {
    VM::init();
    Class::new("Example", None).define(|klass| {
        klass.def("example_method", example_method);
    });
}

The Rutie project has in its plans to remove the need for anyone to write unsafe code for variadic support and will likely be updating the methods! macro to support this natively.

Migrating from Ruru to Rutie

<0.1

For using Rutie versions less than 0.1 the change is simple. Replace all occurrences of the string ruru with rutie in your program. And if you would like to use ruby-sys code from Rutie rather than requiring ruby-sys you can change all existing references to ruby_sys to rutie::rubysys.

0.1

You will have additional considerations to change like Error being removed. For that; change instances of type ruru::result::Error to rutie::AnyException.

0.2

Migrated parse_arguments from VM to util.

0.3

Internal changes util from binding and rubysys have been replaced to reduce confusion and reduce duplication.

Safety — The Rutie Philosophy vs The Rust Philosophy on Safety

I'm writing this section to bring to light that, as of this writing, the safety that Rust likes to guarantee for its crates and the Rutie crate aren't currently aligned. The typical Rust safety for libraries wrapping C code is to have one unsafe crate with a -sys extension in its name and then a crate that wraps that to make it safe.

Rutie is an official fork of the project Ruru and because of this a great deal of the decisions in design for the project remain unchanged. Rutie also brought in the ruby-sys crate and treats it as an internal private API/module; and yet shares it openly for other developers to have full control to design their own API on top of it.

One of the glaring things that Rutie has that goes against the Rust Philosophy on Safety is that any of the methods that call Ruby code, can potentially raise an exception, and don't return the type Option<AnyObject, AnyException> will panic when an exception is raised from Ruby… which kills the application process running. The way to avoid panics is simple: either guarantee the Ruby code you're running will never raise an exception, or Handling exceptions raised from Ruby in Rust code with "protect" methods that return the type Option<AnyObject, AnyException>. Anyone can implement this safety by reading and understanding how the protect methods are written in this library and working with them.

The important thing to consider as to “why doesn't every method guarantee the safety as Rust would prescribe to?” is that exception handling in Ruby is not a zero cost abstraction. So there is a cost in performance when you need to implement it. One can easily argue that the guarantee of safety is far more important than leaving the risk in the hands of inexperienced developers. But one could also argue that it is better to leave the choice of performance cost, and the fact that exception capturing is occasionally unnecessary, up to the developer. Seeing how the legacy of design decisions is largely inherited this project leans towards the latter argument where the choice of being absolutely safe everywhere vs some extra speed in performance is up to the developer.

I'm not opposed to this project being 100% safe, but that will be a major change and a totally different API with many decisions that need to come into play. Also since this project doesn't strictly adhere to Rust safety principles, as a crate library would be expected to be, this project will not reach the stable 1.0 release as the idea of stability and safety are hand in hand.

I do like safety guarantees and as much as possible new features and language APIs will be built towa

Extension points exported contracts — how you extend this code

VerifiedObject (Interface)
Interface for safe conversions between types This trait is required by `Object::convert_to()` function. All built-in t [19 …
src/class/traits/verified_object.rs
DataTypeWrapper (Interface)
(no doc)
src/typed_data/data_type_wrapper.rs
Object (Interface)
`Object` Trait consists methods of Ruby `Object` class. Every struct like `Array`, `Hash` etc implements this trait. ` [19 …
src/class/traits/object.rs
TryConvert (Interface)
Implicit conversion or `nil`. This is meant for “implicit conversions” much like Ruby's: `Array.try_convert` `Hash.try [2 …
src/class/traits/try_convert.rs
Exception (Interface)
Descendants of class Exception are used to communicate between Kernel#raise and rescue statements in `begin ... end` blo [1 …
src/class/traits/exception.rs
EncodingSupport (Interface)
(no doc) [1 implementers]
src/class/traits/encoding_support.rs

Core symbols most depended-on inside this repo

new
called by 68
src/binding/hash.rs
value
called by 38
src/class/traits/object.rs
value
called by 32
src/class/hash.rs
value
called by 27
src/class/string.rs
value
called by 20
src/class/array.rs
value
called by 19
src/class/class.rs
str_to_num
called by 18
src/class/integer.rs
equals
called by 18
src/class/traits/object.rs

Shape

Method 354
Function 210
Class 40
Enum 6
Interface 6

Languages

Rust98%
Ruby2%

Modules by API surface

src/class/array.rs32 symbols
src/class/traits/object.rs30 symbols
src/class/string.rs28 symbols
src/class/class.rs26 symbols
src/class/module.rs25 symbols
src/binding/vm.rs24 symbols
src/class/vm.rs23 symbols
src/binding/class.rs22 symbols
src/rubysys/value.rs21 symbols
src/util.rs18 symbols
src/class/integer.rs18 symbols
build.rs18 symbols

For agents

$ claude mcp add rutie \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page