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

属性

hooqマクロの挙動は、 #[hooq(ルートメタ)] におけるルートメタおよび、途中に挿入される不活性属性 ( #[hooq::属性(...)] ) によって変更できます。

本ページでは属性による設定可能項目および設定値のデフォルトを解説します。

クイックリファレンス

名前種別説明
flavorマクロルートのメタ指定したフレーバーの設定を適用する
trait_useマクロルートのメタ指定したパス( XXX )について use XXX as _; をアイテムの前に挿入する
method不活性属性挿入/置換するメソッド(置換の場合は式)を設定する
skip_all / skip不活性属性本属性を付与した式へのフックは行わないようになる
hook_targets不活性属性?, return, 末尾式(tail_expr)それぞれについてフックを行うかを切り替え(デフォルトは3種すべてにフック)
tail_expr_idents不活性属性末尾式に来た時にフックを行うidentを指定(デフォルトでは Err )
ignore_tail_expr_idents不活性属性フック対象であった場合でもフックを行わないidentを指定(デフォルトでは Ok )
result_types不活性属性return と末尾式にフックを行う関数の返り値型を指定(デフォルトは Result )
hook_in_macros不活性属性マクロ内にもフックを行うかを指定(デフォルトは true )
binding不活性属性指定したリテラル・式で置換されるメタ変数を作成
  • マクロルートのメタ: 属性マクロ本体 #[hooq(...)]... 部分に指定する属性(メタ)
  • 不活性属性: 属性マクロ本体( #[hooq], #[hooq(...)] )ではなく、その後に来るアイテムの随所に付与される属性。 #[hooq::属性(...)] のようなフォーマットで指定

すべての属性を付与してみたソースコードは以下のような感じです。

use hooq::hooq;

mod sub {
    pub trait Trait {}
}

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq(flavor = "hook", trait_use(sub::Trait))] // Attribute macro root.
#[hooq::method(.inspect_err(|_| { let _ = "error!"; }))] // All following attributes are inert.
#[hooq::hook_targets("?", "return", "tail_expr")]
#[hooq::tail_expr_idents("Err")]
#[hooq::ignore_tail_expr_idents("Ok")]
#[hooq::result_types("Result")]
#[hooq::hook_in_macros(true)]
#[hooq::binding(xxx = "xxx_value")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    #[hooq::skip_all]
    if failable(false)? {
        failable(())?;
    }

    #[hooq::skip]
    if failable(false)? {
        // Next line is not skipped.
        failable(())?;
    }

    #[hooq::method(.inspect_err(|_| { let _ = $xxx; }))]
    failable(())?;

    Ok(())
}

上記のソースコードでマクロが展開されると次のようになります。

use hooq::hooq;
mod sub {
    pub trait Trait {}
}
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
#[allow(unused)]
use sub::Trait as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())
        .inspect_err(|_| {
            let _ = "error!";
        })?;
    if failable(false)? {
        failable(())?;
    }
    if failable(false)? {
        failable(())
            .inspect_err(|_| {
                let _ = "error!";
            })?;
    }
    failable(())
        .inspect_err(|_| {
            let _ = "xxx_value";
        })?;
    Ok(())
}

ルートメタ

hooq属性マクロ( #[hooq] )をアイテムに付与する際に、メタ部分で指定できる事項が2つ存在します。

名前付与方法
flavor#[hooq(FLAVOR_NAME)] or #[hooq(flavor = "FLAVOR_NAME")]
trait_use#[hooq(trait_use(TRAIT_PATH, ...))] or #[hooq(trait_uses(TRAIT_PATH, ...))]

解説は各項でします。

flavor

#[hooq(FLAVOR_NAME)] または #[hooq(flavor = "FLAVOR_NAME")] というフォーマットでベースとなる設定をフレーバーから設定します。

use hooq::hooq;

#[hooq(my_flavor)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    func()?;

    Ok(())
}

#[hooq(my_flavor::sub_flavor)]
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq(flavor = "my_flavor.sub_flavor")]
fn func() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    Ok(())
}

hooq.toml の内容は以下であるとします。

[my_flavor]
method = """.inspect(|_| {
    let _ = $tag;
})"""
bindings = { tag = "\"my_flavor\"" }

[my_flavor.sub_flavor]
bindings = { tag = "\"my_flavor.sub_flavor\"" }
tail_expr_idents = ["Ok"]

この時フレーバーの設定が読まれ展開後は以下のようになります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    func()
        .inspect(|_| {
            let _ = "my_flavor";
        })?;
    Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
        .inspect(|_| {
            let _ = "my_flavor.sub_flavor";
        })
}
fn func() -> Result<(), Box<dyn std::error::Error>> {
    failable(())
        .inspect(|_| {
            let _ = "my_flavor.sub_flavor";
        })?;
    Ok(())
        .inspect(|_| {
            let _ = "my_flavor.sub_flavor";
        })
}

例ではサブフレーバーも利用しています。サブフレーバーは、元となるフレーバーに上書きで設定を施したフレーバーです。

サブフレーバーは文字列の場合 .::、パスのような形で直接渡す場合 :: で区切られた名前空間で表し、ネストさせる(つまり、サブフレーバーにさらにサブフレーバーを設ける)ことができます。

フレーバーのさらなる情報、特にanyhowなどのhooqが予め用意しているフレーバーや、hooq.tomlへの書き方等についてはフレーバーのページを参照してください。

trait_use

#[hooq(trait_use(トレイトパス, ...))] というフォーマットで指定します。 trait_use ではなく trait_uses でも構いません1。この時指定されたトレイトパスに対して #[allow(unused)] use トレイトパス as _; というuse文が、 #[hooq(...)] を付与したアイテムの前に出力されます。

use hooq::hooq;

mod sub {
    pub trait Inserted {
        fn inserted(self) -> Self;
    }

    impl<T, E> Inserted for Result<T, E> {
        fn inserted(self) -> Self {
            self
        }
    }
}

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq(trait_use(sub::Inserted))]
#[hooq::method(.inserted())]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    Ok(())
}

展開後の main 関数とその上に出力されるuse文を抜粋すると次のようになっています。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
mod sub {
    pub trait Inserted {
        fn inserted(self) -> Self;
    }
    impl<T, E> Inserted for Result<T, E> {
        fn inserted(self) -> Self {
            self
        }
    }
}
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
#[allow(unused)]
use sub::Inserted as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(()).inserted()?;
    Ok(())
}

例で示したように、 trait_use はフックするメソッドを呼び出すのに必要なトレイトを一緒に出力する目的で設けています。もちろんこの属性指定を利用せず直接use文を書いてしまっても構いませんが、例えば #[cfg_attr(test, hooq(flavor = "test", trait_use(Trait)))] のようにコンパイル条件付きでhooqの利用をしたい場合などに、記述を多少見やすくする効果が期待できます。

あるいは、hooq.tomlでも trait_use は(trait_uses フィールドで)指定できるため、 システム上一応属性でも設定可能にしたとも言えます。例えばanyhowフィーチャーでは with_context メソッドを挿入しますが、この時同時に必要なトレイトである anyhow::Context をuse文で導入しています2

method

名前付与方法
method#[hooq::method(...)]

#[hooq::method(.method_name())] のようなフォーマットで、フックするメソッドを設定できる不活性属性です。

use hooq::hooq;

#[hooq]
#[hooq::method(.inspect_err(|_| { let _ = "specified @ root"; }))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    #[hooq::method(.inspect_err(|_| { let _ = "specified @ inner"; }))]
    failable(())?;

    Ok(())
}

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

上の例のように、「 #[hooq] マクロのすぐ下」・「関数内部」のどちらでも設定の変更を行うことができます。これは以降紹介する不活性属性で共通の性質です。 main 関数は以下のように展開されます。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())
        .inspect_err(|_| {
            let _ = "specified @ root";
        })?;
    failable(())
        .inspect_err(|_| {
            let _ = "specified @ inner";
        })?;
    Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq::method(...)] には 挿入モード置換モード の2つのモードがあります。

  • . (ドット)で始まる場合は挿入モードです。対象の式と ? の間(あるいは return や末尾式なら単に式の末尾)にメソッドを挿入します。
  • 上記以外は置換モードになり、与えられた式によって対象の式を置換します。対象の式は $expr メタ変数で得られるので、例えば fn func<T, E>(_r: Result<T, E>) {} などに対して #[hooq::method(func($expr))] のように書くことで関数で対象式をラップするといった記述が可能です。

その他、フックするメソッド内では $line$source といった メタ変数 を利用することが可能です。

ユーザーが特に何も指定しない場合、defaultフレーバーの設定値である以下のメソッドが挿入されます。

.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}");
})

モードやメタ変数に関する詳細はメソッドメタ変数のページを参照してください。

フックの付与をスキップする

名前付与方法
skip_all#[hooq::skip_all]
skip#[hooq::skip]

skip_all

hooqは(デフォルトの設定では)かなり貪欲にメソッドを ? 等にフックしてきます。「この ? にはフックしたくない!」といった要望は自然に起きると思います。そのような場合はフック付与を行いたくない式・文に #[hooq::skip_all] を付けてください。

use hooq::hooq;

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    #[hooq::skip_all]
    let f = || -> Option<()> {
        optional(())?; // If the hook is applied, an compile error occurs.

        Some(())
    };

    let _ = failable(f())?;

    Ok(())
}

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

fn optional<T>(val: T) -> Option<T> {
    Some(val)
}

付与した式とその内部にはフックされなくなります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(()).inspect_err(|_| {})?;
    let f = || -> Option<()> {
        optional(())?;
        Some(())
    };
    let _ = failable(f()).inspect_err(|_| {})?;
    Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn optional<T>(val: T) -> Option<T> {
    Some(val)
}

skip

#[hooq::skip] は滅多に利用することはないであろう不活性属性になりますが、一応用意した #[hooq::skip_all] の亜種になります。

skip_allでは付与対象全体でフックがスキップされましたが、skipでは 付与した(親)スコープでのみ フックのスキップが起きます。

skipは、末尾式がネストしてしまっており、そのままロギングをフックするとログが見辛くなってしまう場合等に有用です。

use hooq::hooq;

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn func1() -> Result<(), String> {
    match failable(failable(()))? {
        Ok(()) => Ok(()),
        Err(s) => Err(s),
    }
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn func2() -> Result<(), String> {
    #[hooq::skip]
    match failable(failable(()))? {
        Ok(()) => Ok(()),
        Err(s) => Err(s),
    } // Not hooked here.
}

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

fn main() {
    let _ = func1();
    let _ = func2();
}

#[hooq::skip] がついている func2 の方ではmatch式の外につく inspect_err がなくなっていることがわかります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn func1() -> Result<(), String> {
    match failable(failable(())).inspect_err(|_| {})? {
        Ok(()) => Ok(()),
        Err(s) => Err(s).inspect_err(|_| {}),
    }
        .inspect_err(|_| {})
}
fn func2() -> Result<(), String> {
    match failable(failable(()))? {
        Ok(()) => Ok(()),
        Err(s) => Err(s).inspect_err(|_| {}),
    }
}
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn main() {
    let _ = func1();
    let _ = func2();
}

フック対象判定を制御する

以下は各式に対して、フックするかしないかをカスタマイズするための不活性属性群になります。

名前付与方法... の部分が取りうる値
hook_targets#[hooq::hook_targets(...)]? or return or tail_expr の中から複数
tail_expr_idents#[hooq::tail_expr_idents(...)]Err など
ignore_tail_expr_idents#[hooq::ignore_tail_expr_idents(...)]Ok など
result_types#[hooq::result_types(...)]Result など
hook_in_macros#[hooq::hook_in_macros(...)]true or false

設定値詳細は各項目にて示します。ここには、設定の適用優先順位を記します3

  • skip_all が付与されている場合はフックしない
    • skip の場合は子スコープを除いた同スコープ内についてのみフックしない
  • 対象式がマクロ呼び出し内部にあり、かつ hook_in_macrosfalse である場合はフックしない
  • ? へのフックの場合
    • hook_targets? が含まれていればフックする
  • return へのフックの場合
    • hook_targetsreturn が含まれていなければフックしない
    • 返り値の識別子が tail_expr_idents に含まれていればフックする
    • 関数の返り値型が result_types に含まれ、かつ返り値の識別子が ignore_tail_expr_idents に含まれない場合フックする
  • 末尾式へのフックの場合
    • hook_targetstail_expr が含まれていなければフックしない
    • 末尾式の識別子が tail_expr_idents に含まれていればフックする
    • 関数・クロージャが持つブロックの末尾式であり、かつその関数・クロージャの返り値型が result_types に含まれ、かつ返り値の識別子が ignore_tail_expr_idents に含まれない場合フックする

hook_targets

? 演算子(Question Operator)、 return、 末尾式( tail_expr )の3つについて、それぞれフックするかしないかを指定できます。デフォルトでは3種類すべてフックされます。

use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::hook_targets("?")]
fn target_question() -> Result<(), String> {
    failable(())?;

    if failable(false)? {
        return Err("error".into());
    }

    if failable(true)? {
        Ok(())
    } else {
        Err("error".into())
    }
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::hook_targets("return")]
fn target_return() -> Result<(), String> {
    failable(())?;

    if failable(false)? {
        return Err("error".into());
    }

    if failable(true)? {
        Ok(())
    } else {
        Err("error".into())
    }
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::hook_targets("tail_expr")]
fn target_tail_expr() -> Result<(), String> {
    failable(())?;

    if failable(false)? {
        return Err("error".into());
    }

    if failable(true)? {
        Ok(())
    } else {
        Err("error".into())
    }
}

fn main() {
    let _ = target_question();
    let _ = target_return();
    let _ = target_tail_expr();
}

展開結果は次の通りとなります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn target_question() -> Result<(), String> {
    failable(()).inspect_err(|_| {})?;
    if failable(false).inspect_err(|_| {})? {
        return Err("error".into());
    }
    if failable(true).inspect_err(|_| {})? { Ok(()) } else { Err("error".into()) }
}
fn target_return() -> Result<(), String> {
    failable(())?;
    if failable(false)? {
        return Err("error".into()).inspect_err(|_| {});
    }
    if failable(true)? { Ok(()) } else { Err("error".into()) }
}
fn target_tail_expr() -> Result<(), String> {
    failable(())?;
    if failable(false)? {
        return Err("error".into());
    }
    if failable(true)? { Ok(()) } else { Err("error".into()).inspect_err(|_| {}) }
        .inspect_err(|_| {})
}
fn main() {
    let _ = target_question();
    let _ = target_return();
    let _ = target_tail_expr();
}

今回は個別に書きましたが、 #[hooq::hook_targets("?", "return")] のように一つだけ抜く書き方等ももちろんできます。

tail_expr_idents

return の返り値あるいは末尾式が指定した識別子である際は、関数の返り値型が result_types に含まれるかに関わらずフックを行います。デフォルトは Err で、カンマ区切りで複数指定可能です。

識別子にはパス( xxx::yyy::Zzz )は認められず、単体( Zzz )である必要があります。

一致に関しては、パス中で最後の識別子(xxx::yyy::Zzz なら Zzz)で判別されます。

use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() -> Result<(), String> {
    let _: Result<(), String> = {
        let _: Result<(), String> = {
            let res = "error".to_string();
            Err(res)
        };

        failable(())
    };

    #[hooq::tail_expr_idents("Err", "failable")]
    let _: Result<(), String> = {
        let _: Result<(), String> = {
            let res = "error".to_string();
            Err(res)
        };

        failable(()) // This will be hooked because of tail_expr_idents.
    };

    Ok(())
}

tail_expr_idents のおかげで、ブロックの返り値などに Err が含まれる場合、そこにもフックが行われるようになります。 Err 以外にこのような性質を持たせたい識別子がある際は tail_expr_idents に加えることで同様の挙動になります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn main() -> Result<(), String> {
    let _: Result<(), String> = {
        let _: Result<(), String> = {
            let res = "error".to_string();
            Err(res).inspect_err(|_| {})
        };
        failable(())
    };
    let _: Result<(), String> = {
        let _: Result<(), String> = {
            let res = "error".to_string();
            Err(res).inspect_err(|_| {})
        };
        failable(()).inspect_err(|_| {})
    };
    Ok(())
}

ignore_tail_expr_idents

tail_expr_idents とは対照的に、 return の返り値あるいは末尾式が指定した識別である際は関数の返り値型が result_types に含まれている場合でも フックを行いません 。デフォルトは Ok で、カンマ区切りで複数指定可能です。

use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() {
    let f = || -> Result<(), String> { failable(()) };

    #[hooq::ignore_tail_expr_idents("failable")]
    let g = || -> Result<(), String> { failable(()) };

    #[hooq::tail_expr_idents("!failable")]
    let h = || -> Result<(), String> { failable(()) };

    f().unwrap();
    g().unwrap();
    h().unwrap();
}

上記例にあるように、 ignore_tail_expr_idents を利用せずとも、 tail_expr_idents への指定において頭に ! (Exclamation Mark) を付けることでも同様の設定を行うことが可能です。展開結果は以下の通りとなります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn main() {
    let f = || -> Result<(), String> { failable(()).inspect_err(|_| {}) };
    let g = || -> Result<(), String> { failable(()) };
    let h = || -> Result<(), String> { failable(()) };
    f().unwrap();
    g().unwrap();
    h().unwrap();
}
両方に含まれる場合の挙動について

同じ識別子が tail_expr_identsignore_tail_expr_idents の両方に含まれる場合、機構が単純なため フックされてしまいます。なるべく ! (Exclamation Mark) を利用して tail_expr_idents 経由で設定した方が確実です。

Errの例

Err はデフォルトで tail_expr_idents に含まれるので、 ignore_tail_expr_idents で指定してもフックされてしまいます。

use hooq::hooq;

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() {
    let f = || -> Result<(), String> { Err("error!".to_string()) };

    #[hooq::ignore_tail_expr_idents("Err")]
    let g = || -> Result<(), String> { Err("error!".to_string()) };

    #[hooq::tail_expr_idents("!Err")]
    let h = || -> Result<(), String> { Err("error!".to_string()) };

    f().unwrap_err();
    g().unwrap_err();
    h().unwrap_err();
}

クロージャ g ではフックしてほしくないのにフックされていることが確認できます。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() {
    let f = || -> Result<(), String> { Err("error!".to_string()).inspect_err(|_| {}) };
    let g = || -> Result<(), String> { Err("error!".to_string()).inspect_err(|_| {}) };
    let h = || -> Result<(), String> { Err("error!".to_string()) };
    f().unwrap_err();
    g().unwrap_err();
    h().unwrap_err();
}

result_types

hooqマクロを付与した関数の返り値がフック対象であるかを判別するための識別子を設定する不活性属性です。関数の返り値型の識別子が result_types で指定した識別子と一致する時、 return や末尾式でフックを行うようになります。デフォルトは Result で、カンマ区切りで複数指定可能です。

tail_expr_idents等と同様に、識別子にはパス( xxx::yyy::Zzz )は認められず、単体( Zzz )である必要があります。

一致に関しては、パス中で最後の識別子(xxx::yyy::Zzz なら Zzz)で判別されます。

Result 型以外に独自で別な型を扱う際などに有用です。

use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

type MyResult = Result<(), String>;

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn func1() -> MyResult {
    let _ = || -> Result<(), String> { failable(()) };

    failable(())
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::result_types("MyResult")]
fn func2() -> MyResult {
    // No longer hooked.
    let _ = || -> Result<(), String> { failable(()) };

    failable(())
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::result_types("Result", "MyResult")]
fn func3() -> MyResult {
    let _ = || -> Result<(), String> { failable(()) };

    let _ = || {
        // Not hooked because return type of the closure is unknown.
        failable(())
    };

    failable(())
}

fn main() {
    let _ = func1();
    let _ = func2();
    let _ = func3();
}

展開結果は次のようになります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
type MyResult = Result<(), String>;
fn func1() -> MyResult {
    let _ = || -> Result<(), String> { failable(()).inspect_err(|_| {}) };
    failable(())
}
fn func2() -> MyResult {
    let _ = || -> Result<(), String> { failable(()) };
    failable(()).inspect_err(|_| {})
}
fn func3() -> MyResult {
    let _ = || -> Result<(), String> { failable(()).inspect_err(|_| {}) };
    let _ = || { failable(()) };
    failable(()).inspect_err(|_| {})
}
fn main() {
    let _ = func1();
    let _ = func2();
    let _ = func3();
}

hook_in_macros

関数風マクロ呼び出し内に存在する対象の式に対し、フックを行うかを決定します。デフォルトは true でありマクロ呼び出し内までフックが行われます。

use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("{}", failable("hello")?);

    #[hooq::hook_in_macros(false)]
    println!("{}", failable("world")?);

    Ok(())
}

展開結果は次の通りです。 println! マクロも展開されている点に注意してください。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
    {
        ::std::io::_print(format_args!("{0}\n", failable("hello").inspect_err(|_| {})?));
    };
    {
        ::std::io::_print(format_args!("{0}\n", failable("world")?));
    };
    Ok(())
}

関数風マクロの引数部分はRustの文法に従った構文になっているとは限らず、フックを行う場合に解析に少しコストがかかるためオフにできる不活性属性を設けました。マクロの内側までフックする必要がない場合はこちらの設定を false にすることで多少コンパイル時間が短くなるかもしれません(多分)。

binding

メタ変数 において、ユーザーが自由に式を保存できる バインディング 機能があります。同じ意味を表すいくつかの書き方があります。

付与方法備考
#[hooq::binding(xxx = ...)]
#[hooq::var(xxx = ...)]
#[hooq::xxx = ...]この方法で記述する場合 xxx は他不活性属性と名前衝突不可
use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

#[hooq]
#[hooq::method(.inspect_err(|_| { let _ = $xxx; }))]
#[hooq::xxx = 10]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    #[hooq::binding(xxx = "in block")]
    {
        failable(())?;

        #[hooq::var(xxx = 42)]
        failable(())?;

        failable(())?;
    }

    failable(())?;

    Ok(())
}

展開結果は次のようになります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())
        .inspect_err(|_| {
            let _ = 10;
        })?;
    {
        failable(())
            .inspect_err(|_| {
                let _ = "in block";
            })?;
        failable(())
            .inspect_err(|_| {
                let _ = 42;
            })?;
        failable(())
            .inspect_err(|_| {
                let _ = "in block";
            })?;
    }
    failable(())
        .inspect_err(|_| {
            let _ = 10;
        })?;
    Ok(())
}

バインディング については当該ページの方も参照してください。

flavor を利用した設定の部分適用

ここまで紹介してきた不活性属性による設定は、フレーバーを用いた部分適用が可能です。部分適用は #[hooq::属性 = フレーバー名] で行います。

また、すべての設定を不活性属性で上書きしたい場合は #[hooq::flavor = フレーバー名]、存在するユーザー定義のバインディングを上書きしたい場合は #[hooq::bindings = フレーバー名] という記法が使えます。

use hooq::hooq;

fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}

type MyResult = Result<(), String>;

#[hooq]
#[hooq::method(.inspect_err(|_| {
    let _x = $xxx;
    let _y = $yyy;
}))]
#[hooq::xxx = "from root"]
#[hooq::yyy = "from root"]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())?;

    // Not hooked.
    let _ = || -> MyResult { failable(()) };

    #[hooq::method = "my_flavor"]
    // Method will be changed.
    failable(())?;

    #[hooq::result_types = "my_flavor"]
    // Hooked now.
    let _ = || -> MyResult { failable(()) };

    #[hooq::bindings = "my_flavor"]
    // Bindings will be changed.
    failable(())?;

    #[hooq::flavor = "my_flavor"]
    // All will be changed.
    failable(())?;

    Ok(())
}

hooq.tomlの内容は以下である時、

[my_flavor]
method = """.inspect_err(|_| {
    let _ = "from my_flavor";
    let _x = $xxx;
    let _y = $yyy;
})"""
result_types = ["Result", "MyResult"]
bindings = { xxx = "\"xxx from my_flavor\"", yyy = "\"yyy from my_flavor\"" }

展開結果は次のようになります。

#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
    Ok(val)
}
type MyResult = Result<(), String>;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    failable(())
        .inspect_err(|_| {
            let _x = "from root";
            let _y = "from root";
        })?;
    let _ = || -> MyResult { failable(()) };
    failable(())
        .inspect_err(|_| {
            let _ = "from my_flavor";
            let _x = "from root";
            let _y = "from root";
        })?;
    let _ = || -> MyResult {
        failable(())
            .inspect_err(|_| {
                let _x = "from root";
                let _y = "from root";
            })
    };
    failable(())
        .inspect_err(|_| {
            let _x = "xxx from my_flavor";
            let _y = "yyy from my_flavor";
        })?;
    failable(())
        .inspect_err(|_| {
            let _ = "from my_flavor";
            let _x = "xxx from my_flavor";
            let _y = "yyy from my_flavor";
        })?;
    Ok(())
}

  1. 開発中に表記ゆれが起きてしまい両方残していますが、機能的には変わりません

  2. 置換モードを用いて書き方を工夫すれば実はトレイトのuseは不要だったりしますが、hooqマクロでは人間が良く書く直感的な記法の方を挿入する形を採用しています。

  3. フローチャートで示したかったのですがmdbook-mermaidがうまく機能しなかったため普通に箇条書きで示しています。