Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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();
}

  1. 手続きマクロにはあまり高度なリフレクション機能がないので、型推論で Result 型かどうか判別したりはできません。