use crate::{Choice, CtAssign, CtAssignSlice, CtEq, CtEqSlice, CtSelect};
use core::ops::{Deref, DerefMut};
#[macro_export]
macro_rules! map {
($opt:expr, $mapper:path) => {{ $crate::CtOption::new($mapper($opt.to_inner_unchecked()), $opt.is_some()) }};
}
#[macro_export]
macro_rules! unwrap_or {
($opt:expr, $default:expr, $select:path) => {
$select(&$default, $opt.as_inner_unchecked(), $opt.is_some())
};
}
#[derive(Clone, Copy, Debug)]
pub struct CtOption<T> {
value: T,
is_some: Choice,
}
impl<T> CtOption<T> {
#[inline]
#[must_use]
pub const fn new(value: T, is_some: Choice) -> CtOption<T> {
Self { value, is_some }
}
#[inline]
#[must_use]
pub const fn some(value: T) -> CtOption<T> {
Self::new(value, Choice::TRUE)
}
#[inline]
#[must_use]
pub fn none() -> CtOption<T>
where
T: Default,
{
Self::new(Default::default(), Choice::FALSE)
}
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> CtOption<&mut T> {
CtOption {
value: &mut self.value,
is_some: self.is_some,
}
}
#[inline]
#[must_use]
pub const fn as_ref(&self) -> CtOption<&T> {
CtOption {
value: &self.value,
is_some: self.is_some,
}
}
#[inline]
#[must_use]
pub fn as_deref(&self) -> CtOption<&T::Target>
where
T: Deref,
{
self.as_ref().map(Deref::deref)
}
#[inline]
#[must_use]
pub fn as_deref_mut(&mut self) -> CtOption<&mut T::Target>
where
T: DerefMut,
{
self.as_mut().map(DerefMut::deref_mut)
}
#[inline]
#[must_use]
#[track_caller]
pub fn expect(self, msg: &str) -> T {
assert!(self.is_some().to_bool(), "{}", msg);
self.value
}
#[inline]
#[must_use]
#[track_caller]
pub const fn expect_copied(self, msg: &str) -> T
where
T: Copy,
{
*self.expect_ref(msg)
}
#[inline]
#[must_use]
#[track_caller]
pub const fn expect_ref(&self, msg: &str) -> &T {
assert!(self.is_some.to_bool_vartime(), "{}", msg);
self.as_inner_unchecked()
}
pub fn insert(&mut self, value: T) -> &mut T {
self.value = value;
self.is_some = Choice::TRUE;
&mut self.value
}
pub fn insert_if(&mut self, value: &T, condition: Choice)
where
T: CtAssign,
{
self.value.ct_assign(value, condition);
self.is_some.ct_assign(&Choice::TRUE, condition);
}
#[inline]
pub fn into_option(self) -> Option<T> {
if self.is_some.to_bool() {
Some(self.value)
} else {
None
}
}
#[inline]
pub const fn into_option_copied(self) -> Option<T>
where
T: Copy,
{
if self.is_some.to_bool_vartime() {
Some(self.value)
} else {
None
}
}
#[inline]
#[must_use]
pub const fn is_some(&self) -> Choice {
self.is_some
}
#[inline]
#[must_use]
pub const fn is_none(&self) -> Choice {
self.is_some.not()
}
#[inline]
#[must_use]
pub fn and<U>(self, mut optb: CtOption<U>) -> CtOption<U> {
optb.is_some &= self.is_some;
optb
}
#[inline]
#[must_use]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> CtOption<U>,
{
let mut ret = f(self.value);
ret.is_some &= self.is_some;
ret
}
#[inline]
#[must_use]
pub const fn as_inner_unchecked(&self) -> &T {
&self.value
}
#[inline]
#[must_use]
pub fn filter<P>(mut self, predicate: P) -> Self
where
P: FnOnce(&T) -> Choice,
{
self.is_some &= predicate(&self.value);
self
}
#[inline]
#[must_use]
pub const fn filter_by(mut self, is_some: Choice) -> Self {
self.is_some = self.is_some.and(is_some);
self
}
#[inline]
#[must_use]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> U,
{
CtOption::new(f(self.value), self.is_some)
}
#[inline]
#[must_use = "if you don't need the returned value, use `if let` instead"]
pub fn map_or<U, F>(self, default: U, f: F) -> U
where
U: CtSelect,
F: FnOnce(T) -> U,
{
self.map(f).unwrap_or(default)
}
#[inline]
#[must_use]
pub fn map_or_default<U, F>(self, f: F) -> U
where
U: CtSelect + Default,
F: FnOnce(T) -> U,
{
self.map_or(U::default(), f)
}
#[inline]
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
self.into_option().ok_or(err)
}
#[inline]
#[allow(clippy::missing_errors_doc)]
pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
where
F: FnOnce() -> E,
{
self.ok_or(err())
}
#[inline]
#[must_use]
pub fn or(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
{
CtOption {
value: self.value.ct_select(&optb.value, self.is_none()),
is_some: self.is_some | optb.is_some,
}
}
#[inline]
#[must_use]
pub const fn to_inner_unchecked(self) -> T
where
T: Copy,
{
self.value
}
#[inline]
#[must_use]
#[track_caller]
pub fn unwrap(self) -> T {
assert!(
self.is_some.to_bool(),
"called `CtOption::unwrap()` on a value with `is_some` set to `Choice::FALSE`"
);
self.value
}
#[inline]
#[must_use]
pub fn unwrap_or(self, default: T) -> T
where
T: CtSelect,
{
default.ct_select(&self.value, self.is_some)
}
#[inline]
#[must_use]
pub fn unwrap_or_default(self) -> T
where
T: CtSelect + Default,
{
self.unwrap_or(T::default())
}
#[inline]
#[must_use]
pub fn xor(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
{
CtOption {
value: self.value.ct_select(&optb.value, self.is_none()),
is_some: self.is_some ^ optb.is_some,
}
}
pub fn zip<U>(self, other: CtOption<U>) -> CtOption<(T, U)> {
CtOption {
value: (self.value, other.value),
is_some: self.is_some & other.is_some,
}
}
pub fn zip_with<U, F, R>(self, other: CtOption<U>, f: F) -> CtOption<R>
where
F: FnOnce(T, U) -> R,
{
self.zip(other).map(|(a, b)| f(a, b))
}
}
impl<T> CtOption<&T> {
#[must_use = "`self` will be dropped if the result is not used"]
pub const fn copied(self) -> CtOption<T>
where
T: Copy,
{
CtOption {
value: *self.value,
is_some: self.is_some,
}
}
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> CtOption<T>
where
T: Clone,
{
CtOption {
value: self.value.clone(),
is_some: self.is_some,
}
}
}
impl<T> CtOption<&mut T> {
#[must_use = "`self` will be dropped if the result is not used"]
pub const fn copied(self) -> CtOption<T>
where
T: Copy,
{
CtOption {
value: *self.value,
is_some: self.is_some,
}
}
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> CtOption<T>
where
T: Clone,
{
CtOption {
value: self.value.clone(),
is_some: self.is_some,
}
}
}
impl<T: CtAssign> CtAssign for CtOption<T> {
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.value.ct_assign(&other.value, choice);
self.is_some.ct_assign(&other.is_some, choice);
}
}
impl<T: CtAssign> CtAssignSlice for CtOption<T> {}
impl<T: CtEq> CtEq for CtOption<T> {
#[inline]
fn ct_eq(&self, other: &CtOption<T>) -> Choice {
(self.is_some & other.is_some & self.value.ct_eq(&other.value))
| (self.is_none() & other.is_none())
}
}
impl<T: CtEq> CtEqSlice for CtOption<T> {}
impl<T: CtSelect> CtSelect for CtOption<T> {
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
Self {
value: self.value.ct_select(&other.value, choice),
is_some: self.is_some.ct_select(&other.is_some, choice),
}
}
}
impl<T: Default> Default for CtOption<T> {
fn default() -> Self {
Self::none()
}
}
impl<T> From<CtOption<T>> for Option<T> {
fn from(src: CtOption<T>) -> Option<T> {
src.into_option()
}
}
#[cfg(feature = "subtle")]
impl<T> From<subtle::CtOption<T>> for CtOption<T>
where
T: subtle::ConditionallySelectable + Default,
{
#[inline]
fn from(src: subtle::CtOption<T>) -> CtOption<T> {
let is_some = src.is_some();
CtOption {
value: src.unwrap_or(Default::default()),
is_some: is_some.into(),
}
}
}
#[cfg(feature = "subtle")]
impl<T> From<CtOption<T>> for subtle::CtOption<T> {
#[inline]
fn from(src: CtOption<T>) -> subtle::CtOption<T> {
subtle::CtOption::new(src.value, src.is_some.into())
}
}
#[cfg(feature = "subtle")]
impl<T> subtle::ConditionallySelectable for CtOption<T>
where
T: Copy, Self: CtSelect,
{
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
CtSelect::ct_select(a, b, choice.into())
}
}
#[cfg(feature = "subtle")]
impl<T> subtle::ConstantTimeEq for CtOption<T>
where
Self: CtEq,
{
#[inline]
fn ct_eq(&self, other: &Self) -> subtle::Choice {
CtEq::ct_eq(self, other).into()
}
}
#[cfg(test)]
mod tests {
use crate::{Choice, CtEq, CtOption, CtSelect};
const VALUE: u8 = 42;
const SOME: CtOption<u8> = CtOption::new(VALUE, Choice::TRUE);
const NONE: CtOption<u8> = CtOption::new(VALUE, Choice::FALSE);
const OTHER: CtOption<u8> = CtOption::new(VALUE + 1, Choice::TRUE);
#[derive(Debug, Eq, PartialEq)]
struct Error;
#[test]
fn map_macro() {
assert!(map!(NONE, u16::from).is_none().to_bool());
assert_eq!(map!(SOME, u16::from).unwrap(), u16::from(VALUE));
}
#[test]
fn unwrap_or_macro() {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn select_vartime(a: &u8, b: &u8, choice: Choice) -> u8 {
if choice.to_bool_vartime() { *b } else { *a }
}
assert_eq!(
unwrap_or!(NONE, OTHER.unwrap(), select_vartime),
OTHER.unwrap()
);
assert_eq!(unwrap_or!(SOME, OTHER.unwrap(), select_vartime), VALUE);
}
#[test]
fn ct_eq() {
assert!(NONE.ct_eq(&NONE).to_bool());
assert!(NONE.ct_ne(&SOME).to_bool());
assert!(SOME.ct_ne(&NONE).to_bool());
assert!(SOME.ct_eq(&SOME).to_bool());
assert!(SOME.ct_ne(&OTHER).to_bool());
}
#[test]
fn ct_select() {
assert!(NONE.ct_select(&SOME, Choice::FALSE).is_none().to_bool());
assert!(NONE.ct_select(&SOME, Choice::TRUE).ct_eq(&SOME).to_bool());
assert!(SOME.ct_select(&NONE, Choice::FALSE).ct_eq(&SOME).to_bool());
assert!(SOME.ct_select(&NONE, Choice::TRUE).is_none().to_bool());
}
#[test]
fn default() {
assert!(NONE.ct_eq(&CtOption::default()).to_bool());
}
#[test]
fn expect_some() {
assert_eq!(SOME.expect("should succeed"), VALUE);
}
#[test]
#[should_panic]
fn expect_none() {
let _ = NONE.expect("should panic");
}
#[test]
fn into_option() {
assert_eq!(SOME.into_option(), Some(VALUE));
assert_eq!(NONE.into_option(), None);
}
#[test]
fn into_option_copied() {
assert_eq!(SOME.into_option_copied(), Some(VALUE));
assert_eq!(NONE.into_option_copied(), None);
}
#[test]
fn is_some() {
assert!(SOME.is_some().to_bool());
assert!(!NONE.is_some().to_bool());
}
#[test]
fn is_none() {
assert!(!SOME.is_none().to_bool());
assert!(NONE.is_none().to_bool());
}
#[test]
fn and() {
assert!(SOME.and(NONE).is_none().to_bool());
assert_eq!(SOME.and(OTHER).unwrap(), OTHER.unwrap());
}
#[test]
fn and_then() {
assert!(NONE.and_then(|_| NONE).is_none().to_bool());
assert!(NONE.and_then(|_| SOME).is_none().to_bool());
let ret = SOME.and_then(|value| {
assert_eq!(VALUE, value);
OTHER
});
assert!(ret.ct_eq(&OTHER).to_bool());
}
#[test]
fn filter() {
assert!(NONE.filter(|_| Choice::TRUE).ct_eq(&NONE).to_bool());
assert!(NONE.filter(|_| Choice::FALSE).ct_eq(&NONE).to_bool());
assert!(SOME.filter(|_| Choice::FALSE).ct_eq(&NONE).to_bool());
let ret = SOME.filter(|&value| {
assert_eq!(VALUE, value);
Choice::TRUE
});
assert_eq!(ret.unwrap(), VALUE);
}
#[test]
fn filter_by() {
assert!(NONE.filter_by(Choice::FALSE).is_none().to_bool());
assert!(NONE.filter_by(Choice::TRUE).is_none().to_bool());
assert!(SOME.filter_by(Choice::FALSE).ct_eq(&NONE).to_bool());
assert_eq!(SOME.filter_by(Choice::TRUE).unwrap(), VALUE);
}
#[test]
fn insert() {
let mut example = NONE;
assert!(example.is_none().to_bool());
let ret = example.insert(42);
assert_eq!(ret, &42);
assert!(example.is_some().to_bool());
}
#[test]
fn insert_if() {
let mut example = NONE;
assert!(example.is_none().to_bool());
example.insert_if(&42, Choice::FALSE);
assert!(example.is_none().to_bool());
example.insert_if(&42, Choice::TRUE);
assert_eq!(example.unwrap(), 42);
}
#[test]
fn map() {
assert!(NONE.map(|value| value + 1).ct_eq(&NONE).to_bool());
assert!(SOME.map(|value| value + 1).ct_eq(&OTHER).to_bool());
}
#[test]
fn map_or() {
let example = 52;
assert_eq!(NONE.map_or(example, |value| value + 1), example);
assert_eq!(SOME.map_or(example, |value| value + 1), VALUE + 1);
}
#[test]
fn map_or_default() {
assert_eq!(NONE.map_or_default(|value| value + 1), Default::default());
assert_eq!(SOME.map_or_default(|value| value + 1), VALUE + 1);
}
#[test]
fn ok_or() {
assert_eq!(NONE.ok_or(Error), Err(Error));
assert_eq!(SOME.ok_or(Error), Ok(VALUE));
}
#[test]
fn ok_or_else() {
assert_eq!(NONE.ok_or_else(|| Error), Err(Error));
assert_eq!(SOME.ok_or_else(|| Error), Ok(VALUE));
}
#[test]
fn or() {
assert!(NONE.or(NONE).is_none().to_bool());
assert!(SOME.or(NONE).ct_eq(&SOME).to_bool());
assert!(NONE.or(SOME).ct_eq(&SOME).to_bool());
assert!(SOME.or(OTHER).ct_eq(&SOME).to_bool());
}
#[test]
fn some() {
assert!(CtOption::some(VALUE).ct_eq(&SOME).to_bool());
}
#[test]
fn unwrap_some() {
assert_eq!(SOME.unwrap(), VALUE);
}
#[test]
#[should_panic]
fn unwrap_none() {
let _ = NONE.unwrap();
}
#[test]
fn unwrap_or() {
let example = 52;
assert_eq!(NONE.unwrap_or(example), example);
assert_eq!(SOME.unwrap_or(example), VALUE);
}
#[test]
fn unwrap_or_default() {
assert_eq!(NONE.unwrap_or_default(), Default::default());
assert_eq!(SOME.unwrap_or_default(), VALUE);
}
#[test]
fn xor() {
assert!(NONE.xor(NONE).is_none().to_bool());
assert!(SOME.xor(NONE).ct_eq(&SOME).to_bool());
assert!(NONE.xor(SOME).ct_eq(&SOME).to_bool());
assert!(SOME.xor(OTHER).is_none().to_bool());
}
#[test]
fn zip() {
assert!(NONE.zip(NONE).is_none().to_bool());
assert!(NONE.zip(SOME).is_none().to_bool());
assert!(SOME.zip(NONE).is_none().to_bool());
assert_eq!(SOME.zip(OTHER).unwrap(), (SOME.unwrap(), OTHER.unwrap()));
}
#[test]
fn zip_with() {
assert!(NONE.zip_with(NONE, |a, b| a + b).is_none().to_bool());
assert!(NONE.zip_with(SOME, |a, b| a + b).is_none().to_bool());
assert!(SOME.zip_with(NONE, |a, b| a + b).is_none().to_bool());
assert_eq!(
SOME.zip_with(OTHER, |a, b| a + b).unwrap(),
SOME.unwrap() + OTHER.unwrap()
);
}
}