Rosy
High-level, zero (or low) cost bindings of Ruby's C API for Rust.
Index
Features
-
Zero or very-low cost abstractions over Ruby's C API.
If speed is of the utmost importance, Rosy has functionality that cannot be beaten performance-wise when using the C API directly. However, this may require carefully writing some
unsafecode.-
For example,
Object::callwill catch any raised Ruby exceptions via theprotectedfamily of functions. On the other hand,Object::call_uncheckedwill allow any thrown exception propagate (which causes a segmentation fault in Rust-land) unlessprotected.Checking for exceptions via
protectedhas a cost associated with it and so it may be best to wrap multiple instances of exception-throwing code with it rather than just one. -
If it is known that no
panic!will occur anywhere within exception-checked code, then callingprotected_no_panicwill emit fewer instructions at the cost of safety. TheFnOncepassed into this function is called within an FFI context, and panicking here is undefined behavior. Panics in a normalprotectedcall are safely caught with the stack unwinding properly.
-
-
Bindings that leverage Rust's type system to the fullest:
-
Rosy makes certain Ruby types generic over enclosing types:
-
When defining methods via
Class::def_methodordef_method!:-
The receiver is statically typed as the generic
Objecttype that theClassis meant for. -
Arguments (excluding the receiver) are generic up to and including 15
AnyObjects. It may also take either anArrayor anAnyObjectpointer paired with a length. These allow for passing in a variable number of arguments.
-
-
-
Safety wherever possible.
Unfortunately, due to the inherent nature of Ruby's C API, this isn't easily achievable without a few compromises in performance. A few factors that cause this are:
-
Ruby's garbage collector de-allocating objects whose references don't live on the stack if they are not
marked. This may lead to a possible use after free. -
Many Ruby functions can throw exceptions. 😓
These cause a segmentation fault in Rust code that's not being called originally from a Ruby context. Functions that may throw an exception are marked as
unsafeor have a safe exception-checking equivalent.
-
Installation
This crate is available on crates.io and can be used by adding the
following to your project's Cargo.toml:
[]
= "0.0.6"
Rosy has functionality that is only available for certain Ruby versions. The following features can currently be enabled:
ruby_2_6
For example:
[]
= "0.0.6"
= ["ruby_2_6"]
Finally add this to your crate root (main.rs or lib.rs):
extern crate rosy;
Usage
Rosy allows you to perform many operations over Ruby objects in a way that feels very natural in Rust.
use String;
// The VM must be initialized before doing anything
init.expect;
let string = Stringfrom;
string.call.unwrap;
assert_eq!;
Defining Ruby Methods
To define a UTF-8-aware method blank? on Ruby's String class, one can very
simply use the def_method! macro. This allows for defining a function that
takes the typed object (in this case String) for the class as its receiver.
use *;
let class = ;
def_method!.unwrap;
let string = Stringfrom;
let result = string.call;
assert_eq!;
Although the macro may feel somewhat magical, it's actually just a zero-cost
wrapper around Class::def_method, which itself is a low-cost abstraction
over rb_define_method_id. To bring the abstraction cost down to absolute zero,
use def_method_unchecked!.
Defining Ruby Classes
Defining a new class is rather straightforward:
let my_object = def.unwrap;
Attempting to define an existing class will result in an error:
let array = def
.unwrap_err
.existing_class
.unwrap;
assert_eq!;
To get an existing named class if it's not a
built-in class,
one should call Class::get:
let my_object = get.unwrap;
And if it's ambiguous as to whether the class already exists, there's the best
of both worlds: Class::get_or_def. This will define a class with the given
name if it doesn't already exist.
let my_object = get_or_def.unwrap;
To define a class within the namespace of a class or module, use
Mixin::def_class.
Defining Ruby Subclasses
The Class::subclass method allows for creating a new class that inherits
from the method receiver's class.
let sub_object = my_object.subclass.unwrap;
assert!;
To define a subclass within the namespace of a class or module, use
Mixin::def_subclass.
Catching Ruby Exceptions
Rust code can be protected from Ruby exceptions very easily.
use ;
let string = Stringfrom;
let result = protected;
assert!;
Authors
-
Creator: Nikolai Vazquez
License
This project is made available under either the conditions of the MIT License or Apache License 2.0 at your choosing.
See LICENSE.md.