hiro99ma blog

何か技術的なこと

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 というものを使った。

C から Rust を呼ぶことについて書いてあるじゃないか。
Rust のことはわからないので内容は見ていないが、雰囲気としては Rust っぽい部分以外は普通な気がする。

おわりに

一応、C も Rust もお互いを呼べるようだった。

ただ組み込みで Rust を使うかというと、今の段階だと C がメインで Rust をライブラリにして呼び出すという形になるかな。
ncs みたいにプラットフォームとセットになっていると、他のしくみが入り込むとどうなるか心配という気持ちが先になってしまう。
並列処理もできるようだけど、言語でできるからどの環境でもできるとは限るまい。
プラットフォームが Rust も使って大丈夫になっていると安心なのだが。

あとは動的なメモリを使える環境かどうかだろうか。
そのアプリで使うメモリを最大限確保しても動かないといけないので、 それなら最初からメモリを static に確保しておこう、ということもしばしばだ。
そうなるとメモリの保護もいらないのかも? と思ったが、Rust がどこまでどうするか分かってないので考えても仕方ないな。

まあ、あれだ。
学習が大変という噂を聞いて尻込みしているのだよ。

< Top page