フレーバーでプリセットを作成/利用する
前のレッスン の最後に、 Option 型のメソッド ok_or_else が Result 型にはないためにフックをスキップするという話をしました。
そもそも Result と Option の両方にあるメソッドをフックすることはできないでしょうか…? anyhowクレートが提供するメソッド Context::with_context がまさにうってつけです。 Option 型に対して .with_context(...) を呼ぶと、 None の時は anyhow::Result の Err バリアントへと変換を行ってくれます。
use anyhow::{Context, Result};
use hooq::hooq;
#[hooq]
#[hooq::method(.with_context(|| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
format!("[{path}:{line}:{col}]\n{expr}")
}))]
fn display_name_by_mista(val: &toml::Value) -> Result<()> {
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(anyhow::anyhow!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq]
#[hooq::method(.with_context(|| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
format!("[{path}:{line}:{col}]\n{expr}")
}))]
fn main() -> Result<()> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
エラーが発生するように実行すると、 .with_context(...) の分だけエラーがスタックされ、トレースが得られます。
Error: [mdbook-source-code/tutorial-4-with-anyhow/src/main.rs:41:39]
41> display_name_by_mista(&cargo_toml)?
|
Caused by:
0: [mdbook-source-code/tutorial-4-with-anyhow/src/main.rs:17:9]
17> return Err(anyhow::anyhow!(
18| "name `{name}` contains '4'. Guido Mista disallow this."
19| ))
|
1: name `tutorial-4-with-anyhow` contains '4'. Guido Mista disallow this.
anyhowクレートはとても良く使われるクレートであり、そして .with_context(...) はここまでで示した通りとても便利なメソッドです。
そこで、このフックは頻出であろうと考えhooqではanyhowに対し特別なプリセット… anyhow フレーバー を設けています。hooqではプリセットのことをわかりやすさのためフレーバー(flavor)と呼称しています。anyhowにとどまらずlog, eyreやtracingといったクレートに対してもフレーバーを用意しています。
今まで #[hooq] と記載していた属性マクロ呼び出しを #[hooq(フレーバー名)] とすることでフレーバーを指定できます。
use anyhow::Result;
use hooq::hooq;
#[hooq(anyhow)]
fn display_name_by_mista(val: &toml::Value) -> Result<()> {
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(anyhow::anyhow!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq(anyhow)]
fn main() -> Result<()> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
結果はフレーバーを使わないソースコードと同一です!
あらかじめ用意されているフレーバーについてはフレーバーのページを参照してください。
よく使う設定をフレーバーとして用意する
hooqクレート側で予め用意したフレーバーだけでなく、ユーザーが事前に用意したフレーバーを用いることも可能です。
フレーバーはクレートルート( CARGO_MANIFEST_DIR が示すディレクトリ)に hooq.toml というファイルを設置することで可能です。以下は hooq.toml の設定例です。
[my_flavor]
method = """.inspect_err(|_| {
eprintln!("Error @ Line {}: Col: {}\n{}", $line, $col, ::hooq::summary!($source));
})
.inspect(|_| {
println!("Success @ Line {}: Col: {}\n{}", $line, $col, ::hooq::summary!($source));
})"""
hook_targets = ["?", "return", "tail_expr"]
tail_expr_idents = ["Ok", "Err"]
result_types = ["Result"]
hook_in_macros = true
[my_flavor.ok_or_else]
method = """
.ok_or_else(|| {
format!("[Line: {}, {}]\n{}",
$line,
$nth,
::hooq::summary!($source),
)
})
.$so_far"""
詳細はフレーバーページにて解説していますが、この時の設定項目の意味は次の通りです。
| 設定項目 | 効果 |
|---|---|
method | フックするメソッドを設定 |
hook_targets | フック対象を3種から設定 |
tail_expr_idents | 末尾式 / return にてこの設定項目の識別子(ident)である時、フックを行う |
result_types | #[hooq] 対象がここに指定された型の関数である時、返り値はフック対象であるとみなされる |
my_flavor.ok_or_else というテーブルは、 my_flavor の サブフレーバー になります。設定項目を my_flavor から引き継ぎつつ、新たに設定を上書きすることが可能です。
設定可能なすべての項目と設定値による挙動(優先度等)については、属性とフレーバーのページをそれぞれ参照してください。
設定したフレーバーはソースコードから利用できるようになります。
use hooq::hooq;
#[hooq(my_flavor)]
fn display_name_by_mista(val: &toml::Value) -> Result<(), String> {
// Method can be overridden by the one in flavor!
#[hooq::method = my_flavor::ok_or_else]
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(format!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq(my_flavor)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
#[hooq::method = フレーバー名] という記法が出てきました。こちらはフレーバーの設定項目を部分適用するための機能です。その他の設定項目に関しても同様に部分適用が可能です。詳しくは属性の方を参照ください。
言い換えると、 #[hooq(フレーバー名)] で指定したフレーバーのmethodには $so_far を含めることはできず、 #[hooq(my_flavor::ok_or_else)] はフレーバーの指定方法としては正しいのですがコンパイルエラーになってしまいます。
#[hooq::method = my_flavor::ok_or_else] は上書きに当たるため .$so_far の利用が可能です。