現在、RustでRubyのネイティブ拡張を書く場合、rutieを使うことが一般的だと思われる。(helixやruruは保守されていない。)
しかし、下記の理由で個人的に好ましくなかったため、ライブラリに頼らずRubyのネイティブ拡張を作る方法を調べた。
- 古いRustコンパイラをサポートするが、MaybeUninit等の、より安全なライブラリが利用されていない。
- unsafeに対する価値観が私と異なり、完全に安全とは言えない関数であってもunsafeとマークされていない。
下記のコードは HelloRust
モジュールをRust側で定義しているサンプルである。
Windows, 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