C に少し Rust を混ぜる
C または C++ のプロジェクト内で Rust コードを使う作業は、主に 2 つの部分から成ります。
- Rust で C フレンドリーな API を作成する
- Rust プロジェクトを外部のビルドシステムに組み込む
cargo と meson を除けば、ほとんどのビルドシステムには Rust のネイティブサポートがありません。
そのため、crate とその依存関係のコンパイルには、
cargo をそのまま使うのがたいてい最善です。
プロジェクトのセットアップ
通常どおり、新しい cargo プロジェクトを作成します。
cargo に通常の Rust ターゲットではなく、
システムライブラリを出力させるためのフラグがあります。
これにより、必要であれば、ライブラリの出力名を
crate の他の部分とは異なる名前に設定することもできます。
[lib]
name = "your_crate"
crate-type = ["cdylib"] # 動的ライブラリを作成
# crate-type = ["staticlib"] # 静的ライブラリを作成
C API の構築
C++ には Rust コンパイラがターゲットにできる安定した ABI がないため、
異なる言語間の相互運用には C を使います。これは、Rust を
C や C++ のコード内で使う場合も例外ではありません。
#[no_mangle]
Rust コンパイラは、シンボル名をネイティブコードのリンカが期待するものとは 異なる形でマングルします。 そのため、Rust が Rust の外部で使うために公開する関数には、 コンパイラによってマングルされないよう指定する必要があります。
extern "C"
デフォルトでは、Rust で書いた関数はすべて Rust ABI を使用します(これも安定化されていません)。 その代わり、外部向けの FFI API を構築する際には、 システム ABI を使うようコンパイラに指示する必要があります。
プラットフォームによっては特定の ABI バージョンをターゲットにしたい場合があります。それらは こちら に文書化されています。
これらを組み合わせると、おおよそ次のような関数になります。
#[no_mangle]
pub extern "C" fn rust_function() {
}
Rust プロジェクト内で C コードを使うときと同じように、今度は
アプリケーションの残りの部分が理解できる形にデータを変換したり、
その形から変換したりする必要があります。
リンクとプロジェクト全体の文脈
これで、問題の半分は解決しました。 では、これをどう使うのでしょうか。
これはプロジェクトやビルドシステムに大きく依存します
cargo は、プラットフォームと設定に応じて、
my_lib.so/my_lib.dll または my_lib.a ファイルを作成します。
このライブラリは、ビルドシステムから単純にリンクできます。
ただし、C から Rust 関数を呼び出すには、 関数シグネチャを宣言するためのヘッダーファイルが必要です。
Rust-ffi API 内のすべての関数には、対応するヘッダー関数が必要です。
#[no_mangle]
pub extern "C" fn rust_function() {}
これは次のようになります。
void rust_function();
などです。
このプロセスを自動化するためのツールとして、 cbindgen があります。これは Rust コードを解析し、 そこから C および C++ プロジェクト向けのヘッダーを生成します。
この時点では、C から Rust 関数を使うのは、 ヘッダーをインクルードして呼び出すだけです。
#include "my-rust-project.h"
rust_function();