Troubleshooting Guide
This chapter provides solutions to common issues encountered when developing Ruby extensions with rb-sys and magnus.
Getting Started Issues
Installation Problems
Missing libclang
Problem: Build fails with errors related to missing libclang:
error: failed to run custom build command for `bindgen v0.64.0`
...
could not find libclang: "couldn't find any valid shared libraries matching: ['libclang.so', ...]"
Solutions:
-
Add libclang to your Gemfile:
gem "libclang", "~> 14.0"
-
On Linux, install libclang through your package manager:
# Debian/Ubuntu apt-get install libclang-dev # Fedora/RHEL dnf install clang-devel
-
On macOS:
brew install llvm export LLVM_CONFIG=$(brew --prefix llvm)/bin/llvm-config
Ruby Headers Not Found
Problem: Build fails with error about missing Ruby headers:
error: failed to run custom build command for `rb-sys v0.9.78`
...
fatal error: ruby.h: No such file or directory
Solutions:
-
Ensure you have Ruby development headers installed:
# Debian/Ubuntu apt-get install ruby-dev # Fedora/RHEL dnf install ruby-devel
-
If using rbenv/rvm/asdf, make sure you've installed the development headers:
# For rbenv RUBY_CONFIGURE_OPTS=--enable-shared rbenv install 3.0.0 # For rvm rvm install 3.0.0 --with-openssl-dir=$(brew --prefix openssl)
-
Check your build.rs file includes:
#![allow(unused)] fn main() { let _ = rb_sys_env::activate()?; }
Compilation Issues
Cargo Build Errors
Version Compatibility
Problem: Build fails with version incompatibility errors:
error: failed to select a version for the requirement `rb-sys = "^0.9.80"`
Solution:
-
Check your magnus and rb-sys versions are compatible:
# Cargo.toml [dependencies] magnus = "0.7" # Check latest compatible version rb-sys = "0.9.80" # Check latest version
-
Update rb-sys in your Gemfile:
gem 'rb_sys', '~> 0.9.80'
Linking Issues
Problem: Build fails with undefined references:
error: linking with `cc` failed: exit status: 1
...
undefined reference to `rb_define_module`
Solutions:
-
Ensure your build.rs is correctly set up:
fn main() -> Result<(), Box<dyn std::error::Error>> { let _ = rb_sys_env::activate()?; Ok(()) }
-
Verify Ruby version compatibility with rb-sys version
-
Ensure you have Ruby development headers installed
Build Script Errors
Problem: Build script execution fails:
error: failed to run custom build command for `rb-sys v0.9.78`
Solutions:
-
Check permissions and environment variables:
# Set necessary environment variables export RUBY_ROOT=$(rbenv prefix) export PATH=$RUBY_ROOT/bin:$PATH
-
If using Docker, ensure the build environment has Ruby installed and configured
Runtime Issues
Ruby Object Management
Segmentation Faults
Problem: Ruby process crashes with a segmentation fault:
[BUG] Segmentation fault
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]
Solutions:
-
Ensure Ruby objects are correctly protected from garbage collection:
#![allow(unused)] fn main() { // Using Magnus (preferred) let obj = RObject::new(ruby, ruby.class_object())?; // With raw rb-sys unsafe { let obj = rb_sys::rb_obj_alloc(rb_sys::rb_cObject); rb_sys::rb_gc_register_mark_object(obj); // Use obj... rb_sys::rb_gc_unregister_mark_object(obj); } }
-
Check all pointers are valid before dereferencing
-
Use TypedData with proper mark implementation
BorrowMutError Panics
Problem: Your extension panics with a "already borrowed" error when using RefCell:
thread '<unnamed>' panicked at 'already borrowed: BorrowMutError'
Solutions:
-
Fix borrow order - always complete immutable borrows before mutable borrows:
#![allow(unused)] fn main() { // WRONG - causes BorrowMutError if self.0.borrow().value > 10 { self.0.borrow_mut().value = 0; // Error: still borrowed from the if condition } // RIGHT - copy values then borrow mutably let current = self.0.borrow().value; // Complete this borrow if current > 10 { self.0.borrow_mut().value = 0; // Now safe to borrow mutably } }
-
Consider restructuring your data to avoid nested borrows
-
Use separate methods for reading and writing
Ruby/Rust Type Conversion Issues
Unexpected Nil Values
Problem: Your extension crashes when encountering nil:
TypeError: no implicit conversion of nil into String
Solutions:
-
Always check for nil before conversion:
#![allow(unused)] fn main() { fn process_string(val: Value) -> Result<String, Error> { if val.is_nil() { // Handle nil case return Ok("default".to_string()); } let string = RString::try_convert(val)?; Ok(string.to_string()?) } }
-
Use Option for conversions that might return nil:
#![allow(unused)] fn main() { let maybe_string: Option<String> = val.try_convert()?; match maybe_string { Some(s) => process_string(s), None => handle_nil_case(), } }
Type Errors
Problem: Function fails with type mismatch errors:
TypeError: wrong argument type Integer (expected String)
Solutions:
-
Add explicit type checking before conversion:
#![allow(unused)] fn main() { fn process_value(ruby: &Ruby, val: Value) -> Result<(), Error> { if !val.is_kind_of(ruby, ruby.class_object::<RString>()?) { return Err(Error::new( ruby.exception_type_error(), format!("Expected String, got {}", val.class().name()) )); } let string = RString::try_convert(val)?; // Process string... Ok(()) } }
-
Use try_convert with proper error handling:
#![allow(unused)] fn main() { match RString::try_convert(val) { Ok(string) => { // Process string... Ok(()) }, Err(_) => { Err(Error::new( ruby.exception_type_error(), "Expected String argument" )) } } }
Memory and Performance Issues
Memory Leaks
Problem: Your extension gradually consumes more memory over time.
Solutions:
-
Ensure Ruby objects are properly released when using raw rb-sys:
#![allow(unused)] fn main() { unsafe { // Register the object to protect it from GC rb_sys::rb_gc_register_mark_object(obj); // Use the object... // Unregister when done (important!) rb_sys::rb_gc_unregister_mark_object(obj); } }
-
Implement proper mark methods for TypedData:
#![allow(unused)] fn main() { #[derive(TypedData)] #[magnus(class = "MyClass", free_immediately, mark)] struct MyObject { references: Vec<Value>, } impl DataTypeFunctions for MyObject { fn mark(&self, marker: &Marker) { for reference in &self.references { marker.mark(*reference); } } } }
-
Use ruby_memcheck to detect leaks (see Debugging chapter)
Global VM Lock (GVL) Issues
Problem: CPU-intensive operations block the Ruby VM.
Solutions:
-
Release the GVL during CPU-intensive work:
#![allow(unused)] fn main() { use rb_sys::rb_thread_call_without_gvl; use std::{ffi::c_void, ptr::null_mut}; pub fn nogvl<F, R>(func: F) -> R where F: FnOnce() -> R, R: Send + 'static, { struct CallbackData<F, R> { func: Option<F>, result: Option<R>, } extern "C" fn callback<F, R>(data: *mut c_void) -> *mut c_void where F: FnOnce() -> R, R: Send + 'static, { let data = unsafe { &mut *(data as *mut CallbackData<F, R>) }; if let Some(func) = data.func.take() { data.result = Some(func()); } null_mut() } let mut data = CallbackData { func: Some(func), result: None, }; unsafe { rb_thread_call_without_gvl( Some(callback::<F, R>), &mut data as *mut _ as *mut c_void, None, null_mut(), ); } data.result.unwrap() } }
-
Only release the GVL for operations that don't interact with Ruby objects:
#![allow(unused)] fn main() { // Safe to run without GVL - pure computation let result = nogvl(|| { compute_intensive_function(input_data) }); // NOT safe to run without GVL - interacts with Ruby // let result = nogvl(|| { // ruby_object.some_method() // WRONG - will crash // }); }
Cross-Platform Issues
Ruby-Head Compatibility Issues
Problem: Gems don't work with ruby-head
or development versions of Ruby.
Solutions:
-
Always publish a source gem alongside platform-specific gems:
# In your release workflow, build both platform-specific and source gems # The source gem allows ruby-head users to compile against their exact version bundle exec rake build # For source gem bundle exec rake native # For platform-specific gems
-
For users, install the gem with compilation enabled:
gem install my_gem --platform=ruby
-
For gem maintainers, update your CI to test against ruby-head:
# .github/workflows/test.yml strategy: matrix: ruby: ["3.1", "3.2", "3.3", "head"]
Platform-Specific Build Problems
Windows Issues
Problem: Build fails on Windows with linking errors.
Solutions:
-
Ensure you have the correct toolchain installed:
rustup target add x86_64-pc-windows-msvc
-
Add platform-specific configuration in Cargo.toml:
[target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["everything"] }
-
Use conditional compilation for platform-specific code:
#![allow(unused)] fn main() { #[cfg(windows)] fn platform_specific() { // Windows-specific code } #[cfg(unix)] fn platform_specific() { // Unix-specific code } }
macOS Issues
Problem: Build fails on macOS with architecture or linking issues.
Solutions:
-
Specify architecture when needed:
RUBY_CONFIGURE_OPTS="--with-arch=x86_64,arm64" rbenv install 3.0.0
-
Fix linking for universal binaries:
#![allow(unused)] fn main() { // build.rs #[cfg(target_os = "macos")] { println!("cargo:rustc-link-arg=-arch"); println!("cargo:rustc-link-arg=arm64"); println!("cargo:rustc-link-arg=-arch"); println!("cargo:rustc-link-arg=x86_64"); } }
Cargo.toml Configuration Issues
Feature Flag Problems
Problem: Build fails because of conflicting or missing feature flags.
Solutions:
-
Check for feature flag issues in dependencies:
[dependencies] magnus = { version = "0.7", features = ["rb-sys"] } rb-sys = { version = "0.9.80", features = ["stable-api"] }
-
Use debug prints in build.rs to check feature detection:
fn main() { println!("cargo:warning=Ruby version: {}", rb_sys_env::ruby_major_version()); #[cfg(feature = "some-feature")] println!("cargo:warning=some-feature is enabled"); }
Ruby Integration Issues
Method Definition Problems
Problem: Ruby method definitions don't work as expected.
Solutions:
-
Check method arity and macro usage:
#![allow(unused)] fn main() { // For instance methods (that use &self) class.define_method("instance_method", method!(MyClass::instance_method, 1))?; // For class/module methods (no &self) class.define_singleton_method("class_method", function!(class_method, 1))?; }
-
Verify method signatures:
#![allow(unused)] fn main() { // Instance method fn instance_method(&self, arg: Value) -> Result<Value, Error> { // Method implementation... } // Class method fn class_method(arg: Value) -> Result<Value, Error> { // Method implementation... } // Method with ruby fn method_with_ruby(ruby: &Ruby, arg: Value) -> Result<Value, Error> { // Method implementation... } }
Module/Class Hierarchy Issues
Problem: Ruby modules or classes aren't defined correctly.
Solutions:
-
Check the correct nesting of defines:
#![allow(unused)] fn main() { // Define a module and a nested class let module = ruby.define_module("MyModule")?; let class = module.define_class("MyClass", ruby.class_object())?; // Define a nested module let nested = module.define_module("Nested")?; }
-
Verify class inheritance:
#![allow(unused)] fn main() { // Get the correct superclass let superclass = ruby.class_object::<RObject>()?; // Define a class with the superclass let class = ruby.define_class("MyClass", superclass)?; }
Debugging Ruby Exceptions
Custom Exception Handling
Problem: Ruby exceptions aren't properly caught or raised.
Solutions:
-
Define and use custom exception classes:
#![allow(unused)] fn main() { fn init(ruby: &Ruby) -> Result<(), Error> { let module = ruby.define_module("MyModule")?; // Define custom exceptions let std_error = ruby.exception_standard_error(); let custom_error = module.define_class("CustomError", std_error)?; Ok(()) } fn raise_custom_error(ruby: &Ruby) -> Result<(), Error> { Err(Error::new( ruby.class_path_to_value("MyModule::CustomError"), "Something went wrong" )) } }
-
Catch specific exception types:
#![allow(unused)] fn main() { fn handle_exceptions(ruby: &Ruby, val: Value) -> Result<Value, Error> { let result = val.funcall(ruby, "may_raise", ()); match result { Ok(v) => Ok(v), Err(e) if e.is_kind_of(ruby, ruby.exception_zero_div_error()) => { // Handle division by zero Ok(ruby.integer_from_i64(0)) }, Err(e) => Err(e), // Re-raise other exceptions } } }
Additional Resources
- Official Documentation: rb-sys and magnus
- Examples: Check the examples directory in rb-sys
- Community Support: Join the Slack channel
- Further Reading: See the Debugging chapter for more detailed debugging techniques