50 行以下の使い捨てコードを書くときに, いちいち Cargo.toml に依存クレートを書き並べて全部コンパイルし直して... というのはちょっと面倒すぎますよね. その状況で使うのは (個人的には) だいたい rand か ndarray/ndarray-linalg に限られていますし, 事前にコンパイルしておいてそれを呼び出す形にすればハッピーじゃないかと. という訳で試してみました.
rand の場合
まずは最もシンプルな例として rand を利用する場合を説明します.
Step 1. rand をビルドしておく
github から欲しいバージョンのソースコードを落としてきて任意のディレクトリ (ここでは /home/osanshouo/.dep
) に展開します.
$ cd /home/osanshouo/.dep
$ wget https://github.com/rust-random/rand/archive/master.zip
$ unzip master.zip
$ mv rand-master rand-0.7.0 #任意で改名
これは普通にビルドすればよいでしょう.
$ cd rand
$ cargo build --release
target/release/librand.rlib
または target/release/deps/librand-*.rlib
が目標生成物です. 依存ライブラリは target/release/deps/
に入っています. なお * は何らかの文字列です.
Step 2. 作成した Rust コードをコンパイルする
続いて任意の作業ディレクトリ (ここでは /home/osanshouo/dev
) に移動して, 普通にコードを作成します.
use rand::{Rng, thread_rng};
fn main() {
let mut rng = thread_rng();
for _ in 0..8 {
println!("{}", rng.gen::<f64>() );
}
}
次にこれを rustc でコンパイルします.
$ 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
ファイルが入っているディレクトリを指定します.
コンパイルが通ったらあとは実行するだけですね.
$ ./sample
ndarray/ndarray-linalg の場合
もうちょっと面倒な例として ndarray-linalg を呼び出す場合を説明します.
Step 1. ndarray-linalg をビルドしておく
これは rand と同様です. なお ndarray は ndarray-linalg の依存ライブラリとして同時にビルドされるので, 個別にビルドする必要はありません.
$ 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 コードをコンパイルする
例えば対称行列の固有値を求めるコードを書いたとしましょう.
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 のビルド時に立っていれば良いのでここでは要りません.
$ 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
を用いて指定しておく必要がありました.
$ 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/