Cと少しのRust

CまたはC++のプロジェクト内でRustのコードを使うためには、主に次の2つの対応をします。

  • Cが扱いやすいAPIをRustに作ります
  • 外部ビルドシステムにRustプロジェクトを組み込みます

cargomesonは別として、ほとんどのビルドシステムはRustをサポートしていません。 そのため、自分のクレートとその依存関係をコンパイルするには、cargoを使うのが一番です。

プロジェクトの準備

いつもどおり、新しいcargoプロジェクトを作成します。

通常のRustのターゲットではなく、システムライブラリを出力するように、cargoに指示するフラグがあります。 クレートの残り部分と異なる名前を付けたい場合、ライブラリに対して、別の名前を設定することもできます。

[lib]
name = "your_crate"
crate-type = ["cdylib"]      # 動的ライブラリを作ります
# crate-type = ["staticlib"] # 静的ライブラリを作ります

C APIの作成

C++は、Rustコンパイラがターゲットにできる安定したABIを持っていないため、別言語との相互運用には、CのABIを使用します。 CとC++のコード内でRustを使うとき、このことに例外はありません。

#[no_mangle]

Rustコンパイラは、シンボル名をネイティブコードリンカが期待するものとは異なるものにマングルします。 そのため、Rustの外にエクスポートするRustの関数は、マングルしないようにコンパイラに指示する必要があります。

extern "C"

デフォルトでは、Rustに書いたいずれの関数もRustのABI(これも安定化されていません)を使います。 代わりに、外に公開するFFI APIはシステムABIを使うように、コンパイラに指示する必要があります。

プラットフォームによっては、特定のABIバージョンをターゲットにしたい場合があります。 ABIについては、ここにドキュメントがあります。


これらの部品をまとめると、おおよそ次のような関数になります。

#[no_mangle]
pub extern "C" fn rust_function() {

}

RustプロジェクトでCコードを使った時と同様に、異なる言語間で理解できるデータ型に変換する必要があります。

リンクとより大きなプロジェクトの状況

ここまでで、問題の半分は解決しました。 これをどうやって使うのでしょうか?

それは、プロジェクトやビルドシステムに強く依存します。

cargoは、プラットフォームと設定に依存して、my_lib.somy_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();