[Rust] cargo を使わずに rand と ndarray-linalg に依存するコードをビルド

50 行以下の使い捨てコードを書くときに, いちいち Cargo.toml に依存クレートを書き並べて全部コンパイルし直して... というのはちょっと面倒すぎますよね. その状況で使うのは (個人的には) だいたい rand か ndarray/ndarray-linalg に限られていますし, 事前にコンパイルしておいてそれを呼び出す形にすればハッピーじゃないかと. という訳で試してみました.

# rand の場合

まずは最もシンプルな例として rand を利用する場合を説明します.

## Step 1. rand をビルドしておく

github から欲しいバージョンのソースコードを落としてきて任意のディレクトリ (ここでは `/home/osanshouo/.dep`) に展開します. 

```shell_session
$ cd /home/osanshouo/.dep
$ wget https://github.com/rust-random/rand/archive/master.zip
$ unzip master.zip
$ mv rand-master rand-0.7.0 #任意で改名
```

これは普通にビルドすればよいでしょう.

```shell_session
$ cd rand
$ cargo build --release
```

`target/release/librand.rlib` または `target/release/deps/librand-*.rlib` が目標生成物です. 依存ライブラリは `target/release/deps/` に入っています. なお * は何らかの文字列です.

## Step 2. 作成した Rust コードをコンパイルする

続いて任意の作業ディレクトリ (ここでは `/home/osanshouo/dev`) に移動して, 普通にコードを作成します.

```rust:/home/osanshouo/dev/sample.rs
use rand::{Rng, thread_rng};

fn main() {
    let mut rng = thread_rng();

    for _ in 0..8 {
        println!("{}", rng.gen::<f64>() );
    }
}
```

次にこれを rustc でコンパイルします.

```shell_session
$ rustc --edition=2018 \
        -L dependency=/home/osanshouo/.dep/rand-0.7.0/target/release/deps \
        --extern rand=/home/osanshouo/.dep/rand-0.7.0/target/release/librand.rlib \
        ./sample.rs
```

(edition 2018 ではソースコードに明示的に書かなくてよくなりましたが) `extern crate` するクレートについては `--extern (crate)=(path)` という形で明示的に指定します. なお `target/release/deps/` に入っている方でも `target/release/librand.rlib` でもどちらでもいいです. さらに rand の依存ライブラリのパスもコンパイラに伝えなければならないので, `-L` オプションで `lib***.rlib` ファイルが入っているディレクトリを指定します. 

コンパイルが通ったらあとは実行するだけですね.

```shell_session
$ ./sample
```

# ndarray/ndarray-linalg の場合

もうちょっと面倒な例として ndarray-linalg を呼び出す場合を説明します. 

## Step 1. ndarray-linalg をビルドしておく

これは rand と同様です. なお ndarray は ndarray-linalg の依存ライブラリとして同時にビルドされるので, 個別にビルドする必要はありません.

```shell_session
$ cd /home/osanshouo/.dep/
$ wget https://github.com/rust-ndarray/ndarray-linalg/archive/0.11.1.tar.gz
$ tar xf 0.11.1.tar.gz
$ cd ndarray-linalg-0.11.1
$ cargo build --release --features intel-mkl
```

rand との違いは intel-mkl は外部ライブラリのラッパーという点です. いまの場合 intel-mkl-src クレートがビルド時にライブラリをダウンロードしてきて `target/release/build/intel-mkl-src-*/out` ディレクトリに配置してくれます. 

## Step 2. 作成した Rust コードをコンパイルする

例えば対称行列の固有値を求めるコードを書いたとしましょう.

```rust:/home/osanshouo/dev/eigenvalues.rs
use ndarray::{Array2, array};
use ndarray_linalg::{Eigh, lapack::UPLO};

fn main() {
    let a: Array2<f64> = array![[1.0, 0.5, 0.0],
                                [0.5, 1.0, 0.5],
                                [0.0, 0.5, 1.0]];
    
    let (w, _) = a.eigh(UPLO::Upper).unwrap();
    
    for eigenvalue in w.iter() {
        println!("{}", eigenvalue );
    }
}
```

これをコンパイルします. その際 intel-mkl ライブラリのパスを含める必要があります. フィーチャーフラグは ndarray-linalg のビルド時に立っていれば良いのでここでは要りません.

```shell-session
$ rustc --edition=2018 \
    ./eigenvalues.rs \
    -L native=/home/osanshouo/.dep/ndarray-linalg-0.11.1/target/release/build/intel-mkl-src-946ed936d19ba029/out \
    -L dependency=/home/osanshouo/.dep/ndarray-linalg-0.11.1/target/release/deps \
    --extern ndarray=/home/osanshouo/.dep/ndarray-linalg-0.11.1/target/release/deps/libndarray-3057c4819317fd3c.rlib \
    --extern ndarray_linalg=/home/osanshouo/.dep/ndarray-linalg-0.11.1/target/release/deps/libndarray_linalg-16a771331f762fc8.rlib
```

## Step 3. 実行する

これで実行ファイル `./eigenvalues` が生成されたので実行しましょう. ただし, 環境によるのかもしれませんが, 手元の環境では intel-mkl ライブラリのパスがバイナリに埋め込まれていなかったため, 環境変数 `LD_LIBRARY_PATH` を用いて指定しておく必要がありました.

```shell_session
$ export LD_LIBRARY_PATH=/home/osanshouo/.dep/ndarray-linalg-0.11.1/target/release/build/intel-mkl-src-946ed936d19ba029/out:$LD_LIBRARY_PATH
$ ./eigenvalues
```

# 所感

コンパイル時のオプションがとても長くなってしまいましたが, いったんビルドしておけばそのマシンではコマンドは不変なので, スクリプトにするとかエイリアスを設定するとかいろいろやりようはあると思います.

rand と ndarray/ndarray-linalg 以外のクレートでも, `cargo build` するときにオプション `--verbose` をつけておくと実際に実行されている rustc コマンドが表示されるので, それを見れば応用できるはずです.

そもそもこういうことは Python の pip みたいに言語側がサポートしてくれると有難いんですけどね. Rust のシステムプログラミング言語という性質上, cargo のようにプロジェクトごとにライブラリをコンパイルして保持しておくという戦略にしないと依存関係壊れて死ぬというのもわかりますが. でも科学技術計算向けには需要があると思うので, `cargo install` できるバイナリクレート作ったら Rust で使い捨てコードを量産する界隈が喜ぶんじゃないでしょうか？ 

# 参考文献

https://doc.rust-lang.org/rustc/command-line-arguments.html
https://www.reddit.com/r/rust/comments/5pe6vr/how_to_bundle_dependencies_without_cargo/