🛸

Rustのpub useの使い所

2021/06/02に公開

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::kindskindsって汎用的すぎて意味を持たないよね、なら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_likelibc::unix::linux_like::androidで同じ名前が使われていた場合にそれをwarningとして発見することもできます。便利ですね。
例えば、src/unix/linux_like/linux/mod.rsにあるuseconds_tsrc/unix/linux_like/mod.rsに再定義すると、
a warning on
といったような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はもっと色々な活用法を知っているやもしれませんね。そうであればこっそりあるいは大胆に教えて下さい。

川´_ゝ`)

GitHubで編集を提案

Discussion