High-level, zero (or low) cost bindings of [Ruby]'s C API for [Rust].
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 unsafe
code.
Object::call
] will catch any raised Ruby exceptions via
the [protected
] family of functions. On the other hand,
[Object::call_unchecked
] will allow any thrown exception propagate
(which causes a segmentation fault in Rust-land) unless [protected
].Checking for exceptions via [protected
] has 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.
panic!
] will occur anywhere within
exception-checked code, then calling [protected_no_panic
] will emit
fewer instructions at the cost of safety. The [FnOnce
] passed into this
function is called within an FFI context, and panicking here is undefined
behavior. Panics in a normal [protected
] call 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:
[Array
] is generic over [Object
] types that it contains, defaulting to
[AnyObject
].
[Hash
] is generic over [Object
] keys and values, both defaulting to
[AnyObject
].
[Class
] is generic over an [Object
] type that it may instantiate via
[Class::new_instance
].
When defining methods via [Class::def_method
] or [def_method!
]:
The receiver is statically typed as the generic [Object
] type that the
[Class
] is meant for.
Arguments (excluding the receiver) are generic up to and including 15
[AnyObject
]s. It may also take either an [Array
] or an [AnyObject
]
pointer 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 [mark
]ed. 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 unsafe
or have a safe exception-checking equivalent.
This crate is available on crates.io and can be used by adding the
following to your project's [Cargo.toml
]:
toml
[dependencies]
rosy = "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:
toml
[dependencies.rosy]
version = "0.0.6"
features = ["ruby_2_6"]
Finally add this to your crate root (main.rs
or lib.rs
):
rust
extern crate rosy;
Rosy allows you to perform many operations over Ruby objects in a way that feels very natural in Rust.
```rust use rosy::String;
// The VM must be initialized before doing anything rosy::vm::init().expect("Could not initialize Ruby");
let string = String::from("hello\r\n"); string.call("chomp!").unwrap();
assert_eq!(string, "hello"); ```
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.
```rust use rosy::prelude::*;
let class = Class::of::
rosy::defmethod!(class, "blank?", |this: String| { this.iswhitespace() }).unwrap();
let string = String::from(" \r\n"); let result = string.call("blank?");
assert_eq!(result.unwrap(), true); ```
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 a new class is rather straightforward:
rust
let my_object = Class::def("MyObject").unwrap();
Attempting to define an existing class will result in an error:
```rust let array = Class::def("Array") .unwraperr() .existingclass() .unwrap();
assert_eq!(array, Class::array()); ```
To get an existing named class if it's not a
built-in class,
one should call [Class::get
]:
rust
let my_object = Class::get("MyObject").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.
rust
let my_object = Class::get_or_def("MyObject").unwrap();
To define a class within the namespace of a class or module, use
[Mixin::def_class
].
The [Class::subclass
] method allows for creating a new class that inherits
from the method receiver's class.
```rust let subobject = myobject.subclass("MyObjectChild").unwrap();
assert!(subobject < myobject); ```
To define a subclass within the namespace of a class or module, use
[Mixin::def_subclass
].
Rust code can be [protected
] from Ruby exceptions very easily.
```rust use rosy::{Object, String, protected};
let string = String::from("¡Hola!");
let result = protected(|| unsafe { string.callunchecked("likespie?") });
assert!(result.unwraperr().isnomethoderror()); ```
Creator: Nikolai Vazquez
This project is made available under either the conditions of the MIT License or Apache License 2.0 at your choosing.
See LICENSE.md
.