#![feature(try_trait_v2)]
#![no_std]
use core::convert::Infallible;
use core::fmt;
use tryx_core::{ControlFlow, FromResidual, Try, TryxResidual};
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::sync::Arc;
#[cfg(feature = "std")]
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cancel<T> {
Done(T),
Cancelled,
}
impl<T> Cancel<T> {
pub fn ok(value: T) -> Self {
Self::Done(value)
}
pub fn cancelled() -> Self {
Self::Cancelled
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Cancel<U> {
match self {
Self::Done(value) => Cancel::Done(f(value)),
Self::Cancelled => Cancel::Cancelled,
}
}
pub fn and_then<U>(self, f: impl FnOnce(T) -> Cancel<U>) -> Cancel<U> {
match self {
Self::Done(value) => f(value),
Self::Cancelled => Cancel::Cancelled,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cancellation;
impl TryxResidual for Cancellation {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CancelError;
impl fmt::Display for CancelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("operation cancelled")
}
}
#[cfg(feature = "std")]
impl std::error::Error for CancelError {}
#[cfg(feature = "std")]
#[derive(Debug, Clone)]
pub struct CancelToken {
cancelled: Arc<AtomicBool>,
}
#[cfg(feature = "std")]
#[derive(Debug, Clone)]
pub struct CancelHandle {
cancelled: Arc<AtomicBool>,
}
#[cfg(feature = "std")]
impl CancelToken {
pub fn new() -> (Self, CancelHandle) {
let cancelled = Arc::new(AtomicBool::new(false));
(
Self {
cancelled: Arc::clone(&cancelled),
},
CancelHandle { cancelled },
)
}
pub fn check(&self) -> Cancel<()> {
if self.cancelled.load(Ordering::Relaxed) {
Cancel::Cancelled
} else {
Cancel::Done(())
}
}
}
#[cfg(feature = "std")]
impl CancelHandle {
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Relaxed);
}
}
impl<T> Try for Cancel<T> {
type Output = T;
type Residual = Cancellation;
fn from_output(output: Self::Output) -> Self {
Self::ok(output)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Self::Done(value) => ControlFlow::Continue(value),
Self::Cancelled => ControlFlow::Break(Cancellation),
}
}
}
impl<T> FromResidual<Cancellation> for Cancel<T> {
fn from_residual(_: Cancellation) -> Self {
Self::Cancelled
}
}
impl<T> FromResidual<Result<Infallible, CancelError>> for Cancel<T> {
fn from_residual(_: Result<Infallible, CancelError>) -> Self {
Self::Cancelled
}
}
impl<T, E> FromResidual<Cancellation> for Result<T, E>
where
E: From<CancelError>,
{
fn from_residual(_: Cancellation) -> Self {
Err(CancelError.into())
}
}
#[cfg(feature = "cancel-reason")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CancelWith<T, R> {
Done(T),
Cancelled(R),
}
#[cfg(feature = "cancel-reason")]
impl<T, R> CancelWith<T, R> {
pub fn ok(value: T) -> Self {
Self::Done(value)
}
pub fn cancelled(reason: R) -> Self {
Self::Cancelled(reason)
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> CancelWith<U, R> {
match self {
Self::Done(value) => CancelWith::Done(f(value)),
Self::Cancelled(reason) => CancelWith::Cancelled(reason),
}
}
pub fn and_then<U>(self, f: impl FnOnce(T) -> CancelWith<U, R>) -> CancelWith<U, R> {
match self {
Self::Done(value) => f(value),
Self::Cancelled(reason) => CancelWith::Cancelled(reason),
}
}
}
#[cfg(feature = "cancel-reason")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CancellationReason<R>(pub R);
#[cfg(feature = "cancel-reason")]
impl<R> TryxResidual for CancellationReason<R> {}
#[cfg(feature = "cancel-reason")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CancelWithError<R>(pub R);
#[cfg(feature = "cancel-reason")]
impl<R> CancelWithError<R> {
pub fn reason(&self) -> &R {
&self.0
}
pub fn into_reason(self) -> R {
self.0
}
}
#[cfg(feature = "cancel-reason")]
impl<R: fmt::Display> fmt::Display for CancelWithError<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "operation cancelled: {}", self.0)
}
}
#[cfg(all(feature = "std", feature = "cancel-reason"))]
impl<R> std::error::Error for CancelWithError<R> where R: fmt::Debug + fmt::Display {}
#[cfg(feature = "cancel-reason")]
impl<T, R> Try for CancelWith<T, R> {
type Output = T;
type Residual = CancellationReason<R>;
fn from_output(output: Self::Output) -> Self {
Self::ok(output)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
match self {
Self::Done(value) => ControlFlow::Continue(value),
Self::Cancelled(reason) => ControlFlow::Break(CancellationReason(reason)),
}
}
}
#[cfg(feature = "cancel-reason")]
impl<T, R> FromResidual<CancellationReason<R>> for CancelWith<T, R> {
fn from_residual(residual: CancellationReason<R>) -> Self {
Self::Cancelled(residual.0)
}
}
#[cfg(feature = "cancel-reason")]
impl<T, R> FromResidual<Result<Infallible, CancelWithError<R>>> for CancelWith<T, R> {
fn from_residual(residual: Result<Infallible, CancelWithError<R>>) -> Self {
match residual {
Err(error) => Self::Cancelled(error.into_reason()),
}
}
}
#[cfg(feature = "cancel-reason")]
impl<T, R, E> FromResidual<CancellationReason<R>> for Result<T, E>
where
E: From<CancelWithError<R>>,
{
fn from_residual(residual: CancellationReason<R>) -> Self {
Err(CancelWithError(residual.0).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Eq)]
struct AppError;
impl From<CancelError> for AppError {
fn from(_: CancelError) -> Self {
Self
}
}
fn maybe_cancel(cancel: bool, value: u8) -> Cancel<u8> {
if cancel {
Cancel::Cancelled
} else {
Cancel::ok(value)
}
}
fn cancel_returning_success() -> Cancel<u8> {
let a = maybe_cancel(false, 2)?;
let b = maybe_cancel(false, 3)?;
Cancel::Done(a + b)
}
fn cancel_returning_short_circuit() -> Cancel<u8> {
let _ = maybe_cancel(true, 2)?;
Cancel::Done(9)
}
fn result_returning_short_circuit() -> Result<u8, AppError> {
let _ = maybe_cancel(true, 2)?;
Ok(9)
}
#[test]
fn question_mark_continues_inside_cancel() {
assert_eq!(cancel_returning_success(), Cancel::Done(5));
}
#[test]
fn question_mark_short_circuits_inside_cancel() {
assert_eq!(cancel_returning_short_circuit(), Cancel::Cancelled);
}
#[test]
fn question_mark_converts_cancel_into_result_error() {
assert_eq!(result_returning_short_circuit(), Err(AppError));
}
#[cfg(feature = "cancel-reason")]
mod reason {
use super::*;
#[derive(Debug, PartialEq, Eq)]
struct ReasonError(&'static str);
impl From<CancelWithError<&'static str>> for ReasonError {
fn from(error: CancelWithError<&'static str>) -> Self {
Self(error.into_reason())
}
}
fn maybe_cancel(cancel: bool) -> CancelWith<u8, &'static str> {
if cancel {
CancelWith::cancelled("stopped")
} else {
CancelWith::ok(4)
}
}
fn cancel_with_returning_success() -> CancelWith<u8, &'static str> {
let value = maybe_cancel(false)?;
CancelWith::ok(value + 1)
}
fn cancel_with_returning_short_circuit() -> CancelWith<u8, &'static str> {
let _ = maybe_cancel(true)?;
CancelWith::ok(9)
}
fn result_returning_reason() -> Result<u8, ReasonError> {
let _ = maybe_cancel(true)?;
Ok(9)
}
#[test]
fn question_mark_continues_inside_cancel_with() {
assert_eq!(cancel_with_returning_success(), CancelWith::Done(5));
}
#[test]
fn question_mark_short_circuits_inside_cancel_with() {
assert_eq!(
cancel_with_returning_short_circuit(),
CancelWith::Cancelled("stopped"),
);
}
#[test]
fn question_mark_converts_cancel_with_into_result_error() {
assert_eq!(result_returning_reason(), Err(ReasonError("stopped")));
}
}
}