Rustと少しのC

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

  • 公開されているCのAPIを、Rustで使えるようにラッピングする
  • CまたはC++のコードを、Rustのコードと一緒にビルドする

C++は、Rustコンパイラがターゲットにできる安定したABIを持っていないため、CまたはC++とRustとを組み合わせるときは、CのABIを使うのがお勧めです。

インタフェースの定義

RustからCまたはC++のコードを使う前に、リンクされるコードにどのようなデータ型や関数シグネチャが存在するかを、(Rustに)定義する必要があります。 CまたはC++では、このデータを定義したヘッダファイル(.hまたは.hpp)をインクルードします。 Rustでは、これらの定義を手動で変換するか、定義を自動生成するツールを使うか、のいずれかが必要です。

まずは、C/C++からRustに、定義を手動で変換する方法を説明します。

Cの関数とデータ型のラッピング

通常、CまたはC++で書かれたライブラリは、公開インタフェースで使用する全ての型と関数を定義するヘッダファイルを提供します。 ヘッダファイルの例は次の通りです。

/* File: cool.h */
typedef struct CoolStruct {
    int x;
    int y;
} CoolStruct;

void cool_function(int i, char c, CoolStruct* cs);

Rustに変換すると、このインタフェースは次のようになります。

/* File: cool_bindings.rs */
#[repr(C)]
pub struct CoolStruct {
    pub x: cty::c_int,
    pub y: cty::c_int,
}

pub extern "C" fn cool_function(
    i: cty::c_int,
    c: cty::c_char,
    cs: *mut CoolStruct
);

各部分の説明をするため、この定義を1つずつ見ていきましょう。

#[repr(C)]
pub struct CoolStruct { ... }

デフォルトでは、Rustはstructに含まれるデータの順序やパディング、サイズを保証しません。 Cのコードとの互換性を保証するために、#[repr(C)]アトリビュートを使います。 このアトリビュートにより、Rustコンパイラは、構造体のデータをCと同じルールで構成します。

pub x: cty::c_int,
pub y: cty::c_int,

CまたはC++がintcharを定義する方法は柔軟であるため、ctyで定義されているプリミティブデータ型の使用をお勧めします。 ctyは、Cの型をRustの型にマップします。

pub extern "C" fn cool_function( ... );

このステートメントは、cool_functionという名前の、CのABIを使った関数シグネチャを定義します。 関数本体の定義なしにシグネチャを定義することで、この関数の定義は別の場所で与えられるか、静的ライブラリから最終的なライブラリまたはバイナリにリンクされる必要があります。

    i: cty::c_int,
    c: cty::c_char,
    cs: *mut CoolStruct

上記のデータ型と同様に、Cと互換の定義を使って、関数の引数のデータ型を定義します。 わかりやすくするために、引数の名前は同じままにしてあります。

*mut CoolStructという新しい型があります。Cは、&mut CoolStructのようなRustの参照という概念を持っていません。 代わりに、生ポインタを使います。 このポインタの参照外しは、unsafeです。また、このポインタは実際にnullポインタになる可能性があります。 CまたはC++のコードとやり取りする時には、Rustが通常行う保証を確実にするように気をつける必要があります。

インタフェースの自動生成

面倒であり間違いの元である手動のインタフェース生成ではなく、bindgenと呼ばれるインタフェース変換を自動で行ってくれるツールがあります。 bindgenの使用手順は、bindgenユーザマニュアルを参照して下さい。 bindgenを使用するための一般的なプロセスは、次の通りです。

  1. Rustで使いたいインタフェースやデータ型を定義している全てのCまたはC++ヘッダを集めます。
  2. ステップ1で集めたヘッダファイルを#include "..."するbindings.hファイルを書きます。
  3. bindgenにコンパイルフラグとコードと共に、bindings.hを与えます。 ヒント:#![no_std]互換なコードを生成するために、Builder.ctypes_prefix("cty")--ctypes-prefix=ctyを使って下さい。
  4. bindgenは、端末のウィンドウに生成したRustコードを出力します。これは、bindings.rsのようなプロジェクトのファイルにパイプすることができます。 外部ライブラリとしてコンパイル、リンクされたC/C++コードとやり取りするために、このファイルをRustプロジェクトで使用できます。

C/C++コードのビルド

Rustコンパイラは、CまたはC++のコード(または、Cのインタフェースを提供する他の言語のコード)をコンパイルする方法を直接は知らないため、Rustでないコードは事前にコンパイルする必要があります。

組込みプロジェクトでは、C/C++のコードを(cool-library.aのような)静的なアーカイブにコンパイルすることを意味します。 このアーカイブは、最終リンク時に、Rustのコードに組み込むことができます。

使おうとしているライブラリが、既に静的なアーカイブとして配布されている場合、そのコードを再度ビルドする必要はありません。 提供されているインタフェースのヘッダファイルを、上述の方法で、単に変換するだけです。 そして、その静的なアーカイブをコンパイル/リンク時に組み込みます。

もしコードがソースファイルとして存在するのであれば、C/C++のコードを静的ライブラリとしてコンパイルする必要があります。 (makeCMakeのような)ビルドシステムを使うか、必要なコンパイルステップをccクレートを使って移植するか、いずれかの方法を取れます。 どちらの方法でも、build.rsスクリプトを使う必要があります。

Rustのbuild.rsビルドスクリプト

build.rsスクリプトは、Rustの構文で書かれたファイルです。 このスクリプトは、プロジェクトの依存をビルドした後、プロジェクトをビルドする前に、コンパイルを行っているコンピュータ上で実行されます。

完全なリファレンスはここにあります。 build.rsスクリプトは、(bindgenのようなツールを用いて)コードを生成したり、 Makeのような外部ビルドシステムを呼び出したり、ccクレートを使ってC/C++を直接ビルドしたりするのに便利です。

外部ビルドシステムの使用

複雑な外部プロジェクトや外部ビルドシステムを使うプロジェクトに関しては、[std::process::Command]を使って、他のビルドシステムに対して「シェルを呼び出す」のが最も簡単でしょう。 これにより、相対パスを渡り歩いたり、(make libraryのような)固定のコマンドを呼び出したり、その後、targetビルドディレクトリにある静的ライブラリをコピーしたりすることができます。

作っているクレートがno_stdな組込みプラットフォームをターゲットにしていたとしても、build.rsはクレートをコンパイルしているコンピュータ上でのみ動作します。 そのため、コンパイルしているホスト上で動作する、あらゆるRustのクレートを使うことができます。

C/C++コードをccクレートでビルド

依存や複雑さが少ないプロジェクトや、(最終バイナリや実行ファイルではなく)静的ライブラリを作成するためにビルドシステムを修正するのが難しいプロジェクトであれば、 代わりにccクレートを使う方が簡単でしょう。 ccクレートは、ホスト上のコンパイラへの、扱いやすいRustインタフェースを提供します。

単一のCファイルを静的ライブラリにコンパイルする最も単純な例では、ccクレートを使ったbuild.rsスクリプトは次のようになります。

extern crate cc;

fn main() {
    cc::Build::new()
        .file("foo.c")
        .compile("libfoo.a");
}