[Rust] anyhow::Context を use したいが名前が被ってしまうときの解決策 -> impl-only-use
anyhow::Context は便利
2021年2月現在、Rust でエラーを扱う上でのデファクトスタンダードは anyhow (と thiserror)です。これらの使い方などは以下の記事によくまとめられているので、必要に応じてご参照ください。
anyhow は anyhow::Result
や anyhow::Error
のような便利な型に加えて、 anyhow::Context
というトレイト も提供しています。このトレイトは、Option
から anyhow::Result
へと変換したい場合に特に役に立ちます。例えば、以下のコードを見てみましょう:
use anyhow::Context;
fn index_of(values: &[i32], target: i32) -> anyhow::Result<usize> {
values
.iter()
.position(|&v| v == target)
.with_context(|| format!("{} is not found", target))
}
この関数 index_of
は、引数で &[i32]
と i32
を受け取って、目的の値がスライスの何番目に位置しているのかを求めます。目的の値が存在する場合には Ok
、存在しなかった場合には Err
で返すような実装にしたいとします。
「目的の値を見つける」部分は、上記のようにイテレータに生えている position
というメソッドを使うのが便利です。ただし、このメソッドは Option<usize>
を返してくるので、これをどうにかして anyhow::Result
に変換する必要があります。
ここで使えるのが anyhow::Context
トレイトです。このトレイトは任意の Option
型に対して実装されているので、use anyhow::Context
をインポートするだけで使えるようになります。上記の例の中に登場している with_context
というメソッドは anyhow::Context
が提供しているもので、これによって Option
を anyhow::Result
に変換することができます。
Context って名前、汎用的すぎない?
さて、このように anyhow::Context
は脇役ポジションではあるものの割と使いどころがある便利トレイトなのですが、この記事ではトレイト自体の紹介をしたいわけではありません。この記事で解決したいのは、
Context
って名前が被ったときにはどうすればいいの?
という問題です。Context
という名前はそこかしこに出てきます。例えば、Rust で書かれている TypeScript リンターである deno_lint では、各リントルールが以下のようなトレイトで表現されています:
pub trait LintRule {
// ルールのインスタンスを作成
fn new() -> Box<Self> where Self: Sized;
// リントを実行する
fn lint_program(&self, context: &mut Context, program: &Program);
// ルールごとに定まっているコードを返す
fn code(&self) -> &'static str;
// ルールの種別を返す
fn tags(&self) -> &'static [&'static str] {
&[]
}
// ルールのドキュメント文言を返す
fn docs(&self) -> &'static str {
""
}
}
リントの実際の処理内容は lint_program
メソッドに書かれるのですが、このメソッドの引数に Context
というのがあります。これはリントを実行する際に役立つデータやメソッドがまとめられているもので、例えばリント対象のソースコードの変数スコープを解析したデータが格納されていたり、リントエラーを発行したい場合に context.add_diagnostic(..)
というようにメソッドを呼び出したり、といった具合です。
Context
が名前被りしてしまったときの(素朴な)回避策
では、この lint_program
の実装で anyhow::Context
を使いたくなくなったらどうすればよいでしょうか? Context
という型名はすでに使われてしまっているので、use anyhow::Context
をすることはできません。
とりあえず、Rust の use
は as XXX
というように as
で別名をつけることができるので、それを使えば回避することができます。
use anyhow::Context as AnyhowContext;
これでとりあえずは大丈夫ですが、冷静になって考えてみると、我々が anyhow::Context
をインポートしたいのは、「anyhow::Context
が提供しているメソッドを Option
型に対して利用したいから」であって、「anyhow::Context
トレイトを自前の型に実装するため」ではありません。つまり、上記のように use anyhow::Context as AnyhowContext;
というふうにしたとして、わざわざ名前を付けた AnyhowContext
が自分たちのコードの中で登場することはないのです。なんかもったいない感じがします。
Context
が名前被りしてしまったときの華麗な回避策
そこで Rust では華麗な方法が用意されています。以下のように書くことができます!
use anyhow::Context as _;
これは "impl-only-use" とか "underscore imports" と呼ばれているもので、「インポートはするけど、名前を付ける必要はないです」ということを表明できます。これで anyhow::Context
をスコープに持ってきつつ、名前空間は汚さない、ということが両立できるのです。
"impl-only-use" の RFC は以下です(RFC 2166)。
"underscore imports" の説明は The Rust Reference にあります(RFC の要約のような感じです):
というわけで、「トレイトに生えているメソッドを使うためにトレイトを use
する必要があるけど、名前空間を汚したくない」というケースでは use SomeTrait as _;
とすれば良い、ということの紹介でした。
Discussion