Rustのpub useの使い所
UFOの夏が始まる時期なので初投稿です。
先日「pub useの使い所わからん~」という嘆きを見かけたのでいくつか紹介していきます。
pub use
って何
そもRustではuse
を用いることでスコープ外の名前をそのスコープに入れることが出来ます。名前をスコープ内で展開してあげるイメージです。
mod foo {
pub fn foofn() {}
}
mod bar {
use crate::foo;
// equals to:
// mod foo {
// pub fn foofn() {}
// }
pub fn barfn() {
foo::foofn()
}
}
pub use
はその展開された名前をpubで修飾する、つまり外部からでも参照できるようにするものです。
pub mod foo {
pub fn foofn() {}
}
mod bar {
pub use crate::foo;
}
mod baz {
use crate::bar::foo;
fn bazfn() {
foo::foofn()
}
}
このように外にあるモジュールをpub use
することをre-export(再公開)と呼んでいます。
pub use
の使い所さん
ではpub use
をどういうところで使うべきなのでしょう。
開発時のモジュール構造を隠蔽する
1つ目は開発時にのみ大きな意味を持つが、外部から使うときには意識させるべきでないモジュール構造を隠蔽するときです。
皆さん大好きThe bookにはこのようなセクションがあります:https://doc.rust-lang.org/nightly/book/ch14-02-publishing-to-crates-io.html#exporting-a-convenient-public-api-with-pub-use
めちゃくちゃ詳しく書いてありますね、もしかしたらこの記事はいらないかもしれません。
要は、外から使うときにart::kinds
のkinds
って汎用的すぎて意味を持たないよね、ならart::PrimaryColor
で使えるようにすると楽じゃん、というやつです。
もう少し実用的な例はlibc crateです。
libc crateでは、様々なターゲットの様々な型や定数、関数などを提供しています。管理する範囲が広範なので、OSやpointer widthごとにモジュールを分けてアイテムを置いています。例えば、Androidのx86_64環境向けのコードはsrc/unix/linux_like/android/b64/x86_64/mod.rs
に置いてあります。
これをそのまま公開してしまうと、ユーザーはlibc::unix::linux_like::android::b64::x86_64::foo
と、めちゃくちゃ深く掘らなければ目的の名前を手に入れることができません。不便ですね。
これを回避するためにlibcではそれぞれのターゲットごとにモジュールを下から再公開していき、すべてのアイテムをトップモジュールから使えるようにしています:https://github.com/rust-lang/libc/blob/a78ab12c798012c581ac4e23ec767e63d1464c35/src/unix/linux_like/mod.rs#L1670-L1686
こうすることでユーザー側がlibc::foo
するだけで済むに留まらず、開発側でもlibc::unix::linux_like
とlibc::unix::linux_like::android
で同じ名前が使われていた場合にそれをwarningとして発見することもできます。便利ですね。
例えば、src/unix/linux_like/linux/mod.rs
にあるuseconds_t
をsrc/unix/linux_like/mod.rs
に再定義すると、
といったようなwarningが出ます。CI上で-D warnings
しておけば簡単に気づけるという寸法です。
自クレートAPI内で露出している外部クレートのバージョンを合わせる
もう一つ考えられる状況として、自クレートから公開しているAPIが外部クレートを必要としている場合に、ユーザー側にバージョンの差異を(できるだけ)意識させないというものがあります。
できる限り、自クレートから公開するAPI、トレイトや関数などの定義に外部クレート依存のものを置きたくはありませんが、そうした方が取り回しやすくなる場合も多いです。
例えば、actix-webのトレイトの一つであるMessageBody
は外部クレートのBytes
構造体をResult
に包んで返します:https://docs.rs/actix-web/3.3.2/actix_web/dev/trait.MessageBody.html#tymethod.poll_next
このような場合、露出している側(i.e. actix-web)はある一つのバージョンのみを期待します。actix-web v3系ではbytes v0.5.xが使われることを期待します。
しかし、ユーザー側にこのようなバージョンの差異を意識させるのは少し心苦しいものですし、露出している外部クレートのバージョンを更新するたびにユーザーコードに影響があることを考えるのはしんどいです。
そこで、外部クレートを再公開することにより、この痛みを(完全に取り去ることはできませんが)和らげられます。
actix-webではBytes
構造体をactix_web::web::Bytes
として再公開しています:https://docs.rs/actix-web/3.3.2/actix_web/web/struct.Bytes.html
こうすることで、自クレート側で依存している(i.e. 期待している)クレートのバージョンとユーザー側のそれを固定でき、ユーザーはactix_web::web::Bytes
とインポートすることでもはやbytes crateの存在を意識しなくてもよくなります。便利ですね。
軽い注意点
再公開の注意ポイントとしてrustdocを生成したときに、元のdoc commentもそのまま持ってくるため再公開側のrustdocにそぐわない記述が出てくる恐れがあります(先程のactix_web::web::Bytes
が分かりやすいですね、ここでuse bytes::Bytes
を見せるのはあまりよくないです)
気にする場合は#[doc(hidden)]
をつけてあげれば隠すことが出来ます。ユースケースに合わせてご活用ください。
最後に
pub use
のパッと思いつく使い方を2つほど挙げてみました。もしかしたら巷のRustaceanはもっと色々な活用法を知っているやもしれませんね。そうであればこっそりあるいは大胆に教えて下さい。
川´_ゝ`)
Discussion