[][src]Crate mruby

mruby

mruby crate provides a safe interface over the raw mruby bindings in mruby-sys. mruby crate aims to expose as much of the mruby API as possible.

Execute Ruby Code

mruby crate exposes several mechanisms for executing Ruby code on the interpreter.

Evaling Source Code

mruby crate exposes eval on the mrb_state with the MrbEval trait. Side effects from eval are persisted across invocations.

use mruby::eval::MrbEval;

let interp = mruby::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> or arguments. funcall takes a type parameter bound by TryFromMrb and converts the result of the function call to a Rust type (which may be Value or another "native" type).

mruby limits functions to a maximum of 16 arguments.

Virtual Filesystem and Kernel#require

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

mruby 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 MrbEval::eval_with_context. For Rust sources, MrbFile::require methods are stored as custom metadata on File nodes in the VFS.

use mruby::eval::MrbEval;
use mruby::load::MrbLoadSources;

let mut interp = mruby::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::<String>().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. mruby 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 mruby;

use mruby::convert::{FromMrb, RustBackedValue, TryFromMrb};
use mruby::def::{rust_data_free, ClassLike, Define};
use mruby::eval::MrbEval;
use mruby::file::MrbFile;
use mruby::load::MrbLoadSources;
use mruby::sys;
use mruby::value::Value;
use mruby::{Mrb, MrbError};
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 interp = unwrap_interpreter!(mrb);
        let api = interp.borrow();
        let int = mem::uninitialized::<sys::mrb_int>();
        let mut argspec = vec![];
        argspec.write_all(format!("{}\0", sys::specifiers::INTEGER).as_bytes()).unwrap();
        sys::mrb_get_args(mrb, argspec.as_ptr() as *const i8, &int);
        let cont = Self { inner: int };
        cont
            .try_into_ruby(&interp, Some(slf))
            .unwrap_or_else(|_| Value::from_mrb(&interp, 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);
        if let Ok(cont) = Self::try_from_ruby(&interp, &Value::new(&interp, slf)) {
            let borrow = cont.borrow();
            Value::from_mrb(&interp, borrow.inner).inner()
        } else {
            Value::from_mrb(&interp, None::<Value>).inner()
        }
    }
}

impl RustBackedValue for Container {}

impl MrbFile for Container {
  fn require(interp: Mrb) -> Result<(), MrbError> {
        let spec = interp.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 = mruby::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 Mrb interpreter.

There are two converter traits:

Supported conversions:

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

Re-exports

pub use mruby_sys as sys;

Modules

class
convert
def
eval
exception
extn
ffi

Functions for interacting directly with mruby_sys structs.

file
fs

Mrb virtual filesystem used for storing Ruby sources.

gc
load
method
module
state
top_self
value
warn

Macros

unwrap_interpreter

Extract an Mrb instance from the userdata on a sys::mrb_state.

Enums

MrbError

Errors returned by mruby crate.

Functions

interpreter

Create and initialize an Mrb interpreter.

Type Definitions

Mrb

Interpreter instance.