Rucy - A Ruby C++ extension helper library
⚠️ Notice
This repository is a read-only mirror of our monorepo. We do not accept pull requests or direct contributions here.
🔄 Where to Contribute?
All development happens in our xord/all monorepo, which contains all our main libraries. If you'd like to contribute, please submit your changes there.
For more details, check out our Contribution Guidelines.
Thanks for your support! 🙌
🚀 About
Rucy is a thin C++ layer on top of Ruby's C API. It reduces the boilerplate involved in writing a Ruby extension in C++ by wrapping VALUE, providing exception-safe method definitions, and converting between native types and Ruby values.
It is used by the native extensions in the xord/* family — Beeps, Rays, and Reflex. It depends on Xot for low-level utilities such as reference counting, the pimpl idiom, and the string / exception classes.
Like Xot, Rucy exists primarily for our own gems. The API is stable enough for us to build on, but it is not a general-purpose extension framework — feel free to read it and learn from it, but pin a specific version if you depend on it directly.
📋 Requirements
- Ruby 3.0.0 or later
- A C++ compiler with C++20 support
- Xot (declared as a runtime dependency)
- Rake and test-unit (development only)
📦 Installation
Add this line to your Gemfile:
gem 'rucy'
Then install:
$ bundle install
Or install it directly:
$ gem install rucy
When linking against Rucy from your own extension, point extconf.rb at the gem's include/ and lib/ directories — Rucy::Extension.inc_dir and Rucy::Extension.lib_dir return the right paths.
📚 What's Included
C++ headers (include/rucy/)
| Header | Provides |
|---|---|
rucy.h |
Rucy::init(), the Rucy module, and the error class hierarchy |
ruby.h |
A safe <ruby.h> wrapper plus the RubyValue / RubySymbol typedefs |
value.h |
Rucy::Value — wraps VALUE with type predicates, conversions, and method calls |
module.h |
Rucy::Module — define_module, define_class, define_method, ... |
class.h |
Rucy::Class — adds define_alloc_func on top of Module |
function.h |
Rucy::call("method", ...), eval, protect — invoke Ruby code with C++ exception safety |
symbol.h |
Rucy::Symbol plus the RUCY_SYM, RUCY_SYM_Q, RUCY_SYM_B macros |
exception.h |
RubyException, RubyJumpTag, and raise / *_error throw helpers |
extension.h |
Method definition macros (RUCY_DEF0 ... RUCY_DEFN) and VALUE ↔ native converters |
Method definition macros
| Macro | Purpose |
|---|---|
RUCY_DEF0 ... RUCY_DEF12 |
Define a Ruby method that takes 0–12 arguments |
RUCY_DEFN |
Define a variadic Ruby method (int argc, const Value* argv) |
RUCY_DEF_ALLOC |
Define an allocator function for a class |
RUCY_DEF_END |
Close a method definition; converts C++ exceptions into Ruby exceptions |
RUCY_TRY / RUCY_CATCH |
Wrap any block of C++ code in Ruby-safe exception translation |
Type conversion macros
RUCY_DECLARE_VALUE_FROM_TO / RUCY_DEFINE_VALUE_FROM_TO (and the WRAPPER / ARRAY variants) generate Rucy::value(...) and Rucy::value_to<T>(...) overloads so you can move data between Ruby and a native class with one line of declaration plus one line of definition.
Ruby side (lib/rucy/)
| Module | Purpose |
|---|---|
Rucy::Extension |
Path / name / version helpers, mirroring Xot::Extension for Rucy-based gems |
Rucy::Rake |
Rake DSL extensions used by xord/* gems (build_native_library, build_ruby_extension, ...) |
Tools (bin/)
| Tool | Purpose |
|---|---|
rucy2rdoc |
Extract doc comments from C++ files using RUCY_DEF* macros into RDoc-friendly stubs |
💡 Usage
A minimal extension
// ext/hello/hello.cpp
#include <rucy/rucy.h>
#include <rucy/extension.h>
using namespace Rucy;
/*
Returns a friendly greeting.
*/
RUCY_DEF1(greet, name)
{
return value(Xot::String("hello, ") + name.c_str());
}
RUCY_DEF_END
extern "C" void
Init_hello ()
{
RUCY_TRY
Module mHello = define_module("Hello");
mHello.define_module_function("greet", greet);
RUCY_CATCH
}
# ext/hello/extconf.rb
require 'rucy/extension'
require 'mkmf'
$INCFLAGS << " -I#{Xot::Extension.inc_dir} -I#{Rucy::Extension.inc_dir}"
$LDFLAGS << " -L#{Xot::Extension.lib_dir} -L#{Rucy::Extension.lib_dir} -lxot -lrucy"
create_makefile 'hello/hello'
# Use it from Ruby
require 'hello/hello'
Hello.greet 'world' # => "hello, world"
Exception-safe method bodies
Anything you throw inside a RUCY_DEF* body — Rucy::RubyException, std::exception, Xot::XotError, or even a const char* — is caught by RUCY_DEF_END and re-raised as the corresponding Ruby exception. You can also raise directly:
RUCY_DEF1(divide, n)
{
if (n.as_i() == 0)
argument_error(__FILE__, __LINE__, "divide by zero");
return value(100 / n.as_i());
}
RUCY_DEF_END
Calling Ruby from C++
Value ary = eval("[1, 2, 3]");
Value n = ary.call("sum"); // => Value(6)
For more examples, see ext/rucy/tester.cpp (used by the test suite) and the native extensions in Beeps, Rays, and Reflex.
🛠️ Development
$ rake lib # build the native C++ library (librucy)
$ rake ext # build the test extension
$ rake test # run the test suite
$ rake doc # generate RDoc from C++ sources via rucy2rdoc
$ rake # default: builds the extension
Several headers and sources are ERB templates (*.erb) expanded automatically at build time. NPARAM_MAX = 12 in the Rakefile auto-generates RUCY_DEF0 ... RUCY_DEF12 and the matching overloads of call, protect, and friends.
📜 License
Rucy is licensed under the MIT License. See the LICENSE file for details.