#![warn(clippy::pedantic, clippy::nursery, rust_2018_idioms)]
#[cfg(test)]
use mock_instant::Instant;
use std::cell::Cell;
use std::mem::{self, MaybeUninit};
use std::time::Duration;
#[cfg(not(test))]
use std::time::Instant;
#[derive(Debug, Clone, Copy)]
enum ValueState {
NoValue,
NotExpired(Instant),
Expired,
}
impl ValueState {
#[inline]
const fn is_not_expired(&self) -> bool {
matches!(self, Self::NotExpired(_))
}
#[inline]
const fn is_expired(&self) -> bool {
matches!(self, Self::Expired)
}
#[inline]
const fn is_no_value(&self) -> bool {
matches!(self, Self::NoValue)
}
#[inline]
const fn exists(&self) -> bool {
!self.is_no_value()
}
fn new_not_expired() -> Self {
Self::NotExpired(Instant::now())
}
}
#[derive(Debug)]
#[must_use]
pub struct EphemeralOption<T> {
state: Cell<ValueState>,
inner: MaybeUninit<T>,
max_time: Duration,
}
impl<T> Drop for EphemeralOption<T> {
fn drop(&mut self) {
if self.state.get().exists() {
unsafe { self.inner.assume_init_drop() }
}
}
}
impl<T> Clone for EphemeralOption<T>
where
T: Clone,
{
fn clone(&self) -> Self {
let new_inner = if self.state.get().exists() {
let val = unsafe { self.inner.assume_init_ref() };
MaybeUninit::new(val.clone())
} else {
MaybeUninit::uninit()
};
Self {
state: self.state.clone(),
inner: new_inner,
max_time: self.max_time,
}
}
}
impl<T> EphemeralOption<T> {
fn check_time(&self) {
if let ValueState::NotExpired(start_time) = self.state.get() {
let cur_time = Instant::now();
if cur_time.duration_since(start_time) > self.max_time {
self.state.set(ValueState::Expired);
}
}
}
unsafe fn extract_value(&mut self) -> T {
self.state.set(ValueState::NoValue);
let val = mem::replace(&mut self.inner, MaybeUninit::uninit());
unsafe { val.assume_init() }
}
fn insert_value(&mut self, val: T) -> &mut T {
self.state.set(ValueState::new_not_expired());
self.inner.write(val)
}
}
impl<T> EphemeralOption<T> {
pub fn new(val: T, max_time: Duration) -> Self {
Self {
state: Cell::new(ValueState::new_not_expired()),
inner: MaybeUninit::new(val),
max_time,
}
}
pub const fn new_empty(max_time: Duration) -> Self {
Self {
state: Cell::new(ValueState::NoValue),
inner: MaybeUninit::uninit(),
max_time,
}
}
pub fn get(&self) -> Option<&T> {
self.check_time();
if self.state.get().is_not_expired() {
return unsafe { Some(self.inner.assume_init_ref()) };
}
None
}
pub const unsafe fn get_unchecked(&self) -> &T {
self.inner.assume_init_ref()
}
pub fn get_expired(&self) -> Option<&T> {
if self.state.get().exists() {
return unsafe { Some(self.inner.assume_init_ref()) };
}
None
}
pub fn get_mut(&mut self) -> Option<&mut T> {
self.check_time();
if self.state.get().is_not_expired() {
return unsafe { Some(self.inner.assume_init_mut()) };
}
None
}
pub unsafe fn get_mut_unchecked(&mut self) -> &mut T {
self.inner.assume_init_mut()
}
pub fn get_mut_expired(&mut self) -> Option<&mut T> {
if self.state.get().exists() {
return unsafe { Some(self.inner.assume_init_mut()) };
}
None
}
pub fn insert(&mut self, val: T) -> &mut T {
if self.state.get().exists() {
unsafe { self.inner.assume_init_drop() }
}
self.insert_value(val)
}
pub fn get_or_insert(&mut self, val: T) -> &mut T {
self.check_time();
let state = self.state.get();
if state.is_not_expired() {
return unsafe { self.inner.assume_init_mut() };
}
if state.is_expired() {
unsafe { self.inner.assume_init_drop() };
}
self.insert_value(val)
}
pub fn take(&mut self) -> Option<T> {
self.check_time();
match self.state.get() {
ValueState::NoValue => None,
ValueState::Expired => {
self.state.set(ValueState::NoValue);
unsafe { self.inner.assume_init_drop() };
None
}
ValueState::NotExpired(_) => {
let val = unsafe { self.extract_value() };
Some(val)
}
}
}
pub fn replace(&mut self, val: T) -> Option<T> {
self.check_time();
let state = self.state.get();
if state.is_not_expired() {
let old_val = mem::replace(&mut self.inner, MaybeUninit::new(val));
self.state.set(ValueState::new_not_expired());
let old_val = unsafe { old_val.assume_init() };
return Some(old_val);
}
if state.is_expired() {
unsafe { self.inner.assume_init_drop() };
}
self.insert_value(val);
None
}
pub fn reset_timer(&self) {
if self.state.get().exists() {
self.state.set(ValueState::new_not_expired());
}
}
pub fn into_option(mut self) -> Option<T> {
self.check_time();
if self.state.get().is_not_expired() {
let val = unsafe { self.extract_value() };
return Some(val);
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use mock_instant::MockClock;
#[test]
fn test_empty_get() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new_empty(Duration::from_secs(1));
assert_eq!(opt.get(), None);
assert_eq!(opt.get_mut(), None);
assert_eq!(opt.get_expired(), None);
assert_eq!(opt.get_mut_expired(), None);
MockClock::advance(Duration::from_millis(2001));
assert_eq!(opt.get(), None);
assert_eq!(opt.get_mut(), None);
assert_eq!(opt.get_expired(), None);
assert_eq!(opt.get_mut_expired(), None);
}
#[test]
fn test_expired_get() {
let mut opt: EphemeralOption<()> = EphemeralOption::new((), Duration::from_secs(1));
MockClock::advance(Duration::from_millis(1001));
assert_eq!(opt.get(), None);
assert_eq!(opt.get_mut(), None);
assert_eq!(opt.get_expired(), Some(&()));
assert_eq!(opt.get_mut_expired(), Some(&mut ()));
}
#[test]
fn test_get() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(2, Duration::from_secs(1));
assert_eq!(opt.get(), Some(&2));
assert_eq!(opt.get_mut(), Some(&mut 2));
assert_eq!(opt.get_expired(), Some(&2));
assert_eq!(opt.get_mut_expired(), Some(&mut 2));
}
#[test]
fn test_into_option_empty() {
let opt: EphemeralOption<Vec<u8>> = EphemeralOption::new_empty(Duration::from_secs(1));
assert_eq!(opt.into_option(), None);
}
#[test]
fn test_into_option_expired() {
let opt: EphemeralOption<Vec<u8>> =
EphemeralOption::new(vec![1, 2, 3], Duration::from_secs(1));
MockClock::advance(Duration::from_millis(1001));
assert_eq!(opt.into_option(), None);
}
#[test]
fn test_into_option() {
let opt: EphemeralOption<Vec<u8>> =
EphemeralOption::new(vec![1, 2, 3], Duration::from_secs(1));
assert_eq!(opt.into_option(), Some(vec![1, 2, 3]));
}
#[test]
fn test_take_empty() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new_empty(Duration::from_secs(1));
assert_eq!(opt.take(), None);
assert_eq!(opt.get(), None);
}
#[test]
fn test_take_expired() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(2, Duration::from_secs(1));
MockClock::advance(Duration::from_millis(1001));
assert_eq!(opt.take(), None);
assert_eq!(opt.get(), None);
}
#[test]
fn test_take() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(2, Duration::from_secs(1));
assert_eq!(opt.take(), Some(2));
assert_eq!(opt.get(), None);
}
#[test]
fn test_goi_empty() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new_empty(Duration::from_secs(1));
assert_eq!(opt.get_or_insert(2), &mut 2);
assert_eq!(opt.get(), Some(&2));
}
#[test]
fn test_goi_expired() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(1, Duration::from_secs(1));
MockClock::advance(Duration::from_millis(1001));
assert_eq!(opt.get_or_insert(2), &mut 2);
assert_eq!(opt.get(), Some(&2));
}
#[test]
fn test_goi() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(1, Duration::from_secs(1));
assert_eq!(opt.get_or_insert(2), &mut 1);
assert_eq!(opt.get(), Some(&1));
}
#[test]
fn test_replace_empty() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new_empty(Duration::from_secs(1));
assert_eq!(opt.replace(2), None);
assert_eq!(opt.get(), Some(&2));
}
#[test]
fn test_replace_expired() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(2, Duration::from_secs(1));
MockClock::advance(Duration::from_millis(1001));
assert_eq!(opt.replace(1), None);
assert_eq!(opt.get(), Some(&1));
}
#[test]
fn test_replace() {
let mut opt: EphemeralOption<u8> = EphemeralOption::new(2, Duration::from_secs(1));
assert_eq!(opt.replace(1), Some(2));
assert_eq!(opt.get(), Some(&1));
}
}