safe-arithmetic 0.0.3

Traits for safe arithmetic operations in rust
Documentation
use std::any::Any;
use std::fmt::{self, Debug, Display};

pub trait AsErr {
    fn as_err(&self) -> &(dyn std::error::Error + 'static);
}

impl AsErr for dyn std::error::Error + 'static {
    fn as_err(&self) -> &(dyn std::error::Error + 'static) {
        self
    }
}

impl AsErr for dyn std::error::Error + Send + 'static {
    fn as_err(&self) -> &(dyn std::error::Error + 'static) {
        self
    }
}

impl AsErr for dyn std::error::Error + Sync + 'static {
    fn as_err(&self) -> &(dyn std::error::Error + 'static) {
        self
    }
}

impl AsErr for dyn std::error::Error + Send + Sync + 'static {
    fn as_err(&self) -> &(dyn std::error::Error + 'static) {
        self
    }
}

impl<T> AsErr for T
where
    T: std::error::Error + 'static,
{
    fn as_err(&self) -> &(dyn std::error::Error + 'static) {
        self
    }
}

pub trait Arithmetic:
    Sized + Clone + PartialEq + AsErr + std::error::Error + Send + Sync + 'static
{
}

pub trait DynArithmetic: AsErr + std::error::Error + Send + Sync + 'static {
    fn as_any(&self) -> &dyn Any;
    fn eq(&self, other: &dyn DynArithmetic) -> bool;
    fn clone(&self) -> Box<dyn DynArithmetic + Send + Sync + 'static>;
}

impl<E> DynArithmetic for E
where
    Self: Arithmetic,
    E: Send + Sync,
{
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn eq(&self, other: &dyn DynArithmetic) -> bool {
        match other.as_any().downcast_ref::<Self>() {
            Some(other) => PartialEq::eq(self, other),
            None => false,
        }
    }

    fn clone(&self) -> Box<dyn DynArithmetic + Send + Sync + 'static> {
        Box::new(self.clone())
    }
}

impl PartialEq for dyn DynArithmetic + Send + Sync + 'static {
    fn eq(&self, other: &Self) -> bool {
        DynArithmetic::eq(self, other)
    }
}

// required fix for derived PartialEq that otherwise moves
impl PartialEq<&Self> for Box<dyn DynArithmetic + Send + Sync + 'static> {
    fn eq(&self, other: &&Self) -> bool {
        DynArithmetic::eq(self.as_ref(), other.as_ref())
    }
}

impl Clone for Box<dyn DynArithmetic + Send + Sync + 'static> {
    fn clone(&self) -> Box<dyn DynArithmetic + Send + Sync + 'static> {
        // let source: &dyn DynArithmetic = self.as_ref();
        self.as_ref().clone()
    }
}

#[derive(PartialEq, Clone, Debug)]
pub struct Error(pub Box<dyn DynArithmetic + Sync + Send + 'static>);

impl std::ops::Deref for Error {
    type Target = dyn DynArithmetic + Sync + Send + 'static;

    fn deref(&self) -> &Self::Target {
        &*self.0
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.0.source()
    }
}

impl<E> From<E> for Error
where
    E: Arithmetic,
{
    fn from(err: E) -> Self {
        Error(Box::new(err))
    }
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Kind {
    Overflow,
    Underflow,
    DivideByZero,
}

impl Display for Kind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Kind::Underflow => write!(f, "underflow"),
            Kind::Overflow => write!(f, "overflow"),
            Kind::DivideByZero => write!(f, "divide by zero"),
        }
    }
}

#[derive(PartialEq, Clone, Debug)]
pub struct Operation<Lhs, Rhs> {
    pub lhs: Lhs,
    pub rhs: Rhs,
    pub kind: Option<Kind>,
    pub cause: Option<Error>,
}

pub trait DivideByZero<Rhs>
where
    Self: super::Type + Sized + Copy,
    Rhs: super::Type + Sized + Copy + num::Zero,
{
    fn divide_by_zero(self) -> Operation<Self, Rhs> {
        Operation {
            lhs: self,
            rhs: Rhs::zero(),
            kind: Some(Kind::DivideByZero),
            cause: None,
        }
    }
}

pub trait Overflow<Lhs>
where
    Self: super::Type + Sized + Copy,
    Lhs: super::Type + Sized + Copy,
{
    fn overflows(self, lhs: Lhs) -> Operation<Lhs, Self> {
        Operation {
            lhs,
            rhs: self,
            kind: Some(Kind::Overflow),
            cause: None,
        }
    }
}

pub trait Underflow<Lhs>
where
    Self: super::Type + Sized + Copy,
    Lhs: super::Type + Sized + Copy,
{
    fn underflows(self, lhs: Lhs) -> Operation<Lhs, Self> {
        Operation {
            lhs,
            rhs: self,
            kind: Some(Kind::Underflow),
            cause: None,
        }
    }
}

impl<L, R> Underflow<L> for R
where
    L: super::Type + Sized + Copy,
    R: super::Type + Sized + Copy,
{
}

impl<L, R> Overflow<L> for R
where
    L: super::Type + Sized + Copy,
    R: super::Type + Sized + Copy,
{
}

impl<R, L> DivideByZero<R> for L
where
    L: super::Type + Sized + Copy,
    R: super::Type + Sized + Copy + num::Zero,
{
}

#[cfg(test)]
mod tests {
    use super::DynArithmetic as ArithmeticError;
    use crate::{error::Overflow, ops};

    #[test]
    fn arithmetic_error_is_std_error() {
        let err: &dyn ArithmeticError = &ops::AddError(10u32.overflows(10u32));
        let _: &dyn std::error::Error = &err;
    }

    #[test]
    fn arithmetic_error_partial_eq() {
        type BoxedError = Box<dyn ArithmeticError + Send + Sync + 'static>;
        let add_err1: BoxedError = Box::new(ops::AddError(10u32.overflows(10u32)));
        let add_err2: BoxedError = Box::new(ops::AddError(10u32.overflows(10u64)));
        assert!(add_err1 == add_err1);
        assert!(add_err1 != add_err2);

        let sub_err1: BoxedError = Box::new(ops::SubError(10u32.overflows(12u32)));
        let sub_err2: BoxedError = Box::new(ops::SubError(10u32.overflows(15u32)));
        assert!(sub_err1 == sub_err1);
        assert!(sub_err1 != sub_err2);
    }
}