項目24:APIに型が登場する依存ライブラリは再エクスポートしよう
LT;DR
Cargo は 2 つの異なるバージョンのクレートを「直接」利用することはできないが、「間接」的には可能 他のクレートの型を API に含める場合は、事前によく考えること
hr.icon
e.g. rand のバージョンが混在しているケース code:Cargo.toml
# ...
rand = "=0.8.5"
dep-lib = { path = "dep-lib" }
code:dep-lib/Cargo.toml
# ...
name = "dep_lib"
path = "src/lib.rs"
rand = "=0.7.3"
上記のバージョン間では、rand::gen_range() メソッドの シグネチャ が異なる 0.7.x: low と height の 2 の引数を取る
0.8.x: 単一のパラメータ range を取る
しかしながら、上記のバージョンを組み合わせたバイナリは問題なく動作する
しかし、ライブラリクレートが API の一部として依存クレートの型を再エクスポートする(公開依存 となる)と話がややこしくなる e.g. dep-lib の API に 0.7.x の Rng が含まれているケース
code:dep-lib/src.rs
pub fn pick_number_with<R: Rng>(rng: &mut R, n: usize) -> usize {
rng.gen_range(0, n)
}
この関数を src/main.rs から呼び出そうとすると、コンパイルが通らない
code:src/main.rs
let mut rng = rand::thread_rng();
let max: usize = rng.gen_range(1..=100);
let choice = dep_lib::pick_number_with(&mut rng, max);
これは src/main.rs が実装している rand_core::RngCore は RngCore_v0_8_5 だが、dep-lib が要求しているのは RngCore_v0_7_3 であるから
「他のクレートの型を API に含める場合は、事前によく考えよう」
含めてしまうと、依存クレートがメジャーバージョンアップすると、そのクレートも自動的にメジャーバージョンアップする必要がある
warning.icon ただし、rand は標準クレートに準ずるもので広く使われており、自身が依存するクレートも少ないので、上記は悪い選択ではない
どう解決するか?
重要なのは、同じクレートの 2 つのバージョンを「直接」使うことはできない が、「間接」的には使うことが可能 である点
バイナリクレートの作者観点(src/main.rs 側)だと対応するのが非常に手間
e.g. バージョン 0.7 の rand を直接使わず、ラッパクレートを間に挟む
一方、ライブラリクレートの作者観点(dep-lib 側)だと簡単
明示的に以下のどちらかを再エクスポートすれば良い
API が使用している型
依存しているクレート
今回の例では、Rng や RngCore だけでなく、これらのインスタンスを生成する関数(e.g. thread_rng)も利用可能になるため、後者が良い
code:dep-lib/src/lib.rs
pub use rand;
これにより、src/main.rs では v0.7 の rand を dep_lib から指定できるようになる
code:src/main.rs
use dep_lib::rand::Rng;
let mut prev_rng = dep_lib::rand::thread_rng();
let max: usize = prev_rng.gen_range(5, 10);
let choice = dep_lib::pick_number_with(&mut prev_rng, max);