rb-sys

website·Indexed 32 days ago

https://oxidize-rb.org

Low-level Rust bindings for the Ruby C API used to build safe and high-performance Ruby extensions. The documentation covers memory management with Ruby's GC, type conversions, error mapping between Rust and Ruby, project structure for gems, and the use of rb-sys-test-helpers for integration testing.

Tokens
23.9K
Snippets
121
Records
153
Agent score
58%

What's inside rb-sys

  1. Overview of rb-sys for Ruby and Rust integration

    rb-sys is a toolchain designed to integrate Rust's performance, memory safety, and modern tooling into the Ruby ecosystem. It simplifies the FFI (Foreign Function Interface) integration between Ruby and Rust by providing simplified bindings to the Ruby C API, allowing developers to write CPU-intensive bottlenecks (such as parsing, image processing, and complex computations) in Rust while maintaining a Ruby application structure.
  2. Understand the Ruby-Rust bridge architecture

    The connection between Ruby and Rust is implemented in layers: rb-sys provides the low-level, unsafe bindings to Ruby's C API, while higher-level libraries like Magnus provide a safe and ergonomic wrapper for developers.
  3. Choose between rb-sys and Magnus for Ruby extensions

    When building Ruby extensions with Rust, you can choose between low-level control via rb-sys or high-level convenience via Magnus.

    AspectDirect rb-sysMagnus (High-Level)
    ControlMaximum control over Ruby VMAbstracts away VM details
    SafetyRequires unsafe blocksMostly safe API
    VerbosityVerbose; manual type conversionConcise; automatic type conversion
    Error HandlingManual check of Ruby error stateIdiomatic Result<T, Error>
    Best ForPerformance-critical code, internal APIsMost general-purpose gems

    For most projects, Magnus is recommended. Use rb-sys only for critical performance requirements or when accessing Ruby internals not covered by wrappers.

  4. Structure a Ruby gem with Rust extensions

    A production rb-sys project follows a specific directory structure to separate Ruby logic, Rust extension code, and build configurations. The root contains the workspace configuration and gem specifications, while the ext/ directory houses the Rust crates.
    my_gem/
    ├── 📦 Cargo.toml              # Workspace configuration
    ├── 💎 Gemfile                 # Ruby dependencies
    ├── 🔨 Rakefile                # Build automation
    ├── 📋 my_gem.gemspec          # Gem specification
    ├── 📝 README.md               # Documentation
    ├── 🔒 Gemfile.lock            # Locked dependencies
    ├── 📂 ext/
    │   └── my_gem/
    │       ├── 🦀 Cargo.toml      # Extension dependencies
    │       ├── 🔧 extconf.rb      # Build configuration
    │       ├── 🏗️ build.rs        # Build script (optional)
    │       └── 📂 src/
    │           ├── 📄 lib.rs      # Rust entry point
    │           └── 📄 *.rs        # Additional modules
    ├── 📂 lib/
    │   ├── 💎 my_gem.rb           # Ruby entry point
    │   └── 📂 my_gem/
    │       ├── 📄 version.rb      # Version constant
    │       └── 📄 *.rb            # Ruby modules
    ├── 🧪 test/                   # Test files
    ├── 📊 benchmark/              # Performance tests
    └── 📚 examples/               # Usage examples
  5. Understand the oxidize-rb technology stack

    The oxidize-rb ecosystem provides a layered approach to building Ruby extensions:

    1. Ruby Code: The end-user interface calling the library.
    2. Magnus: A high-level, ergonomic Rust API that handles Ruby objects safely.
    3. rb-sys: Low-level bindings to the Ruby C API.
    4. Rust Ecosystem: Access to any crate available on crates.io.
  6. Understand rb-sys and Magnus core concepts

    rb-sys provides low-level Rust bindings to Ruby's C API, serving as the foundation for building Ruby extensions in Rust. Magnus is a high-level Rust library built on top of rb-sys that provides a safer and more ergonomic API for developers.
  7. Understand the Rust-based Ruby gem compilation process

    When running bundle exec rake compile, the build process follows these four steps:

    1. extconf.rb execution: Ruby's mkmf library executes the extension's extconf.rb script.
    2. Makefile generation: The create_rust_makefile helper from rb-sys generates a Makefile configured for the Rust code.
    3. cargo build invocation: The Makefile triggers cargo build to compile the Rust crate into a dynamic shared library.
    4. Library copying: The resulting compiled library is moved into the gem's lib directory for Ruby to load.
  8. Core principles for building Ruby extensions with Magnus

    When developing Ruby extensions in Rust using Magnus, follow these critical guidelines:

    • Error Handling: Use Result<T, magnus::Error> for any function that can fail. This ensures Rust errors are automatically mapped to Ruby exceptions.
    • Type Conversions: Use Magnus's automatic type conversions for simple types (e.g., String, i64, Vec<T>).
    • Performance/Complexity: For performance-critical code or complex types, use Ruby-specific objects like RString, RArray, and RHash instead of standard Rust types.
    • Stability: Never use panic!. A panic in Rust will crash the entire Ruby process.
  9. Build a Ruby extension using Magnus

    Magnus provides a high-level, ergonomic API that handles automatic type conversion and uses Rust's Result for error handling, significantly reducing the need for unsafe code. It is suitable for most general-purpose Ruby gems.
    use magnus::{function, prelude::*, Error, Ruby};
    
    fn hello(subject: String) -> String {
        format!("Hello from Rust, {subject}!")
    }
    
    #[magnus::init]
    fn init(ruby: &Ruby) -> Result<(), Error> {
        let module = ruby.define_module("StringUtils")?;
        module.define_singleton_method("hello", function!(hello, 1))?;
        Ok(())
    }
  10. Choose the right Ruby-Rust integration approach

    Depending on your goal, use different tools for interacting with Ruby from Rust:

    • Raw C API access: Use rb-sys directly for unsafe access to libruby.
    • Gem Development: Prefer high-level libraries like Magnus (with the rb-sys feature) for a safer API.
    • Embedding Ruby: Use rb-sys with the link-ruby feature to embed the Ruby VM in a Rust binary.
  11. Debug Rust extensions

    To debug a Rust extension, compile the code with debug symbols and use standard debuggers such as gdb or lldb.
  12. Handle Ruby nil values in Rust

    Ruby's nil is a special value. When converting a magnus::Value to a Rust type, TryConvert::try_convert(val).ok() will return None if the value is nil or of an incompatible type. To return nil to Ruby, use ruby.qnil().as_value().
    use magnus::{Value, Ruby, RString, TryConvert, Error};
    
    fn handle_nil(_ruby: &Ruby, val: Value) -> Result<(), Error> {
        // Returns None if val is nil or not a string
        let maybe_string: Option<RString> = TryConvert::try_convert(val).ok();
        
        Ok(())
    }
    
    fn returns_nil(ruby: &Ruby) -> Value {
        ruby.qnil().as_value()
    }