ffi_time 0.2.1

FFI assistant for Rust time types
Documentation
//! std::time::{Duration, Instant} bindings assistant.
//!
//! # Warning: No guarantee for safe compatibility. Use it at your own risk. Data without `#[repr(C)]` is very fragile on the FFI boundary.
//!
//! Using these union types provides a few benefits.
//!
//! First, memory layout optimization can be prevented. Since these types are not `#[repr(C)]`, the layout can be freely optimized on the Rust side. Using a union helps the internal `CDuration` reserve the full size of these types.
//! Second, it provides an error check mechanism for operations outside of Rust.
//!
//! Still, be aware of the risks. This does not mean it is safe, nor does it mean it cannot be broken by future Rust changes.

/// FFI-available `Duration` corresponding to `std::time::Duration`.
///
/// In `rust_time.h`, `rust::time::Duration` matches this type.
///
/// Note: This is a platform-dependent implementation. Major platforms are compatible.
#[repr(C)]
#[derive(Clone, Copy)]
pub union Duration {
    duration: std::time::Duration,
    payload: CDuration,
}
static_assertions::assert_eq_size!(Duration, std::time::Duration);
static_assertions::assert_eq_size!(Duration, CDuration);

impl std::fmt::Debug for Duration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if unsafe { self.payload }.is_valid() {
            write!(f, "{:?}", unsafe { self.duration })
        } else {
            write!(f, "Duration({:?})", unsafe { self.payload })
        }
    }
}

impl std::hash::Hash for Duration {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        unsafe { self.duration }.hash(state);
    }
}

impl PartialEq for Duration {
    fn eq(&self, other: &Self) -> bool {
        unsafe { self.payload.eq(&other.payload) }
    }
}

impl PartialOrd for Duration {
    fn partial_cmp(&self, other: &Self) -> std::option::Option<std::cmp::Ordering> {
        if unsafe { !self.payload.is_valid() || !other.payload.is_valid() } {
            return None;
        }
        unsafe { self.duration.partial_cmp(&other.duration) }
    }
}

impl Duration {
    // Create from a Rust `std::time::Duration` object.
    pub fn from_duration(duration: std::time::Duration) -> Self {
        let mut uninit = std::mem::MaybeUninit::<Duration>::uninit();
        unsafe {
            uninit.as_mut_ptr().write(Self { duration });
            uninit.assume_init()
        }
    }
    pub fn into_duration(self) -> std::option::Option<std::time::Duration> {
        if unsafe { self.payload }.is_valid() {
            Some(unsafe { self.duration })
        } else {
            None
        }
    }
    pub fn as_duration(&self) -> std::option::Option<&std::time::Duration> {
        if unsafe { self.payload }.is_valid() {
            Some(unsafe { &self.duration })
        } else {
            None
        }
    }
    pub fn as_mut_duration(&mut self) -> std::option::Option<&mut std::time::Duration> {
        if unsafe { self.payload }.is_valid() {
            Some(unsafe { &mut self.duration })
        } else {
            None
        }
    }
    /// # Safety: The payload representation must match a valid `std::time::Duration`.
    pub unsafe fn into_duration_unchecked(self) -> std::time::Duration {
        self.duration
    }
    /// # Safety: The payload representation must match a valid `std::time::Duration`.
    pub unsafe fn as_duration_unchecked(&self) -> &std::time::Duration {
        &self.duration
    }

    /// # Safety: The payload representation must match a valid `std::time::Duration`.
    pub unsafe fn as_mut_duration_unchecked(&mut self) -> &mut std::time::Duration {
        &mut self.duration
    }

    #[cfg(test)]
    pub fn as_c_ptr(&self) -> *const libc::c_void {
        unsafe { &raw const self.payload as *const _ }
    }
    #[cfg(test)]
    pub fn as_c_mut_ptr(&mut self) -> *mut libc::c_void {
        unsafe { &raw mut self.payload as *mut _ }
    }
}

impl From<std::time::Duration> for Duration {
    fn from(duration: std::time::Duration) -> Self {
        Self::from_duration(duration)
    }
}

impl From<CDuration> for Duration {
    fn from(payload: CDuration) -> Self {
        Self { payload }
    }
}

/// FFI-available `Instant` corresponding to `std::time::Instant`.
///
/// In `rust_time.h`, `rust::time::Instant` matches this type.
///
/// Note: This is a platform-dependent implementation. Major platforms are compatible.
#[repr(C)]
#[derive(Clone, Copy)]
pub union Instant {
    instant: std::time::Instant,
    payload: CDuration,
}
static_assertions::assert_eq_size!(Instant, std::time::Instant);
static_assertions::assert_eq_size!(Instant, CDuration);

impl std::fmt::Debug for Instant {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if unsafe { self.payload }.is_valid() {
            write!(f, "{:?}", unsafe { self.instant })
        } else {
            write!(f, "Instant({:?})", unsafe { self.payload })
        }
    }
}

impl std::hash::Hash for Instant {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        unsafe { self.instant }.hash(state);
    }
}

impl PartialEq for Instant {
    fn eq(&self, other: &Self) -> bool {
        unsafe { self.payload.eq(&other.payload) }
    }
}

impl PartialOrd for Instant {
    fn partial_cmp(&self, other: &Self) -> std::option::Option<std::cmp::Ordering> {
        if unsafe { !self.payload.is_valid() || !other.payload.is_valid() } {
            return None;
        }
        unsafe { self.instant.partial_cmp(&other.instant) }
    }
}

impl Instant {
    // Create from a Rust `std::time::Instant` object.
    pub fn from_instant(instant: std::time::Instant) -> Self {
        let mut uninit = std::mem::MaybeUninit::<Self>::uninit();
        unsafe {
            uninit.as_mut_ptr().write(Self { instant });
            uninit.assume_init()
        }
    }
    pub fn into_instant(self) -> std::option::Option<std::time::Instant> {
        if unsafe { self.payload }.is_valid() {
            Some(unsafe { self.instant })
        } else {
            None
        }
    }
    pub fn as_instant(&self) -> std::option::Option<&std::time::Instant> {
        if unsafe { self.payload }.is_valid() {
            Some(unsafe { &self.instant })
        } else {
            None
        }
    }
    pub fn as_mut_instant(&mut self) -> std::option::Option<&mut std::time::Instant> {
        if unsafe { self.payload }.is_valid() {
            Some(unsafe { &mut self.instant })
        } else {
            None
        }
    }
    /// # Safety: The payload representation must match a valid `std::time::Instant`.
    pub unsafe fn into_instant_unchecked(self) -> std::time::Instant {
        self.instant
    }
    /// # Safety: The payload representation must match a valid `std::time::Instant`.
    pub unsafe fn as_instant_unchecked(&self) -> &std::time::Instant {
        &self.instant
    }
    /// # Safety: The payload representation must match a valid `std::time::Instant`.
    pub unsafe fn as_mut_instant_unchecked(&mut self) -> &mut std::time::Instant {
        &mut self.instant
    }

    #[cfg(test)]
    pub fn as_c_ptr(&self) -> *const libc::c_void {
        unsafe { &raw const self.payload as *const _ }
    }
    #[cfg(test)]
    pub fn as_c_mut_ptr(&mut self) -> *mut libc::c_void {
        unsafe { &raw mut self.payload as *mut _ }
    }
}

impl From<std::time::Instant> for Instant {
    fn from(instant: std::time::Instant) -> Self {
        Self::from_instant(instant)
    }
}

impl From<CDuration> for Instant {
    fn from(payload: CDuration) -> Self {
        Self { payload }
    }
}

/// The internal representation of `std::time::Duration` and `std::time::Instant`, but C compatible.
///
/// # Warning: No guarantee for safe compatibility.
#[derive(Clone, Copy)]
#[repr(C)]
struct CDuration {
    pub secs: u64,
    pub nanos: u32,
    /// This field must never be accessed. Accessing this field might be UB by creation path.
    _padding: u32,
}

impl CDuration {
    const NONE_NANOS: u32 = (-1i32) as u32;
    fn is_valid(&self) -> bool {
        self.nanos < 1_000_000_000
    }
    fn is_none(&self) -> bool {
        self.nanos == Self::NONE_NANOS
    }
}

impl std::fmt::Debug for CDuration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{{ secs: {}, nanos: {} }}", self.secs, self.nanos)
    }
}

impl PartialEq for CDuration {
    fn eq(&self, other: &Self) -> bool {
        self.secs == other.secs && self.nanos == other.nanos
    }
}

/// `std::option::Option`-like wrapper of Duration and Instant.
///
/// This is only created from FFI functions.
/// When checked operation is called in C++ side and directly passed to Rust,
/// it must be typed as `Option<Duration>` or `Option<Instant>` to check none value.
#[derive(Debug)]
#[repr(C)]
pub struct Option<T>(CDuration, std::marker::PhantomData<T>);

impl<T> Option<T> {
    pub fn is_none(&self) -> bool {
        self.0.is_none()
    }
    pub fn is_some(&self) -> bool {
        !self.is_none()
    }
}

#[allow(private_bounds)]
impl<T: From<CDuration>> Option<T> {
    pub fn unwrap(self) -> T {
        T::from(self.0)
    }
    #[track_caller]
    pub fn expect(self, msg: &str) -> T {
        if self.0.is_none() {
            panic!("{}", msg);
        }
        T::from(self.0)
    }
}

impl<T: PartialEq> PartialEq for Option<T> {
    fn eq(&self, other: &Self) -> bool {
        let l_is_none = self.is_none();
        let r_is_none = other.is_none();
        if l_is_none || r_is_none {
            l_is_none == r_is_none
        } else {
            self.0.eq(&other.0)
        }
    }
}

impl Option<Duration> {
    pub fn into_duration(self) -> std::option::Option<std::time::Duration> {
        if self.0.is_valid() {
            Some(Duration { payload: self.0 }.into_duration().unwrap())
        } else {
            None
        }
    }
}

impl Option<Instant> {
    pub fn into_instant(self) -> std::option::Option<std::time::Instant> {
        if self.0.is_valid() {
            Some(Instant { payload: self.0 }.into_instant().unwrap())
        } else {
            None
        }
    }
}