rust: RustライブラリとC言語を混ぜて使えるのか
2025/02/07
はじめに
プログラミング言語の Rust。
組み込み向けも視野に入っているので興味はある。
Bitcoin 界隈でも最近は Rust をよく使う傾向にあるし。
ただ、学習が大変そうということでなかなか手が出せていない。
もし学習したとしても、Rust だけで全部書けるようになるのは時間がかかりそうだ。 Rust で書いてそれを C言語から呼び出せるようにするとよいだろうし、逆に Cのライブラリを Rust から呼び出したいこともあるだろう。
そんなことができるのか調べておこう。
C言語と C++
C言語と他の言語というパターンでよくあるのが C++ だ。
C++ は namespace
だったりオーバーロードだったりがあるのでメンバー関数ではない関数であっても普通にコンパイルするとオブジェクトでは C言語とは違う名前ルールになっている。
こんな感じだ。
#include <iostream>
int printHello(const char *name)
{
std::cout << "hello " << name << std::endl;
return int(name[0]);
}
nm
でオプションを付けないと何だかわからない名前になる。
$ gcc -c a.cpp
$ nm a.o
U _GLOBAL_OFFSET_TABLE_
00000000000000af t _GLOBAL__sub_I__Z10printHelloPKc
0000000000000000 T _Z10printHelloPKc
0000000000000059 t _Z41__static_initialization_and_destruction_0ii
U _ZNSolsEPFRSoS_E
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U __cxa_atexit
U __dso_handle
$ nm -C a.o
U _GLOBAL_OFFSET_TABLE_
00000000000000af t _GLOBAL__sub_I__Z10printHelloPKc
0000000000000000 T printHello(char const*)
0000000000000059 t __static_initialization_and_destruction_0(int, int)
U std::ostream::operator<<(std::ostream& (*)(std::ostream&))
U std::ios_base::Init::Init()
U std::ios_base::Init::~Init()
U std::cout
U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000000000 b std::__ioinit
U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
U __cxa_atexit
U __dso_handle
extern "C"
で囲むと Cルールの名前になる。
#include <iostream>
extern "C" {
int printHello(const char *name)
{
std::cout << "hello " << name << std::endl;
return int(name[0]);
}
}
$ gcc -c a.cpp
$ nm a.o
U _GLOBAL_OFFSET_TABLE_
00000000000000af t _GLOBAL__sub_I_printHello
0000000000000059 t _Z41__static_initialization_and_destruction_0ii
U _ZNSolsEPFRSoS_E
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U __cxa_atexit
U __dso_handle
0000000000000000 T printHello
囲んでない方を nm
でデマングルすると引数まで出てくるのはオーバーロード対応のため記号化された部分を置き換えたのだろう。
Java の JNI でもそういうのが出てきた気がする。
こんな感じで C と C++ を混ぜる場合は extern "C"
をつけて Cの名前ルールにあわせる。
Rust と FFI
以前、Go言語から Rustのライブラリを使いたいことがあって調べたときに FFI というものを使った。
- FFI - The Rustonomicon
- Calling foreign functions
- Rust が Cの関数を呼び出す
- Calling Rust code from C
- Cが Rustの関数を呼び出す
- Callbacks from C code to Rust functions
- Rust の関数をコールバックしてもらうよう Cの関数の引数で与えて、
trigger_callback()
で Cから登録した関数を呼び出す
- Rust の関数をコールバックしてもらうよう Cの関数の引数で与えて、
- Targeting callbacks to Rust objects
- これも同じ感じだが、Rust のデータも一緒に登録して Cに保持してもらい
trigger_callback()
で一緒に返してもらう - 非同期だとこのパターンが多いかな
- 値ではなくアドレスを登録するので、たぶん Rust 側ではそのメモリを保護したりするのかな?
- これも同じ感じだが、Rust のデータも一緒に登録して Cに保持してもらい
- Calling foreign functions
C から Rust を呼ぶことについて書いてあるじゃないか。
Rust のことはわからないので内容は見ていないが、雰囲気としては Rust っぽい部分以外は普通な気がする。
おわりに
一応、C も Rust もお互いを呼べるようだった。
ただ組み込みで Rust を使うかというと、今の段階だと C がメインで Rust をライブラリにして呼び出すという形になるかな。
ncs みたいにプラットフォームとセットになっていると、他のしくみが入り込むとどうなるか心配という気持ちが先になってしまう。
並列処理もできるようだけど、言語でできるからどの環境でもできるとは限るまい。
プラットフォームが Rust も使って大丈夫になっていると安心なのだが。
あとは動的なメモリを使える環境かどうかだろうか。
そのアプリで使うメモリを最大限確保しても動かないといけないので、
それなら最初からメモリを static に確保しておこう、ということもしばしばだ。
そうなるとメモリの保護もいらないのかも?
と思ったが、Rust がどこまでどうするか分かってないので考えても仕方ないな。
まあ、あれだ。
学習が大変という噂を聞いて尻込みしているのだよ。