use core::{any::type_name, fmt};
use super::ErasedFnOnce;
pub struct Thunk<'a, T>(ThunkKind<'a, T>);
enum ThunkKind<'a, T> {
Done(T),
Bounce(ErasedFnOnce<'a, Thunk<'a, T>>),
}
impl<'a, T> Thunk<'a, T> {
pub const fn new<F>(fn_once: F) -> Self
where
F: FnOnce() -> T + 'a,
{
Self::bounce(move || Self::value(fn_once()))
}
pub const fn value(value: T) -> Self {
Self(ThunkKind::Done(value))
}
pub const fn bounce<F>(fn_once: F) -> Self
where
F: FnOnce() -> Self + 'a,
{
Self(ThunkKind::Bounce(ErasedFnOnce::new(fn_once)))
}
#[inline(always)]
pub fn call(mut self) -> T {
loop {
match self.0 {
ThunkKind::Bounce(erased_fn_once) => self = erased_fn_once.call(),
ThunkKind::Done(value) => return value,
}
}
}
}
impl<T> fmt::Debug for Thunk<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Thunk -> {}", type_name::<T>())
}
}
#[cfg(test)]
mod tests {
use super::Thunk;
use core::mem::size_of;
#[cfg(target_pointer_width = "64")]
#[test]
fn thunk_is_32_bytes_on_64_bit_targets() {
assert_eq!(size_of::<Thunk<'static, ()>>(), 32);
assert_eq!(size_of::<Thunk<'static, bool>>(), 32);
assert_eq!(size_of::<Thunk<'static, u64>>(), 32);
}
}