Cの関数ポインタとRustの関数ポインタ
# include <stdio.h>
typedef void (*rust_callback)(int);
rust_callback cb;
int register_callback(rust_callback callback) {
cb = callback;
return 1;
}
void trigger_callback() {
if (cb != NULL) {
cb(7);
} else {
printf("callback function is not set!");
}
}
(他言語関数インターフェイスのものを改変)
このようなコールバックを扱うCのライブラリを呼び出すRustのFFIを作りましょう。
他言語関数インターフェイスでは以下のように作るように書いてあります:
extern fn callback(a: i32) {
println!("I'm called from C with value {0}", a);
}
# [link(name = "ext")]
extern {
fn register_callback(cb: extern fn(i32)) -> i32;
fn trigger_callback();
}
fn main() {
unsafe {
register_callback(callback);
trigger_callback(); // Triggers the callback
}
}
これは勿論動作します。しかし、これは本当にCのAPIを正しく踏襲できているでしょうか?
元のCのプログラムにおいて、cb
にはNULL
を入れてもいいはずです。CのAPIでは、コールバックが必要ない場合は関数ポインタとしてNULL
を入れることはよく行われます。
register_callback(NULL); // callbackは登録しない
さてここで問題になるのはRust側のregister_callback
の定義です。NULL
に相当するのはstd::ptr::null_mut()
なのでこれを代入してみましょう:
use std::ptr::null_mut;
extern "C" {
fn register_callback(cb: extern "C" fn(i32)) -> i32;
}
fn main() {
unsafe { register_callback(null_mut()) };
}
これはコンパイルできません:
|
12 | unsafe { register_callback(null_mut()) };
| ^^^^^^^^^^ expected fn pointer, found *-ptr
|
= note: expected type `extern "C" fn(i32)`
found type `*mut _`
これはas
で明示的に変換しようとしてもできません。
Nullableな関数ポインタ
ところでCからRustのバインディングを生成できるrust-bindgenはどうやっているのでしょうか?上のCのコードをbindgen
してみましょう
pub type rust_callback = ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
extern "C" {
#[link_name = "\u{1}cb"]
pub static mut cb: rust_callback;
}
extern "C" {
pub fn register_callback(callback: rust_callback) -> ::std::os::raw::c_int;
}
#include<stdio.h>
があるので余計な部分がたくさん出てきますが、該当部分はこれだけです。
Option<extern fn(i32)>
を使えば良いようですね。
extern "C" fn callback(a: i32) {
println!("I'm called from C with value {0}", a);
}
# [link(name="ext")]
extern "C" {
fn register_callback(cb: Option<extern "C" fn(i32)>) -> i32;
fn trigger_callback();
}
fn main() {
unsafe {
register_callback(Some(callback));
trigger_callback();
}
unsafe {
register_callback(None);
trigger_callback();
}
}
$ cargo run
I'm called from C with value 7
callback function is not set!
上手くできました(/・ω・)/
参考
以上のソースコードは https://github.com/termoshtt/nullable_fn_ptr にあります