Build Ruby gems that learn Rust for free.
Two design patterns for Ruby/Rust collaboration:
- FFI Hybrid - Ruby wraps Rust for optional 10-100x speedup
- Mirror API - Parallel Ruby/Rust implementations with conceptual parity
Matryoshka dolls (Russian nesting dolls) share the same design across different scales. This repo's patterns embody that concept in two ways:
Same logic, nested layers. The Ruby API wraps FFI which wraps Rust. Each layer looks the same to the user.
Same design, parallel implementations. Two independent projects that share the same conceptual API, each optimized for its language.
Both are matryoshka because they maintain conceptual identity across different forms—whether nested (FFI) or parallel (Mirror).
What it does: Ruby gem with optional Rust acceleration. Falls back to pure Ruby gracefully.
Ruby usage:
Under the hood:
- Pure Ruby implementation (fallback)
- Rust FFI speedup (65x faster when available)
- no_std core (compiles to ESP32)
When to use: Compute-heavy code (parsing, crypto, math) where the algorithm is identical in both languages.
What it does: Two independent implementations (Ruby + Rust) with 90%+ feature parity, NO FFI.
Ruby version:
Rust version:
When to use: When ownership semantics differ (mutation vs. consumption), or when FFI would destroy type safety guarantees.
If your Ruby code was "good enough" at 1x speed, it's amazing at 65x.
Write logic in Ruby → Compile Rust core to ESP32. No C required.
Ruby devs read Rust ports → Understand 40% of Rust syntax by osmosis.
- ✅ Explicit variable bindings
- ✅ Ruby-like method names
- ❌ No .fetch().then().map() chains
- ❌ No "clever" APIs that sacrifice clarity
Short answer: The architecture makes segfaults extremely difficult.
Why this is safer than C extensions:
Traditional C extensions directly manipulate Ruby VM internals:
Matryoshka FFI Hybrid pattern:
Architecture guarantees:
- No direct Ruby VM calls - Magnus abstracts all rb_* functions
- No manual GC interaction - Rust never touches Ruby's garbage collector
- Only primitives cross FFI boundary - i64, f64, String (copied, not borrowed)
- Rust core is isolated - no_std crate with no Ruby types
- Type conversions are explicit - Magnus enforces compile-time safety
Failure modes (and mitigations):
| Panic/crash | Segfault | Magnus catches, converts to Ruby exception |
| Bad type | Runtime crash | Compile-time error (Magnus type checking) |
| Memory leak | Easy (forget to free) | Impossible (Rust ownership) |
| GC bug | Holding pointers across GC | No Ruby heap access |
| Race condition | Undefined behavior | Document thread-safety (same as Ruby) |
Compare to pg/mysql2/trilogy: Those gems call C libraries (libpq, libmysqlclient) and carefully manage Ruby GC, exceptions, and memory. Much larger "sus" surface area.
Matryoshka's promise: If the Rust core is no_std and only passes primitives across FFI, segfaults are architecturally prevented, not just "avoided by good coding."
Real example (ChronoMachines):
- Input: i64, f64 (primitives from Ruby)
- Computation: Pure Rust math (no allocations, no Ruby VM)
- Output: f64 (primitive to Ruby)
- No way to segfault - no pointers, no Ruby VM access, no GC interaction
- chrono_machines - Retry logic with exponential backoff (reference implementation)
- examples/simple_parser/ - Minimal string parsing example
- examples/embedded_blinker/ - ESP32 using shared Rust core
- state_machines (Ruby) + state-machines-rs (Rust)
- Identical state machine DSL
- Ruby: ActiveRecord integration, runtime flexibility
- Rust: Compile-time type safety, embedded targets
Ruby:
Rust (intentionally similar):
What you learn:
- let = variable binding
- fn name(&self, param: Type) -> ReturnType = method signature
- .powi() = integer exponent (like **)
- .min() = same as Ruby
- Last expression returns (no return needed)
After reading 3-4 ported methods, you understand 40% of Rust syntax.
FFI Hybrid:
- Parsers (JSON, XML, CSV, Markdown)
- String manipulation (regex, sanitization)
- Cryptography (hashing, encoding, JWT)
- Math-heavy algorithms (statistics, simulations)
- Date/time conversions
Mirror API:
- State machines (when type safety matters)
- Protocol implementations (different ownership models)
- Educational projects (teaching Rust via Ruby)
- Embedded + server dual-target libraries
- Pure metaprogramming gems (ActiveSupport)
- Network clients (latency dominates, not CPU)
- Simple wrappers around system commands
- Gems with < 3 compute-intensive methods
- Understand the philosophy → PHILOSOPHY.md
- Choose your pattern:
- FFI Hybrid → FFI_HYBRID.md
- Mirror API → MIRROR_API.md
- Learn Rust syntax → SYNTAX.md
- Study examples → examples/ directory
- Use templates → templates/ directory
- 65x faster retry delay calculations
- Works on JRuby (pure Ruby fallback)
- Rust core compiles to ESP32 (same retry logic in firmware)
- Zero changes to public API
- Ruby version: 7,050 LOC, Rails integration, runtime flexibility
- Rust version: 39,711 LOC, compile-time type safety, no_std support
- 90%+ feature parity despite different ownership models
- Ruby devs learning Rust via familiar patterns
We welcome:
- ✅ Example gems using these patterns
- ✅ Documentation improvements
- ✅ Embedded platform guides (ESP32, STM32, RP2040)
- ✅ Translation guides for other languages
We reject:
- ❌ "Clever" Rust code that's hard to read
- ❌ Breaking no_std compatibility in FFI Hybrid cores
- ❌ Performance-only optimizations that sacrifice clarity
MIT (copy freely, attribution appreciated)
-
"Why not just use C extensions?" → C doesn't have the crate ecosystem. In traditional C extensions (pg, trilogy, mysql2), the C code is tightly coupled to Ruby—it's just extension code, not a reusable library. With Matryoshka, the Rust crate is a fully functional, standalone library that can be published to crates.io and used in pure Rust projects (embedded, CLI tools, other libraries). The Ruby gem is just ONE consumer of it. The crate has independent value beyond Ruby.
-
"Why not FFI for everything?" → Some patterns (like typestate) lose their value across FFI. When ownership semantics differ fundamentally (Ruby's mutation vs Rust's consumption), parallel implementations (Mirror API) preserve language-specific guarantees that FFI would destroy.
-
"Why the Russian doll metaphor?" → See top of this README. TL;DR: Matryoshka dolls share the same design across different scales—same concept for nested layers (FFI Hybrid) and parallel implementations (Mirror API).
-
"Can I use this in production?" → Yes. ChronoMachines is production-tested with graceful fallbacks.
-
"Do I need to know Rust first?" → No. Read Ruby code, then read Rust port side-by-side. Learn by comparison (~40% syntax coverage after 3-4 methods).
Start here: PHILOSOPHY.md → Understand the "why" before the "how"
.png)

