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.
mruby crate exposes several mechanisms for executing Ruby code on the interpreter.
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));
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.
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");
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
:
RustBackedValue::try_into_ruby
consumesself
and returns a livemrb_value
that wrapsT
.RustBackedValue::try_from_ruby
extracts anRc<RefCell<T>>
from anmrb_value
and manages the strong count of theRc
smart pointer to ensure that themrb_value
continues to point to valid memory.
These mrb_value
s with type tag MRB_TT_DATA
can be used to implement Ruby
Class
es and Module
s 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 = interpreter_or_raise!(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 };
unwrap_value_or_raise!(interp, cont.try_into_ruby(&interp, Some(slf)))
}
unsafe extern "C" fn value(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
let interp = interpreter_or_raise!(mrb);
let cont = unwrap_or_raise!(
interp,
Self::try_from_ruby(&interp, &Value::new(&interp, slf)),
Value::from_mrb(&interp, None::<Value>).inner()
);
let borrow = cont.borrow();
Value::from_mrb(&interp, borrow.inner).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));
}
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:
FromMrb
provides infallible conversions that returnSelf
. Converting from a Rust native type to a Rubymrb_value
is usually an infallible conversion.TryFromMrb
provides fallible conversions that returnResult<Self, Error>
. Converting from a Rubymrb_value
to a Rust native type is always an fallible conversion because anmrb_value
may be any type tag.
Supported conversions:
- Ruby primitive types to Rust types. Primitive Ruby types are
TrueClass
,FalseClass
,String
(both UTF-8 and binary),Fixnum
(i64
),Float
(f64
). - Rust types to Ruby types. Supported Rust types are
bool
,Vec<u8>
,&[u8]
, integer types that losslessly convert toi64
(i64
,i32
,i16
,i8
,u32
,u16
,u8
),f64
,String
,&str
. - Ruby
nil
able types to RustOption<T>
. - Rust
Option<T>
types to Rubynil
or anmrb_value
converted fromT
. - Ruby
Array
to RustVec<T>
whereT
corresponds to a Ruby primitive type. - Rust
Vec<T>
to RubyArray
whereT
corresponds to a Ruby primitive type. - Ruby
Hash
to RustVec<(Key, Value)>
orHashMap<Key, Value>
whereKey
andValue
correspond to Ruby primitive types. - Rust
Vec<(Key, Value)>
orHashMap<Key, Value>
to RubyHash
whereKey
andValue
correspond to Ruby primitive types. - Identity conversion from
Value
toValue
, which is useful when working with collection types.
The infallible converters are safe Rust functions. The fallibile converters are
unsafe
Rust functions.