Currently, when writing native extensions to Ruby in Rust, it seems common to use rutie. (Helix and ruru are not maintained.) However, I personally didn't like it for the following reasons, so I investigated how to make a native extension of Ruby without relying on the library.
The code below is a sample that defines the HelloRust
module on the Rust side.
Refer to Continued when running on Windows or Linux.
run.rb
require __dir__ + "/hello_rust"
if defined?(HelloRust)
puts "HelloRust is defined"
else
puts "HelloRust is not defined"
end
src/lib.rs
use std::ffi;
#[repr(transparent)]
pub struct Value(usize);
#[link(name = "ruby")]
extern "C" {
fn rb_define_module(name: *const i8) -> Value;
}
fn define_module(name: &ffi::CStr) -> Value {
unsafe { rb_define_module(name.as_ptr()) }
}
#[export_name = "Init_hello_rust"]
pub extern "C" fn init() {
let s = ffi::CString::new("HelloRust").unwrap();
define_module(&s);
}
build.rs
use std::process::Command;
fn libdir() -> String {
let output = Command::new("ruby")
.args(&["-e", "print RbConfig::CONFIG['libdir']"])
.output()
.expect("failed run ruby");
return String::from_utf8(output.stdout).unwrap();
}
fn main() {
println!("cargo:rustc-link-search={}", libdir());
}
Cargo.toml
[package]
name = "hello_rust"
version = "0.1.0"
authors = ["irxground"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
Makefile
.PHONY: build
build: hello_rust.bundle
hello_rust.bundle: cargo_build
cp -a target/release/libhello_rust.dylib $@
.PHONY: cargo_build
cargo_build:
cargo build --release
.PHONY: run
run: clean build
ruby run.rb
.PHONY: clean
clean:
rm *.bundle
cargo clean
$ make run
rm *.bundle
cargo clean
cargo build --release
Compiling hello_rust v0.1.0 (/Users/user/work/private/ruby_rust/minimam_rust_example)
Finished release [optimized] target(s) in 0.75s
cp target/release/libhello_rust.dylib hello_rust.bundle
ruby run.rb
HelloRust is defined