#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(clippy::all)]
#[cfg(feature = "alloc")]
extern crate alloc;
mod reason;
pub use reason::StopReason;
pub trait Stop: Send + Sync {
fn check(&self) -> Result<(), StopReason>;
#[inline]
fn should_stop(&self) -> bool {
self.check().is_err()
}
#[inline]
fn may_stop(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Unstoppable;
#[deprecated(since = "0.3.0", note = "Use `Unstoppable` instead for clarity")]
pub type Never = Unstoppable;
impl Stop for Unstoppable {
#[inline(always)]
fn check(&self) -> Result<(), StopReason> {
Ok(())
}
#[inline(always)]
fn should_stop(&self) -> bool {
false
}
#[inline(always)]
fn may_stop(&self) -> bool {
false
}
}
impl<T: Stop + ?Sized> Stop for &T {
#[inline]
fn check(&self) -> Result<(), StopReason> {
(**self).check()
}
#[inline]
fn should_stop(&self) -> bool {
(**self).should_stop()
}
#[inline]
fn may_stop(&self) -> bool {
(**self).may_stop()
}
}
impl<T: Stop + ?Sized> Stop for &mut T {
#[inline]
fn check(&self) -> Result<(), StopReason> {
(**self).check()
}
#[inline]
fn should_stop(&self) -> bool {
(**self).should_stop()
}
#[inline]
fn may_stop(&self) -> bool {
(**self).may_stop()
}
}
#[cfg(feature = "alloc")]
impl<T: Stop + ?Sized> Stop for alloc::boxed::Box<T> {
#[inline]
fn check(&self) -> Result<(), StopReason> {
(**self).check()
}
#[inline]
fn should_stop(&self) -> bool {
(**self).should_stop()
}
#[inline]
fn may_stop(&self) -> bool {
(**self).may_stop()
}
}
#[cfg(feature = "alloc")]
impl<T: Stop + ?Sized> Stop for alloc::sync::Arc<T> {
#[inline]
fn check(&self) -> Result<(), StopReason> {
(**self).check()
}
#[inline]
fn should_stop(&self) -> bool {
(**self).should_stop()
}
#[inline]
fn may_stop(&self) -> bool {
(**self).may_stop()
}
}
impl<T: Stop> Stop for Option<T> {
#[inline]
fn check(&self) -> Result<(), StopReason> {
match self {
Some(s) => s.check(),
None => Ok(()),
}
}
#[inline]
fn should_stop(&self) -> bool {
match self {
Some(s) => s.should_stop(),
None => false,
}
}
#[inline]
fn may_stop(&self) -> bool {
match self {
Some(s) => s.may_stop(),
None => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unstoppable_does_not_stop() {
assert!(!Unstoppable.should_stop());
assert!(Unstoppable.check().is_ok());
}
#[test]
fn unstoppable_is_copy() {
let a = Unstoppable;
let b = a; let _ = a; let _ = b;
}
#[test]
fn unstoppable_is_default() {
let _: Unstoppable = Default::default();
}
#[test]
fn reference_impl_works() {
let unstoppable = Unstoppable;
let reference: &dyn Stop = &unstoppable;
assert!(!reference.should_stop());
}
#[test]
#[allow(deprecated)]
fn never_alias_works() {
let stop: Never = Unstoppable;
assert!(!stop.should_stop());
}
#[test]
fn stop_reason_from_impl() {
#[derive(Debug, PartialEq)]
#[allow(dead_code)]
enum TestError {
Stopped(StopReason),
Other,
}
impl From<StopReason> for TestError {
fn from(r: StopReason) -> Self {
TestError::Stopped(r)
}
}
fn might_stop(stop: impl Stop) -> Result<(), TestError> {
stop.check()?;
Ok(())
}
assert!(might_stop(Unstoppable).is_ok());
}
#[test]
fn dyn_stop_works() {
fn process(stop: &dyn Stop) -> bool {
stop.should_stop()
}
let unstoppable = Unstoppable;
assert!(!process(&unstoppable));
}
#[test]
fn unstoppable_may_not_stop() {
assert!(!Unstoppable.may_stop());
}
#[test]
fn dyn_unstoppable_may_not_stop() {
let stop: &dyn Stop = &Unstoppable;
assert!(!stop.may_stop());
}
#[test]
fn may_stop_via_reference() {
let stop = &Unstoppable;
assert!(!stop.may_stop());
}
#[test]
fn option_none_is_noop() {
let stop: Option<&dyn Stop> = None;
assert!(stop.check().is_ok());
assert!(!stop.should_stop());
assert!(!stop.may_stop());
}
#[test]
fn option_some_delegates() {
use core::sync::atomic::{AtomicBool, Ordering};
struct TestStop(AtomicBool);
impl Stop for TestStop {
fn check(&self) -> Result<(), StopReason> {
if self.0.load(Ordering::Relaxed) {
Err(StopReason::Cancelled)
} else {
Ok(())
}
}
}
let inner = TestStop(AtomicBool::new(false));
let stop: Option<&dyn Stop> = Some(&inner);
assert!(stop.check().is_ok());
assert!(!stop.should_stop());
assert!(stop.may_stop());
inner.0.store(true, Ordering::Relaxed);
assert!(stop.should_stop());
assert_eq!(stop.check(), Err(StopReason::Cancelled));
}
#[test]
fn may_stop_hot_loop_pattern() {
fn process(stop: &dyn Stop) -> Result<(), StopReason> {
let stop = stop.may_stop().then_some(stop);
for _ in 0..100 {
stop.check()?;
}
Ok(())
}
assert!(process(&Unstoppable).is_ok());
}
}