この記事はRust Advent Calendar 2020の 10 日目の記事です。
- 9 日目はこちら → no_std の Rust on Linux で Hello, world!する - Qiita
- 11 日目はこちら → Rustの型システムの恩恵:言語処理系としての観点から - Qiita
こんにちは、@tasshi です。
同日にうっかりもう 1 つアドベントカレンダーを登録していて1、
慌てて書いているうちにこちらは 1 日遅れとなってしまいました。
まだ 11 日未明なのでセーフだと信じて強い気持ちで投稿します。
概要
Rust の Re-export の使い方について調べていたところ、気になるトピックがありました。
Guidance around reexporting · Issue #176 · rust-lang/api-guidelines
ざっくりと説明すると、
「公開している API のインターフェースで外部クレートを利用している場合は、
その外部クレート全体を Re-export(再公開)するのが良い」
という内容です。
現在も Open issue のため今後方針が変わる可能性もありますが、現時点ではこれが一番良さそうということで記事に書き起こしてみました。
Re-exporting(再公開)について
pub
キーワードとuse
キーワードを組み合わせると、スコープに持ち込んだ名前を外に対して公開することができます。
これを**Re-exporting(再公開)**と呼びます。
pub mod public_module {
pub mod submodule_in_public {
pub fn my_function() {}
}
}
mod private_module {
pub mod submodule_in_private {
pub fn my_function() {}
}
}
pub use crate::private_module::submodule_in_private; //非公開サブモジュールからRe-export
pub use crate::public_module::submodule_in_public; //公開サブモジュールからRe-export
TRPL (en): Bringing Paths Into Scope with the use Keyword - The Rust Programming Language
TRPL (ja): use キーワードでパスをスコープに持ち込む - The Rust Programming Language 日本語版
Re-export は、以下のような場面で効果を発揮します。
- 階層の深いサブモジュールから外部公開するものを浅い階層へ取り出す
- 非公開サブモジュールで実装を隠蔽しつつ公開用インターフェースのみ公開する
- 外部モジュールをクレートの一部として公開する
Rust のモジュールシステムについては κeen さんの記事を読むのがおすすめです。
Rust のモジュールの使い方 2018 Edition 版 | κeen の Happy Hacκing Blog
公開 API のインターフェースで他のクレートの型を受け取る/返す
さて、本題に入ります。
公開 API が外部モジュールの提供する型を受け取ったり、返したりする場合を考えます。
ここでは架空のクレートimgconverter
を例として進めていきます。
imgconverter
はimage::DynamicImage
を受け取って何かしらの変換を行う関数convert_image
を提供しています。
use image::DynamicImage;
pub fn convert_image(image: DynamicImage) -> DynamicImage {
// なんらかの処理
}
これをアプリケーション側で利用してみましょう。
main.rs
ではimage::DynamicImage
を生成し、convert_image
関数で変換を行います。
Cargo.toml には crates.io からimage
クレートを探して最新バージョンを持ってきます。2
use image::DynamicImage;
use imgconverter::convert_image;
fn main() {
let src_image = DynamicImage::new_rgb8(800, 600);
let dst_image = convert_image(src_image);
}
# 一部抜粋
[dependencies]
image = "0.23.12"
それではコンパイルしてみましょう。
❯ cargo build
Compiling app v0.1.0
error[E0308]: mismatched types
--> src/bin/main.rs:6:35
|
6 | let dst_image = convert_image(src_image);
| ^^^^^^^^^ expected enum `image::dynimage::DynamicImage`, found enum `image::DynamicImage`
|
= note: perhaps two different versions of crate `image` are being used?
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
error: could not compile `app`.
コンパイルエラーとなってしまいました。
原因
実はimgconverter
は少し古いバージョンのimage
クレートに依存していました。
# 一部抜粋
[dependencies]
image = "0.19.0"
引数として与えられたimage::DynamicImage
のバージョンが0.23.12
であるのに対して、
convert_image
の要求するimage::DynamicImage
のバージョンは0.19.0
です。
要求するバージョンと与えられたバージョンとが違うためコンパイルエラーとなっています。
依存する型を Re-export する
先ほどのエラーは、ライブラリクレートの依存関係を確認して適切なバージョンのimage
クレートを使うことで防ぐことができます。
しかし、クレートの利用者に毎回適切なバージョンのimage
クレートを見つけさせるのは開発体験が良いとは言えません。
利用者をバージョン違いのストレスから開放するために依存している型を Re-export してあげましょう。
新しいimgconverter
ではimage::DynamicImage
をそのまま再公開しています。
pub use image::DynamicImage;
pub fn convert_image(image: DynamicImage) -> DynamicImage {
// なんらかの処理
return image;
}
アプリケーション側ではimgconverter::DynamicImage
を利用することで適切なバージョンのDynamicImage
を利用できます。
use imgconverter::{convert_image, DynamicImage};
fn main() {
let src_image = DynamicImage::new_rgb8(800, 600);
let dst_image = convert_image(src_image);
}
これでmain.rs
が正常にビルドできるようになりました。
返り値にさらに操作を加える
まだ十分ではありません。
変換されたdst_image
にさらに画像処理を加えてみましょう。
image::imageops
にはDynamicImage
に対する様々な画像処理関数が用意されています。34
今回はimage::imageops::flip_horizontal
を利用して画像を左右反転します。
さてどうなるでしょうか。
use image::imageops;
use imgconverter::{convert_image, DynamicImage};
fn main() {
let src_image = DynamicImage::new_rgb8(800, 600);
let dst_image = convert_image(src_image);
imageops::flip_horizontal(&mut dst_image); // コンパイルエラー
}
これもバージョン違いでエラーになります。
このように直接インターフェースで利用している型だけを Re-export しても、そのクレートの他の機能を使うことができません。
依存する型を含むクレート全体を Re-export する
これを防ぐためにタイトルで出てきたクレート全体の Re-export を行います。
imgconverter
ではimage
を再公開します。
pub use image; //imageクレート全体をRe-export
use image::DynamicImage;
pub fn convert_image(image: DynamicImage) -> DynamicImage {
// なんらかの処理
return image;
}
アプリケーション側では再公開されたimgconverter::image
を利用します。
use imgconverter::convert_image;
use imgconverter::image::{imageops, DynamicImage};
fn main() {
let src_image = DynamicImage::new_rgb8(800, 600);
let mut dst_image = convert_image(src_image);
imageops::flip_horizontal(&mut dst_image);
}
これで利用者はインターフェースで利用されているクレートのバージョンを意識しなくて良くなりました。
検証コード
上記のバージョン違いのコードも含まれているのでビルドはできません。
tasshi-playground/rust-re-export-dependencies: Investigation for usage of "Re-exporting"
参考ページ
-
RFC API guideline - re-export a whole crate if exporting any - The Rust Programming Language Forum
-
quickcheck
クレート(ランダムテストフレームワーク)がrand::Rng
だけを再公開していて、利用者がバージョンを意識しないといけないことについての投稿 - 本筋とは関係ないけど、バージョン 1.0 以上のクレートなんかほとんどないだろって言われてちょっと笑った
-
-
Crates which re-export types from other crates should also re-export the entire crate · Issue #170 · rust-lang/api-guidelines
- 上の議論の API guideline 本体への issue
-
This has the problem that all users of quickcheck need to depend on the same version of rand as quickcheck if they want to use the rest of rand's API.
-
Guidance around reexporting · Issue #176 · rust-lang/api-guidelines
- おそらく現時点(2020/12/11)で最新の議論
-
If your crate has a public dependency, then the corresponding crate should be reexported by your crate (via pub extern).
余談
これって依存ライブラリ同士で Re-export しているクレートのバージョンが違った場合どうするんですかね。
-
正確には
image
クレートの提供する画像系の型全般に対する画像処理関数群です ↩