#[hooq] を付与しエラー発生行を取得
はじめに で出した次の例を引き続き使って、hooqマクロが一体ソースコードにどのような細工をしたのかを解説し、hooqの使い方を紹介していきます。
use std::error::Error;
use hooq::hooq;
#[hooq]
fn load_host_and_port() -> Result<String, Box<dyn Error>> {
// Loading APP_HOST
let host = std::env::var("APP_HOST")?;
// Loading APP_PORT
let port = std::env::var("APP_PORT")?;
// Convert to u16
let port: u16 = port.parse()?;
Ok(format!("{host}:{port}"))
}
#[hooq]
fn main() -> Result<(), Box<dyn Error>> {
let host_and_port = load_host_and_port()?;
println!("Server is running on: {}", host_and_port);
// snip
Ok(())
}
#[test]
fn test_load_host_and_port() {
unsafe {
std::env::set_var("APP_HOST", "localhost");
std::env::set_var("APP_PORT", "8080");
}
let host_and_port = load_host_and_port().unwrap();
assert_eq!(host_and_port, "localhost:8080");
}
#[hooq] 属性マクロを関数に付与すると、次のhooqマクロデフォルトのメソッド inspect_err が各 ? の手前、 return の返り値や関数末尾の後ろ(ただし関数シグネチャの返り値型が Result などフック対象の型の場合)に挿入(フック)されるようになります。
.inspect_err(|e| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
::std::eprintln!("[{path}:{line}:{col}] {e:?}\n{expr}");
})
$path のようなものはhooq固有のメタ変数です。詳細は メタ変数 にて触れていますが、デフォルトメソッドに登場するものについては以下の通りです。
| メタ変数 | リテラル種別 | 説明 |
|---|---|---|
$path | 文字列 | クレートルート等からのファイル相対パス |
$line | 整数 | メソッドがフックされた行 |
$col | 整数 | メソッドがフックされた列 |
$source | 対象式のトークン列 | フックされる対象の式(ログ表示用) |
#[hooq] が施された load_host_and_port 関数は次のように展開されます。 eprintln! 等まで展開されてしまうため一致はしませんが cargo expand で確かめると似たような出力が得られるでしょう。
---
source: mdbook-source-code/index/tests/test.rs
expression: "format!(\"{stdout}\")"
---
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use std::error::Error;
use hooq::hooq;
fn load_host_and_port() -> Result<String, Box<dyn Error>> {
let host = std::env::var("APP_HOST")
.inspect_err(|e| {
let path = "mdbook-source-code/index/src/main.rs";
let line = 8usize;
let col = 41usize;
let expr = " 8> std::env::var(\"APP_HOST\")?\n |";
::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
})?;
let port = std::env::var("APP_PORT")
.inspect_err(|e| {
let path = "mdbook-source-code/index/src/main.rs";
let line = 11usize;
let col = 41usize;
let expr = " 11> std::env::var(\"APP_PORT\")?\n |";
::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
})?;
let port: u16 = port
.parse()
.inspect_err(|e| {
let path = "mdbook-source-code/index/src/main.rs";
let line = 14usize;
let col = 33usize;
let expr = " 14> port.parse()?\n |";
::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
})?;
Ok(format!("{0}:{1}", host, port))
}
fn main() -> Result<(), Box<dyn Error>> {
let host_and_port = load_host_and_port()
.inspect_err(|e| {
let path = "mdbook-source-code/index/src/main.rs";
let line = 21usize;
let col = 45usize;
let expr = " 21> load_host_and_port()?\n |";
::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
})?;
{
::std::io::_print(format_args!("Server is running on: {0}\n", host_and_port));
};
Ok(())
}
line!() マクロは非推奨!
行数取得について、「line!() マクロを使えばよいのでは?わざわざ $line メタ変数を用意されても認知負荷が上がる」という声をいただきました。
しかしこちらは意図的な設計で、まさしく line!() マクロがうまく機能しないことがメタ変数導入のきっかけとなっています。
実際に利用していただくとよくわかりますが、 line!()マクロはフックが行われる行を指しません。 #[hooq] が存在する行 (あるいは #[hooq::method(...)] の行)を出力してしまいます。これは欲しい情報ではないでしょう。ゆえにフックが行われた行を正確に取得するために $line メタ変数を設けています。
詳細はこちらの記事に書いています: (WIP)
#[hooq] だけでも上記のように展開されるおかげで、 はじめに に掲載した次の実行結果が得られ、ソースコードのどの行でエラーが発生したかすぐにわかるようになります。
$ APP_PORT=10 cargo run -q
[mdbook-source-code/index/src/main.rs:8:41] NotPresent
8> std::env::var("APP_HOST")?
|
[mdbook-source-code/index/src/main.rs:21:45] NotPresent
21> load_host_and_port()?
|
Error: NotPresent
ところで、デフォルトでフックされるメソッドも十分素敵ですが、カスタムしたいですよね…?メソッドのカスタマイズは 次のレッスン から扱います。