これのC++をRustに置き換えた版です。
https://github.com/termoshtt/julia.rs
追記 (2022/11/24)
以下の記述はFFIがどのような仕組みで動いているかを理解するには有用ですが、実用上は jlrs 等のメンテナンスされているcrateを使うことをおすすめします。
julia-sys crate
まず/usr/include/julia/julia.h
からbindgenを使ってRust bindingを生成します
bindgen \
--whitelist-function "jl_.*" \
--whitelist-type "jl_.*" \
--whitelist-var "jl_.*" \
/usr/include/julia/julia.h \
> src/julia.rs
bindgenはclangを使って#include
しているヘッダーのバインディングまで作ってしまうので、ホワイトリストで生成する型や関数名を指定します。これをinclude!
で取り込みます
#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]
include!("julia.rs");
bindgen
をbuild.rs
で使って動的に生成することも出来ますが今回は静的に作ることにしました。一部自動生成されたコードを手直ししているのでこの方が都合がいいことが多いです。
bindgen
はCのマクロ部分やstatic inlineなコードを生成出来ないので、この部分は手で変換します(´・ω・`)
pub unsafe fn jl_astaggedvalue(ptr: *mut jl_value_t) -> *mut jl_taggedvalue_t {
let p = ptr as *mut jl_taggedvalue_t;
p.offset(-1)
}
pub unsafe fn jl_typeof(ptr: *mut jl_value_t) -> *mut jl_datatype_t {
let tagged = jl_astaggedvalue(ptr);
let t = (*tagged).header & !15_usize;
t as *mut jl_datatype_t
}
pub unsafe fn jl_is_array(ptr: *mut jl_value_t) -> bool {
let ty = jl_typeof(ptr);
(*ty).name == jl_array_typename
}
とりあえず必要そうな最小限だけ実装しました。他のAPIもそのうち追加するかもしれません(予定は未定)
example crate
Rustで共有ライブラリを作るにはcdylib
形式にします
[lib]
crate-type = ["cdylib"]
[dependencies.julia-sys]
path = "../julia-sys"
そして前回のmake_twice_array
をRustで実装したのがこちら
use julia_sys::*;
#[no_mangle]
pub extern "C" fn make_twice_array(ptr: *mut jl_value_t) {
if !unsafe { jl_is_array(ptr) } {
panic!("This is not an array")
}
let array = ptr as *mut jl_array_t;
let data = unsafe {
let n = (*array).length;
let data = (*array).data as *mut f32;
std::slice::from_raw_parts_mut(data, n)
};
for v in data {
*v *= 2.0;
}
}
Juliaの方は共有ライブラリの名前だけ直します
b = ones(Float32, 2, 3)
ccall((:make_twice_array, "libexample"), Cvoid, (Any,), b)
println(b)
これでJuliaから呼び出せます
cargo build
LD_LIBRARY_PATH=$PWD/target/debug julia example.jl
やったぜ(/・ω・)/
課題
- エラー処理
- 配布方法
- Rust側のSafe wrapperの整備