mod以下の関数に一括適用
実はhooqマクロは関数だけではなく処理構文を含むアイテムに付与が可能です。
ここでいう“アイテム“とは、Rustの手続きマクロ(あるいは文法)用語で、関数・mod・implブロック・traitブロック…など、直接ファイル直下に置ける文法要素( Things that can appear directly inside of a module or scope. )です。ただし処理構文を含まないアイテムについては無関係です。詳細はsynクレートの Item を見てください。
ともかく、( mod xxx; という形ではない、中括弧を持った)modが定義されている時に、modに #[hooq] マクロを付けるとその内側の関数については #[hooq] を付けなくてもフックが行われるようになります。
この再帰的性質はmodに限りません。関数の中に関数やクロージャがネストしている場合、内側の関数やクロージャに対しても #[hooq] は設定に従い貪欲にメソッドをフックします。(コンパイルエラーにならないうちは、その方がデバッグがはかどりますよね?コンパイルエラーになる場合は #[hooq::skip_all] を付けてください。)
というわけで、良い機会なのでhooqの性質がわかるパズルを用意してみました。コメントに(英語で)理由を付記しましたが、関数やクロージャの中の値がどういう場合にフックされるか考えながら読んでみてください。
use hooq::hooq;
#[hooq] // attribute macro root exist only here
#[hooq::method(.inspect_err(|_| {}))]
mod hoge {
pub fn bar() -> i32 {
// NOT hook target because this function does not return Result
42
}
pub fn failable<T>(val: T) -> Result<T, ()> {
// The return type of this function is Result
// but "Ok" is specified as ignore_tail_expr_idents by default
// so NOT hook target
Ok(val)
}
#[hooq::tail_expr_idents("Ok", "Err")]
pub fn _failable_2<T>(val: T) -> Result<T, ()> {
if failable(false).unwrap() {
// ↑ above is NOT hook target
// ↓ below: hook target because the return type of this function is Result
return failable(val);
}
// hook target because "Ok" is now specified as tail_expr_idents
Ok(val)
}
pub fn fuga() -> Result<(), ()> {
// hook target because of `?`
failable(())?;
let _ = || {
// hook target because of `?`
failable(())?;
// hook target because of `?`
if failable(false)? {
// hook target because "Err" is specified as tail_expr_idents by default
return Err(());
}
// NOT hook target because "Ok" is specified as ignore_tail_expr_idents by default
Ok(())
};
let _ = || {
// NOT hook target because "Ok" is specified as ignore_tail_expr_idents by default
Result::<(), ()>::Ok(())
};
let _ = {
let _ = bar();
// hook target because "Err" is specified as tail_expr_idents by default
// even if this is the tail expression of the block not a closure
Result::<(), ()>::Err(())
};
let _ = || -> Result<(), ()> {
// hook target because hooq can know this closure returns Result
failable(())
};
let _ = || {
// NOT hook target because hooq cannot know this closure returns Result
failable(())
};
// NOT hook target because "Ok" is specified as ignore_tail_expr_idents by default
Ok(())
}
}
fn main() {
let _ = hoge::bar();
let _ = hoge::failable(123);
let _ = hoge::fuga();
}
展開結果は次のようになります。かなり自然に、しかしマクロにしては1貪欲にフックを仕掛けられていると思います。
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
mod hoge {
pub fn bar() -> i32 {
42
}
pub fn failable<T>(val: T) -> Result<T, ()> {
Ok(val)
}
pub fn _failable_2<T>(val: T) -> Result<T, ()> {
if failable(false).unwrap() {
return failable(val).inspect_err(|_| {});
}
Ok(val).inspect_err(|_| {})
}
pub fn fuga() -> Result<(), ()> {
failable(()).inspect_err(|_| {})?;
let _ = || {
failable(()).inspect_err(|_| {})?;
if failable(false).inspect_err(|_| {})? {
return Err(()).inspect_err(|_| {});
}
Ok(())
};
let _ = || { Result::<(), ()>::Ok(()) };
let _ = {
let _ = bar();
Result::<(), ()>::Err(()).inspect_err(|_| {})
};
let _ = || -> Result<(), ()> { failable(()).inspect_err(|_| {}) };
let _ = || { failable(()) };
Ok(())
}
}
fn main() {
let _ = hoge::bar();
let _ = hoge::failable(123);
let _ = hoge::fuga();
}
-
手続きマクロにはあまり高度なリフレクション機能がないので、型推論で
Result型かどうか判別したりはできません。 ↩