hooks_core/traits.rs
1use std::{
2 ops::{Deref, DerefMut},
3 pin::Pin,
4 task::Poll,
5};
6
7mod sealed {
8 pub trait Sealed<'hook, This: ?Sized> {}
9 impl<'hook, T: ?Sized> Sealed<'hook, T> for &'hook T {}
10}
11
12pub trait HookValueBounds<'hook, This: ?Sized>: sealed::Sealed<'hook, This> {}
13
14impl<'hook, T: ?Sized> HookValueBounds<'hook, T> for &'hook T {}
15
16/// [The Captures trick](https://rust-lang.github.io/rfcs/3498-lifetime-capture-rules-2024.html#the-captures-trick)
17pub trait Captures<U> {}
18impl<T: ?Sized, U> Captures<U> for T {}
19
20/// A helper trait to define
21/// *lifetime generic associated types (lifetime-GAT)*
22/// for [`Hook`].
23///
24/// This trait follows the [*better GAT*] pattern.
25/// *better GAT* is a pattern which implements *lifetime-GAT* without real GAT,
26/// and it also solves some problems relating to `for<'hook> HookValue<Value<'hook> = ...>`
27/// that real GAT currently doesn't solve.
28///
29/// Please don't impl this trait manually because in the future this will be changed to GAT.
30/// Instead, use [`impl_hook![...];`](crate::impl_hook).
31///
32/// [*better GAT*]: https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats#the-better-gats
33pub trait HookValue<'hook, ImplicitBounds: HookValueBounds<'hook, Self> = &'hook Self> {
34 /// The output type of [`Hook::use_hook`].
35 ///
36 /// Please don't use this associated type directly.
37 /// Instead, use [`Value![]`](crate::Value) for future compatibility.
38 type Value;
39}
40
41/// Defines reactivity of a hook.
42///
43/// See [`poll_next_update()`](HookPollNextUpdate::poll_next_update) for details.
44pub trait HookPollNextUpdate {
45 /// The meaning of the return value is:
46 ///
47 /// - `Poll::Pending` means this hook's inner state is not updated
48 /// after the last `use_hook`.
49 /// The executor **DO NOT NEED** to call `use_hook` again
50 /// because the returned value is expected to remain the same
51 /// as the value from the last call.
52 /// The executor **CAN** still call `use_hook`
53 /// to re-get the returned value.
54 ///
55 /// - `Poll::Ready(true)` means this hook's inner state has been updated
56 /// since the last `use_hook`.
57 /// The executor **SHOULD** call `use_hook` again to get the new value.
58 /// If the executor doesn't update or use this hook, instead, it polls the hook again,
59 /// the hook may still return `Poll::Ready(true)`.
60 ///
61 /// - `Poll::Ready(false)` means this hook's inner state will never be updated.
62 /// The executor **CAN** drop this hook.
63 /// The executor **CAN** call `use_hook` to get the value or update it, and
64 /// the hook **MIGHT** become dynamic again during `use_hook` or `update_hook`, or when
65 /// some operations is done to the returned value.
66 fn poll_next_update(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<bool>;
67}
68
69/// Defines how to cleanup a hook.
70///
71/// Consider this as a re-entrant and pinned version of [`Drop`].
72/// Cleanups can be run in [`unmount`](HookUnmount::unmount).
73///
74/// After `unmount`, the hook might still be used or updated.
75pub trait HookUnmount {
76 #[inline(always)]
77 fn unmount(self: Pin<&mut Self>) {}
78}
79
80/// Defines how to use a hook (get value from the hook).
81///
82/// A hook is something that outputs values reactively.
83///
84/// ## How to impl `Hook`
85///
86/// Usually, you don't need to impl `Hook`.
87/// You can easily compose hooks with [`hook_fn!(...)`] or
88/// [`#[hook]`](https://docs.rs/hooks/latest/hooks/attr.hook.html).
89///
90/// The hook fn actually returns `impl UpdateHookUninitialized<Hook = impl Hook>`,
91/// so that this hook fn can also be composed in other `hook_fn`.
92/// For more information, see [`hook_fn!(...)`].
93///
94/// ### with `hook_fn!(...)` macro
95///
96/// ```
97/// # extern crate hooks_dev as hooks;
98/// # use hooks::{use_shared_set, hook_fn};
99/// hook_fn![
100/// pub fn use_my_hook() -> &'hook mut i32 {
101/// let (state, updater) = h![use_shared_set(1)];
102/// state
103/// }
104/// ];
105/// ```
106///
107/// ### with `#[hook]` attribute macro
108///
109/// ```
110/// # extern crate hooks_dev as hooks;
111/// # use hooks::{use_effect, hook};
112/// /// Print debug on `value` change.
113/// #[hook]
114/// fn use_debug<T: std::fmt::Debug + PartialEq + Copy>(value: T) {
115/// use_effect(|v: &_| {
116/// println!("{v:?}");
117/// }, value);
118/// }
119/// ```
120///
121/// ### implement `Hook` manually.
122///
123/// See [`impl_hook!`](crate::impl_hook).
124///
125/// ## Comparison with `LendingAsyncIterator`
126///
127/// A `Hook` is like a `LendingAsyncIterator`.
128/// They both produce items asynchronously,
129/// but they have different meanings on pending and terminating:
130///
131/// For pending:
132///
133/// - If a `LendingAsyncIterator` is pending
134/// (`poll_next` returns `Poll::Pending`),
135/// it is producing the next item.
136///
137/// - If a `Hook` is pending,
138/// (`poll_next_update` returns `Poll::Pending`),
139/// it is waiting for its inner state to update.
140/// When a `Hook` is pending, the executor can still *use* it
141/// by calling [`use_hook`](Hook::use_hook) and
142/// the returned value would remain the *same* as the last returned value.
143/// *Using* a hook is like *inspecting* it.
144/// Some hooks may do heavy work in `use_hook`.
145/// It is advised to call `use_hook` only after
146/// `poll_next_update` returns `Poll::Ready(true)`.
147///
148/// For terminating:
149///
150/// - If a `LendingAsyncIterator` is terminated
151/// (`poll_next` returns `Poll::Ready(None)`),
152/// the executor MUST NOT call `poll_next` again.
153///
154/// - There is no termination for a `Hook` until dropped.
155/// When `poll_next_update` returns `Poll::Ready(false)`,
156/// this means the hook is no longer dynamic
157/// (its inner state will no longer update).
158/// Thus, there is no need to call `use_hook` again because
159/// the returned value is expected to remain the *same*.
160/// But the executor can still call `use_hook` to re-get the value
161/// or update it with [`update_hook`](crate::UpdateHook::update_hook) or [`h`](crate::UpdateHookUninitialized::h),
162/// and this might make the hook dynamic again.
163///
164/// This behavior makes it possible to combine multiple hooks.
165/// When some hooks are no longer dynamic
166/// but other hooks depend on their returned values,
167/// the executor can still get the values
168/// from the no-longer-dynamic hooks,
169/// and pass the values to the dynamic hooks.
170///
171/// Also see [`NonLendingHook`] for a subset of hooks that doesn't lend lifetimes to values,
172/// which are like [`AsyncIterator`](std::async_iter::AsyncIterator) or [`Stream`](futures_core::Stream).
173///
174/// [`hook_fn!(...)`]: crate::hook_fn
175pub trait Hook: HookPollNextUpdate + HookUnmount + for<'hook> HookValue<'hook> {
176 fn use_hook(self: Pin<&mut Self>) -> <Self as HookValue<'_>>::Value;
177}
178
179/// `NonLendingHook` is a subset of [`Hook`].
180/// `Value` of `NonLendingHook` is not generic,
181/// thus not borrowing from the hook.
182/// In other words, `NonLendingHook` doesn't lend to its `Value`.
183///
184/// [`Hook`] is like a `LendingAsyncIterator`,
185/// `NonLendingHook` is like an [`AsyncIterator`](std::async_iter::AsyncIterator)
186/// (also known as [`Stream`](futures_core::Stream)).
187///
188/// You can run
189/// [`hook.into_values()`](crate::HookExt::into_values) and [`hook.values()`](crate::HookExt::values)
190/// to get a stream of values from a hook.
191pub trait NonLendingHook:
192 Hook + for<'hook> HookValue<'hook, Value = Self::NonGenericValue>
193{
194 type NonGenericValue;
195}
196
197impl<H, V> NonLendingHook for H
198where
199 H: Hook + for<'hook> HookValue<'hook, Value = V>,
200{
201 type NonGenericValue = V;
202}
203
204macro_rules! impl_for_deref_hook {
205 (
206 impl<$h:ident> (
207 $($ty:ty),*
208 $(,)?
209 ) {
210 $poll_next_update:item
211 $unmount:item
212 $use_hook:item
213 }
214 ) => {$(
215 impl<H: HookPollNextUpdate + Unpin + ?Sized> HookPollNextUpdate for $ty {
216 $poll_next_update
217 }
218
219 impl<H: HookUnmount + Unpin + ?Sized> HookUnmount for $ty {
220 $unmount
221 }
222
223 impl<'hook, H: Hook + Unpin + ?Sized> HookValue<'hook> for $ty {
224 type Value = <H as HookValue<'hook>>::Value;
225 }
226
227 impl<H: Hook + Unpin + ?Sized> Hook for $ty {
228 $use_hook
229 }
230 )*};
231}
232
233impl_for_deref_hook![
234 impl<H> (&mut H, Box<H>) {
235 #[inline]
236 fn poll_next_update(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<bool> {
237 H::poll_next_update(Pin::new(self.get_mut()), cx)
238 }
239
240 #[inline]
241 fn unmount(self: Pin<&mut Self>) {
242 H::unmount(Pin::new(self.get_mut()))
243 }
244
245 #[inline]
246 fn use_hook(self: Pin<&mut Self>) -> <Self as HookValue<'_>>::Value {
247 H::use_hook(Pin::new(self.get_mut()))
248 }
249 }
250];
251
252impl<P> HookPollNextUpdate for Pin<P>
253where
254 P: DerefMut,
255 <P as Deref>::Target: HookPollNextUpdate,
256{
257 #[inline]
258 fn poll_next_update(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<bool> {
259 <P::Target as HookPollNextUpdate>::poll_next_update(
260 crate::utils::pin_as_deref_mut(self),
261 cx,
262 )
263 }
264}
265
266impl<P> HookUnmount for Pin<P>
267where
268 P: DerefMut,
269 <P as Deref>::Target: HookUnmount,
270{
271 #[inline]
272 fn unmount(self: Pin<&mut Self>) {
273 <<P as Deref>::Target as HookUnmount>::unmount(crate::utils::pin_as_deref_mut(self))
274 }
275}
276
277impl<'hook, P> HookValue<'hook> for Pin<P>
278where
279 P: DerefMut,
280 <P as Deref>::Target: Hook,
281{
282 type Value = <<P as Deref>::Target as HookValue<'hook>>::Value;
283}
284
285impl<P> Hook for Pin<P>
286where
287 P: DerefMut,
288 <P as Deref>::Target: Hook,
289{
290 #[inline]
291 fn use_hook(self: Pin<&mut Self>) -> <Self as HookValue<'_>>::Value {
292 <P::Target as Hook>::use_hook(crate::utils::pin_as_deref_mut(self))
293 }
294}
295
296pub trait IntoHook {
297 type Hook: Hook;
298
299 fn into_hook(self) -> Self::Hook;
300
301 #[inline(always)]
302 fn into_hook_values(self) -> crate::Values<Self::Hook>
303 where
304 Self: Sized,
305 {
306 crate::Values::new(self.into_hook())
307 }
308}
309
310impl<H> IntoHook for H
311where
312 H: Hook,
313{
314 type Hook = H;
315
316 #[inline(always)]
317 fn into_hook(self) -> Self::Hook {
318 self
319 }
320}
321
322pub trait UpdateHook: IntoHook {
323 fn update_hook(self, hook: Pin<&mut Self::Hook>);
324}
325
326pub trait UpdateHookUninitialized: UpdateHook {
327 type Uninitialized: HookPollNextUpdate + HookUnmount + Default;
328
329 fn h(self, hook: Pin<&mut Self::Uninitialized>) -> <Self::Hook as HookValue<'_>>::Value;
330}
331
332/// Type alias for [`HookValue::Value`].
333///
334/// In the future, when [`HookValue`] changed to GAT,
335/// this type alias would still works.
336pub type Value<'hook, H> = <H as HookValue<'hook>>::Value;