Rosy

Build status Platforms Lines of code crates.io downloads docs.rs MIT or Apache 2.0

High-level, zero (or low) cost bindings of [Ruby]'s C API for [Rust].

Index

Features

Installation

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.7"

Rosy has functionality that is only available for certain Ruby versions. The following features can currently be enabled:

For example:

toml [dependencies.rosy] version = "0.0.7" features = ["ruby_2_6"]

Finally add this to your crate root (main.rs or lib.rs):

rust extern crate rosy;

Usage

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"); ```

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.

```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 Ruby Classes

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].

Defining Ruby Subclasses

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].

Catching Ruby Exceptions

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()); ```

Platform Support

Rosy uses [aloxide] to find and link Ruby during its build phase. Because of that, Rosy's platform support is totally dependent on it. Changes that fix issues with linking (or in the future, building) Ruby should be submitted to that library for use in this one.

To work locally on aloxide and Rosy in combination with each other, change Rosy's [Cargo.toml] like so:

toml [build-dependencies] aloxide = { path = "path/to/aloxide", version = "0.0.8", default-features = false }

Library Comparison

Like with most technologies, Rosy isn't the first of its kind.

Rosy vs Helix

[Helix] is a Rust library built on top of macros. Interaction with the Ruby runtime is done via a ruby! macro which features a [DSL] that's a mix between Rust and Ruby syntax. To those coming from Ruby, they'll feel right at home. However, those coming from Rust may feel that the macro is a little too magical.

Unlike Helix, for each of Rosy's macros, there's an alternative approach that can be taken purely through types, traits, and functions. Rosy is designed to be convenient and high-level while trying not to hide the low-level details that can allow you to write better-optimized code. This is parallel to the way that Rust acts as a high-level language.

Rosy vs Rutie

[Rutie] is a Rust library that tries to be less magical than Helix. It is a continuation of the work done on [ruru], which is no longer maintained as of the end of 2017. Rutie takes an excellent approach to wrapping Ruby's C API in Rust by exposing Ruby classes as Rust structs. This inspired the layout and design of Rosy to some extent.

However, unlike Rutie, Rosy doesn't expose the lower-level C bindings. The reasoning is that if certain functionality is missing from Rosy, it should be added to the core library by either requesting it through an issue or submitting a pull request with an implementation.

Also, unlike Rutie, Rosy marks all exception-throwing functions as unsafe. Not handling a Ruby exception from Rust-land results in a [segmentation fault]. One of the major reasons that some people choose to write Rust over C is to get away from these. The Rust philosophy is that safe code should not be able to trigger a segmentation fault. Just like with Rutie, Rosy allows Rust code to be [protected] against raised exceptions.

Authors

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.


Congrats on making it this far! ʕノ•ᴥ•ʔノ🌹

Back to top