off 0.1.0

Optional FFI Format.
Documentation
#![no_std]

// @todo add docs
// @todo expand api to mirror Option

use core::mem::MaybeUninit;

#[derive(Debug)]
#[repr(C)]
pub struct Optional<T> {
  val: MaybeUninit<T>,
  is_some: bool,
}

const _: () = {
  const fn check<T, const SIZE: usize>() {
    assert!(size_of::<Optional<T>>() == SIZE);
  }

  check::<u8, 2>();
  check::<u16, 4>();
  check::<u32, 8>();

  check::<i8, 2>();
  check::<i16, 4>();
  check::<i32, 8>();

  check::<f32, 8>();
};

impl<T> Optional<T> {
  pub const fn some(val: T) -> Self {
    Self {
      val: MaybeUninit::new(val),
      is_some: true,
    }
  }

  pub const fn none() -> Self {
    Self {
      val: MaybeUninit::uninit(),
      is_some: false,
    }
  }

  // @todo make const https://github.com/rust-lang/rust/issues/73255
  pub fn from_opt(v: Option<T>) -> Self {
    match v {
      Some(v) => Self::some(v),
      None => Self::none(),
    }
  }

  pub const fn to_opt(self) -> Option<T> {
    if self.is_some {
      Some(unsafe { self.val.assume_init() })
    } else {
      None
    }
  }

  pub const fn is_some(&self) -> bool {
    self.is_some
  }

  pub const fn is_none(&self) -> bool {
    !self.is_some
  }
}

impl<T> From<Option<T>> for Optional<T> {
  fn from(v: Option<T>) -> Self {
    Self::from_opt(v)
  }
}

impl<T> From<Optional<T>> for Option<T> {
  fn from(o: Optional<T>) -> Self {
    o.to_opt()
  }
}

impl<T> From<T> for Optional<T> {
  fn from(v: T) -> Self {
    Self::some(v)
  }
}

trait Sealed {}

#[expect(private_bounds)]
pub trait Raw: Sealed {
  type T;
}

macro_rules! to_packable {
  (u8, $val:expr) => {
    $val as u16
  };

  (u16, $val:expr) => {
    $val as u32
  };

  (u32, $val:expr) => {
    $val as u64
  };

  (i8, $val:expr) => {
    to_packable!(u8, $val)
  };

  (i16, $val:expr) => {
    to_packable!(u16, $val)
  };

  (i32, $val:expr) => {
    to_packable!(u32, $val)
  };

  (f32, $val:expr) => {
    $val.to_bits() as u64
  };
}

macro_rules! from_packable {
  (u8, $val:expr) => {
    $val as u8
  };

  (u16, $val:expr) => {
    $val as u16
  };

  (u32, $val:expr) => {
    $val as u32
  };

  (i8, $val:expr) => {
    $val as i8
  };

  (i16, $val:expr) => {
    $val as i16
  };

  (i32, $val:expr) => {
    $val as i32
  };

  (f32, $val:expr) => {
    f32::from_bits($val as _)
  };
}

macro_rules! impl_raw {
  ($T:tt, $Packed:ty) => {
    impl Sealed for $T {}

    impl Raw for $T {
      type T = $Packed;
    }

    impl Optional<$T> {
      pub const fn into_raw(self) -> $Packed {
        if self.is_some {
          let v = unsafe { self.val.assume_init() };
          let v = to_packable!($T, v);
          v | ((self.is_some as $Packed) << <$Packed>::BITS / 2)
        } else {
          0
        }
      }

      pub const fn from_raw(v: $Packed) -> Self {
        let is_some = v & (1 << <$Packed>::BITS / 2) != 0;
        if is_some {
          Self::some(from_packable!($T, v))
        } else {
          Self::none()
        }
      }
    }
  };
}

impl_raw!(u8, u16);
impl_raw!(u16, u32);
impl_raw!(u32, u64);

impl_raw!(i8, u16);
impl_raw!(i16, u32);
impl_raw!(i32, u64);

impl_raw!(f32, u64);

// @todo add tests