[][src]Crate artichoke_backend

artichoke-backend

artichoke-backend crate provides a Ruby interpreter. It currently is implemented with mruby bindings exported by the [sys] module.

Execute Ruby Code

artichoke-backend crate exposes several mechanisms for executing Ruby code on the interpreter.

Evaling Source Code

artichoke-backend crate exposes eval on the State with the Eval trait. Side effects from eval are persisted across invocations.

use artichoke_backend::eval::Eval;
use artichoke_core::value::Value as ValueLike;

let interp = artichoke_backend::interpreter().unwrap();
let result = interp.eval("10 * 10").unwrap();
let result = result.try_into::<i64>();
assert_eq!(result, Ok(100));

Calling Ruby Functions from Rust

The ValueLike trait exposes a funcall interface which can call Ruby functions on a Value using a String function name and a Vec<Value> of arguments. funcall takes a type parameter bound by TryConvert and converts the result of the function call to a Rust type (which may be Value or another "native" type).

artichoke-backend limits functions to a maximum of 16 arguments.

Virtual Filesystem and Kernel#require

The artichoke-backend State embeds an in-memory virtual Unix filesystem. The VFS stores Ruby sources that are either pure Ruby, implemented with a Rust File, or both.

artichoke-backend crate implements Kernel#require and Kernel#require_relative which loads sources from the VFS. For Ruby sources, the source is loaded from the VFS as a Vec<u8> and evaled with Eval::eval_with_context. For Rust sources, File::require methods are stored as custom metadata on File nodes in the VFS.

use artichoke_backend::eval::Eval;
use artichoke_backend::load::LoadSources;
use artichoke_core::value::Value as ValueLike;

let mut interp = artichoke_backend::interpreter().unwrap();
let code = "
def source_location
  __FILE__
end
";
interp.def_rb_source_file("source.rb", code).unwrap();
interp.eval("require 'source'").unwrap();
let result = interp.eval("source_location").unwrap();
let result = result.try_into::<&str>().unwrap();
assert_eq!(result, "/src/lib/source.rb");

Embed Rust Objects in mrb_value

The mrb_value struct is a data type that represents a Ruby object. The concrete type of an mrb_value is specified by its type tag, an mrb_vtype enum value.

One mrb_vtype is MRB_TT_DATA, which allows an mrb_value to store an owned c_void pointer. artichoke-backend crate leverages this to store an owned copy of an Rc<RefCell<T>> for any T that implements RustBackedValue.

RustBackedValue provides two methods for working with MRB_TT_DATA:

These mrb_values with type tag MRB_TT_DATA can be used to implement Ruby Classes and Modules with Rust structs. An example of this is the Regexp class which wraps an Oniguruma regex provided by the [onig] crate.

#[macro_use]
extern crate artichoke_backend;

use artichoke_backend::convert::{Convert, RustBackedValue, TryConvert};
use artichoke_backend::def::{rust_data_free, ClassLike, Define};
use artichoke_backend::eval::Eval;
use artichoke_backend::file::File;
use artichoke_backend::load::LoadSources;
use artichoke_backend::sys;
use artichoke_backend::value::Value;
use artichoke_backend::{Artichoke, ArtichokeError};
use artichoke_core::value::Value as ValueLike;
use std::io::Write;
use std::mem;

struct Container { inner: i64 }

impl Container {
    unsafe extern "C" fn initialize(mrb: *mut sys::mrb_state, mut slf: sys::mrb_value) -> sys::mrb_value {
        let inner = mrb_get_args!(mrb, required = 1);
        let interp = unwrap_interpreter!(mrb);
        let inner = Value::new(&interp, inner);
        let inner = inner.try_into::<i64>().unwrap_or_default();
        let cont = Self { inner };
        cont
            .try_into_ruby(&interp, Some(slf))
            .unwrap_or_else(|_| interp.convert(None::<Value>))
            .inner()
    }

    unsafe extern "C" fn value(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
        let interp = unwrap_interpreter!(mrb);
        let container = Value::new(&interp, slf);
        if let Ok(cont) = Self::try_from_ruby(&interp, &container) {
            let borrow = cont.borrow();
            interp.convert(borrow.inner).inner()
        } else {
            interp.convert(None::<Value>).inner()
        }
    }
}

impl RustBackedValue for Container {
    fn ruby_type_name() -> &'static str {
        "Container"
    }
}

impl File for Container {
  fn require(interp: Artichoke) -> Result<(), ArtichokeError> {
        let spec = interp.0.borrow_mut().def_class::<Self>("Container", None, Some(rust_data_free::<Self>));
        spec.borrow_mut().add_method("initialize", Self::initialize, sys::mrb_args_req(1));
        spec.borrow_mut().add_method("value", Self::value, sys::mrb_args_none());
        spec.borrow_mut().mrb_value_is_rust_backed(true);
        spec.borrow().define(&interp)?;
        Ok(())
    }
}

fn main() {
    let interp = artichoke_backend::interpreter().unwrap();
    interp.def_file_for_type::<_, Container>("container.rb").unwrap();
    interp.eval("require 'container'").unwrap();
    let result = interp.eval("Container.new(15).value * 24").unwrap();
    assert_eq!(result.try_into::<i64>(), Ok(360));
}

Converters Between Ruby and Rust Types

The convert module provides implementations for conversions between mrb_value Ruby types and native Rust types like i64 and HashMap<String, Option<Vec<u8>>> using an Artichoke interpreter.

There are two converter traits:

Supported conversions:

The infallible converters are safe Rust functions. The fallibile converters are unsafe Rust functions.

Modules

class
convert
def
eval
exception
extn
ffi

Functions for interacting directly with mruby structs from [sys].

file
fs

[Artichoke] virtual filesystem used for storing Ruby sources.

gc
load
method
module
state
sys

C bindings for mruby, customized for Artichoke. Module sys contains Rust bindings for mruby (currently version 2.0.1), statically linked with FFI API generated by bindgen.

top_self
types
value
warn

Macros

mrb_get_args

Extract [sys::mrb_value]s from a [sys::mrb_state] to adapt a C entrypoint to a Rust implementation of a Ruby function.

unwrap_interpreter

Extract an Artichoke instance from the userdata on a [sys::mrb_state].

Structs

Artichoke

Interpreter instance.

Enums

ArtichokeError

Errors returned by Artichoke interpreters.

Functions

interpreter

Create and initialize an [Artichoke] interpreter.