Macro hook_fn

Source
macro_rules! hook_fn {
    (
        type Bounds = impl $hook_bound:lifetime $(+ $hook_bounds:lifetime)* ;
        $($rest:tt)*
    ) => { ... };
    ($($rest:tt)*) => { ... };
}
Expand description

Write hook fn without any proc-macro.

A hook fn is a fn which returns impl UpdateHookUninitialized. (Thus the output type is also UpdateHook + IntoHook).

Tips: cargo fmt supports formatting code inside hook_fn!(...); and hook_fn![...];, but not hook_fn!{...}.

§Usage

hook_fn!(
    fn use_constant() -> i32 {
        1
    }
);

/// `use_constant()` actually returns:
UpdateHookUninitialized![i32]
/// The above macro expands to
impl UpdateHookUninitialized<
    Uninitialized = impl HookPollNextUpdate + HookUnmount + Default,
    Hook = impl Hook + for<'hook> HookValue<'hook, Value = i32>
>

§Use other hooks with h!(...)

Usually, you would want to use other hooks in hook_fn.

use hooks::prelude::*;

hook_fn!(
    fn use_auto_increment(max: i32) -> i32 {
        let state = h!(use_shared_signal(0));
        let v = state.get();

        h!(use_effect_with::<i32, _>(|old_dependency| {
            if *old_dependency != Some(v) {
                *old_dependency = Some(v);
                let state = state.clone();
                Some(move |v: &_| state.set(*v + 1))
            } else {
                None
            }
        }));

        v
    }
);

§Capture lifetimes

Lifetimes in fn generics and type Bounds = impl 'a are captured.

For example, in

hook_fn!(
    fn use_hook<'a>(_: &'a str) {}
);

the return type of use_hook auto captures lifetime 'a with trait bound Captures<&'a ()> (even the argument &'a str is not used in the fn body).

If the lifetime is elided and used, it must be specified with type Bounds = impl '_;.

hook_fn!(
    fn use_hook(v: &str) {
        println!("{}", v);
    }
);
hook_fn!(
    type Bounds = impl '_;
    fn use_hook(v: &str) {
        println!("{}", v);
    }
);

If the lifetime comes from outer Self and is used, it must be specified with type Bounds = impl 'a;.

struct Data<'a>(&'a str);

impl<'a> Data<'a> {
    hook_fn!(
        fn use_data(self) {
            println!("{}", self.0)
        }
   );
}
struct Data<'a>(&'a str);

impl<'a> Data<'a> {
    hook_fn!(
        type Bounds = impl 'a;
        fn use_data(self) {
            println!("{}", self.0)
        }
   );
}

In Rust 2024 and later editions, all lifetimes should be auto captured so you don’t need to specify them explicitly.

See lifetime_capture_rules_2024

§Use an explicit identifier for hook

h!(use_hook()) actually expands to use_hook().h(hook_id), where hook_id is of type Pin<&mut UpdateHookUninitialized::Uninitialized>.

You can use an explicit hook_id for debugging.

hook_fn!(
    fn use_my_state() -> i32 {
        println!("{state_hook:?}"); // inspect this hook before it is used

        let mut the_state_hook = state_hook;
        let state_hook = std::pin::Pin::as_mut(&mut the_state_hook);

        let (state, updater) = h![state_hook = use_shared_set(1)];

        let state = *state;

        println!("{the_state_hook:?}"); // inspect this hook after it is used

        state
    }
);

§Limitations

Only top level token trees are parsed.

The following example doesn’t work because two macros are in a token tree (), which stops them to be parsed.

hook_fn!(
    fn use_shared_signal_2() -> (i32, i32) {
        (
            h![use_shared_signal(0)].get(),
            h![use_shared_signal(1)].get(),
        )
    }
);

You have to make sure h! to be top level token trees when using hook_fn!.

hook_fn!(
    fn use_shared_signal_2() -> (i32, i32) {
        let a = h![use_shared_signal(0)].get();
        let b = h![use_shared_signal(1)].get();
        (a, b)
    }
);

#[hook] parses any top level expressions (expressions that are not wrapped in {}). Thus, the following example works.

#[hook]
fn use_shared_signal_2() -> (i32, i32) {
    (
        use_shared_signal(0).get(),
        use_shared_signal(1).get(),
    )
}

Supports at most 10 hooks.

hook_fn!(
    fn use_10_hooks() {
        h!(use_hook(0)); h!(use_hook(1)); h!(use_hook(2)); h!(use_hook(3)); h!(use_hook(4));
        h!(use_hook(5)); h!(use_hook(6)); h!(use_hook(7)); h!(use_hook(8)); h!(use_hook(9));
    }
);
hook_fn!(
    fn use_11_hooks() {
        h!(use_hook(0)); h!(use_hook(1)); h!(use_hook(2)); h!(use_hook(3)); h!(use_hook(4));
        h!(use_hook(5)); h!(use_hook(6)); h!(use_hook(7)); h!(use_hook(8)); h!(use_hook(9));
        h!(use_hook(10));
    }
);

This limitation also applies to #[hook] because traits are only implemented for HookTuple<(0, 1, ...)> with at most 10 elements.

#[hook]
fn use_11_hooks() {
    use_hook(0); use_hook(1); use_hook(2); use_hook(3); use_hook(4);
    use_hook(5); use_hook(6); use_hook(7); use_hook(8); use_hook(9);
    use_hook(10);
}

Output type must not be opaque type (impl Trait)

hook_fn!(
    fn use_future() -> impl Future<Output = ()> {
        async {}
    }
);

#[hook] allows to do so.

#[hook]
fn use_future() -> impl Future<Output = ()> {
    async {}
}