









In 2015-2017 a lot of folks from the Ruby community started talking about Rust



/usr/bin/ruby works
/* Set a local variable (pointed to by 'idx') as val.
'level' indicates the nesting depth from the current block.
*/
DEFINE_INSN
setlocal
(lindex_t idx, rb_num_t level)
(VALUE val)
()
{
vm_env_write(vm_get_ep(GET_EP(), level), -(int)idx, val);
RB_DEBUG_COUNTER_INC(lvar_set);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
}
static VALUE
vm_exec_core(rb_execution_context_t *ec)
{
register rb_control_frame_t *reg_cfp = ec->cfp;
rb_thread_t *th;
while (1) {
reg_cfp = ((rb_insn_func_t) (*GET_PC()))(ec, reg_cfp);
if (UNLIKELY(reg_cfp == 0)) {
break;
}
}
ruby_value_type {
RUBY_T_NONE = 0x00, /**< Non-object (swept etc.) */
RUBY_T_OBJECT = 0x01, /**< @see struct ::RObject */
RUBY_T_CLASS = 0x02, /**< @see struct ::RClass and ::rb_cClass */
RUBY_T_MODULE = 0x03, /**< @see struct ::RClass and ::rb_cModule */
RUBY_T_FLOAT = 0x04, /**< @see struct ::RFloat */
RUBY_T_STRING = 0x05, /**< @see struct ::RString */
RUBY_T_REGEXP = 0x06, /**< @see struct ::RRegexp */
RUBY_T_ARRAY = 0x07, /**< @see struct ::RArray */
RUBY_T_HASH = 0x08, /**< @see struct ::RHash */
RUBY_T_STRUCT = 0x09, /**< @see struct ::RStruct */
RUBY_T_BIGNUM = 0x0a, /**< @see struct ::RBignum */
RUBY_T_FILE = 0x0b, /**< @see struct ::RFile */
// ...
VALUE
rb_obj_as_string(VALUE obj)
{
VALUE str;
if (RB_TYPE_P(obj, T_STRING)) {
return obj;
}
void
rb_gc_mark_children(void *objspace, VALUE obj)
{
struct gc_mark_classext_foreach_arg foreach_args;
if (FL_TEST_RAW(obj, FL_EXIVAR)) {
rb_mark_generic_ivar(obj);
}
switch (BUILTIN_TYPE(obj)) {
case T_FLOAT:
case T_BIGNUM:
case T_SYMBOL:
/* Not immediates, but does not have references and singleton class.
*
* RSYMBOL(obj)->fstr intentionally not marked. See log for 96815f1e
* ("symbol.c: remove rb_gc_mark_symbols()") */
return;
pez.github.io/languages-visualizations/
Other languages benefit from reduced overhead, greater memory efficiency, or parallelism (e.g. polars)
openssl, libpq)| Spawn processes | Fiddle/FFI | Native extensions | |
|---|---|---|---|
| Chromium, GraphicsMagick | LibUI, fluentd | Nokogiri, ruby-vips, pg | |
| Pros | Strong isolation Straightforward* |
Direct access to C APIs Dynamic |
Deeper integration |
| Cons | Overhead IPC |
Overhead Trickier with complex APIs |
More ceremony |
/*
* call-seq:
* require(name) -> true or false
*
* Loads the given +name+, returning +true+ if successful and +false+ if the
* feature is already loaded.
*
* [...]
*
* If the filename has the extension ".rb", it is loaded as a source file; if
* the extension is ".so", ".o", or the default shared library extension on
* the current platform, Ruby loads the shared library as a Ruby extension.
*
* [...]
*/
static void *
dln_load_and_init(const char *file, const char *init_fct_name)
{
#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
void *handle = dln_open(file);
#ifdef RUBY_DLN_CHECK_ABI
typedef unsigned long long abi_version_number;
abi_version_number binary_abi_version =
dln_sym_callable(abi_version_number, (void), handle, EXTERNAL_PREFIX "ruby_abi_version")();
if (binary_abi_version != RUBY_ABI_VERSION && abi_check_enabled_p()) {
dln_loaderror("incompatible ABI version of binary - %s", file);
}
#endif
/* Call the init code */
dln_sym_callable(void, (void), handle, init_fct_name)();
/**
* Defines a method.
*
* @param[out] klass A module or a class.
* @param[in] mid Name of the function.
* @param[in] func The method body.
* @param[in] arity The number of parameters. See @ref defmethod.
* @note There are in fact 18 different prototypes for func.
* @see ::ruby::backward::cxxanyargs::define_method::rb_define_method
*/
void rb_define_method(VALUE klass, const char *mid, VALUE (*func)(ANYARGS), int arity);

Ship the source code and build the extension on install
.gemShip a pre-built binary blob

apt)Iterator)NoMethodError is not a thing)cargo) and ecosystem (see lib.rs, docs.rs)

ruby.h (via bindgen)extconf.rbrb-sys

bundle gem hello_rust --ext rust
Arc or by putting the logic in Rubyclass Document; end
class Element
def initialize(document)
@document = document # <= this is fine
end
def ancestor
@document.ancestor(self)
end
end


#[derive(Clone)]
#[magnus::wrap(class = "Sawzall::Document", free_immediately)]
struct Document(Arc<Mutex<Html>>);
impl Document {
fn root_element(&self) -> Element {
self.with_locked_html(|html| Element {
id: html.root_element().id(),
document: self.clone(),
})
}
}
impl Element {
fn with_element_ref<U, F>(&self, f: F) -> U
where
F: FnOnce(ElementRef) -> U,
{
let html = self.document.0.lock().expect("failed to lock mutex");
let element_ref = html
.tree
.get(self.id)
.and_then(ElementRef::wrap)
.expect("node with id {self.id} must be an element in the tree");
f(element_ref)
}
}
rake compile first.cargo test also works as you would expectedrb-sys provides a rb-sys-test-helpers crate to test Rust code that interacts with the Ruby VM (see oxidize-rb.github.io/rb-sys/testing.html)Write YARD in Rust
YARD::Rustdoc allows you to write YARD documentation in your Rust codeWrite YARD in Ruby
YARD's@!parse and @!method tags let you document classes and methods that aren't defined in the source code (e.g. to support metaprogramming)@example tags with YARD::Doctestdrop implementation called when the GC runs.If your Rust data structures hold onto Ruby objects, you are responsible for providing the appropriate GC hooks so they aren't removed from under you.
See:The rb-sys gem provides a rb-sys-dock command to make this easier using a set of Docker images.
Shopify/blake3-rb/ is a good example of how to do this on GitHub Actions.
Basic example on davidcornu/sawzall