2021-04-17 19:57:30 +00:00
|
|
|
//! # Always-Equal
|
|
|
|
//!
|
|
|
|
//! Always-Equal lets you wrap `PartialEq` implementations around
|
|
|
|
//! things that cannot be compared, like a `File`.
|
|
|
|
//!
|
|
|
|
//! To use Always-Equal, put these two conditional `use`
|
|
|
|
//! statements in your module:
|
|
|
|
//!
|
2021-04-18 17:41:16 +00:00
|
|
|
//! ```rust
|
2021-04-17 19:57:30 +00:00
|
|
|
//! #[cfg (test)]
|
|
|
|
//! use always_equal::test::AlwaysEqual;
|
|
|
|
//!
|
|
|
|
//! #[cfg (not (test))]
|
|
|
|
//! use always_equal::prod::AlwaysEqual;
|
|
|
|
//! ```
|
|
|
|
//!
|
|
|
|
//! Then wrap `AlwaysEqual` around anything that needs
|
|
|
|
//! PartialEq but can't possibly implement it:
|
|
|
|
//!
|
2021-04-18 17:41:16 +00:00
|
|
|
//! ```rust
|
2021-04-17 20:59:03 +00:00
|
|
|
//! use std::fs::File;
|
|
|
|
//! use always_equal::test::AlwaysEqual;
|
|
|
|
//!
|
2021-04-17 19:57:30 +00:00
|
|
|
//! #[derive (Debug, PartialEq)]
|
|
|
|
//! pub struct MyStruct {
|
|
|
|
//! pub b: bool,
|
|
|
|
//! pub i: i64,
|
|
|
|
//! pub file: AlwaysEqual <File>,
|
|
|
|
//! }
|
|
|
|
//!
|
2021-04-17 20:59:03 +00:00
|
|
|
//! // In test code, you can create an empty wrapper using
|
|
|
|
//! // `testing_blank`, which is equal to any other wrapper:
|
2021-04-17 19:57:30 +00:00
|
|
|
//!
|
|
|
|
//! let expected = MyStruct {
|
|
|
|
//! b: true,
|
|
|
|
//! i: 0,
|
|
|
|
//! file: AlwaysEqual::testing_blank (),
|
|
|
|
//! };
|
|
|
|
//!
|
2021-04-17 20:59:03 +00:00
|
|
|
//! # let my_file = File::open ("Cargo.toml").unwrap ();
|
2021-04-17 19:57:30 +00:00
|
|
|
//! let actual = MyStruct {
|
|
|
|
//! b: true,
|
|
|
|
//! i: 0,
|
|
|
|
//! file: my_file.into (),
|
|
|
|
//! };
|
|
|
|
//!
|
|
|
|
//! assert_eq! (expected, actual);
|
|
|
|
//! ```
|
|
|
|
//!
|
|
|
|
//! This is implemented with `Option` in test mode.
|
|
|
|
//! In production mode, wrappers are never equal to any other
|
|
|
|
//! wrapper, and the `Option` is removed so that
|
|
|
|
//! `sizeof::<AlwaysEqual <T>> () == sizeof::<T>`
|
|
|
|
//! _should_ be true.
|
|
|
|
|
2021-04-17 20:59:03 +00:00
|
|
|
/// Should be used for non-test builds
|
|
|
|
|
2021-04-17 19:58:51 +00:00
|
|
|
pub mod prod {
|
|
|
|
use std::fmt;
|
|
|
|
|
2021-04-17 20:59:03 +00:00
|
|
|
/// In prod mode, `AlwaysEqual <T>` has the same size as `T`.
|
|
|
|
/// `Debug` and `Display` are passed through if `T` implements
|
|
|
|
/// them. `PartialEq` always returns `false` in prod mode.
|
|
|
|
///
|
|
|
|
/// Wrapping a string, checking its equality, and unwrapping it:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use always_equal::prod::AlwaysEqual;
|
|
|
|
///
|
|
|
|
/// let s = "some string";
|
|
|
|
/// let a = AlwaysEqual::from (s);
|
|
|
|
/// let b = AlwaysEqual::from (s);
|
|
|
|
/// assert_ne! (a, b);
|
|
|
|
/// let s2 = a.into_inner ();
|
|
|
|
/// ```
|
|
|
|
|
2021-04-17 19:58:51 +00:00
|
|
|
pub struct AlwaysEqual <T> {
|
|
|
|
inner: T,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T: fmt::Debug> fmt::Debug for AlwaysEqual <T> {
|
|
|
|
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
self.inner.fmt (f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T: fmt::Display> fmt::Display for AlwaysEqual <T> {
|
|
|
|
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
self.inner.fmt (f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T> AlwaysEqual <T> {
|
|
|
|
pub fn into_inner (self) -> T {
|
|
|
|
self.inner
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T> From <T> for AlwaysEqual <T> {
|
|
|
|
fn from (inner: T) -> Self {
|
|
|
|
Self {
|
|
|
|
inner,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T> PartialEq for AlwaysEqual <T> {
|
|
|
|
fn eq (&self, _other: &Self) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-17 20:59:03 +00:00
|
|
|
/// Should be used for test builds
|
|
|
|
|
2020-11-08 18:12:45 +00:00
|
|
|
pub mod test {
|
2021-04-17 20:59:03 +00:00
|
|
|
/// In test mode, `AlwaysEqual <T>` has the same size as
|
|
|
|
/// `Option <T>`. `Debug` is derived. `PartialEq` returns
|
|
|
|
/// `true` if either operand is a testing blank.
|
|
|
|
///
|
|
|
|
/// Wrapping a string, comparing it with a testing blank,
|
|
|
|
/// and unwrapping it:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use always_equal::test::AlwaysEqual;
|
|
|
|
///
|
|
|
|
/// let s = "some string";
|
|
|
|
/// let a = AlwaysEqual::from (s);
|
|
|
|
/// let b = AlwaysEqual::testing_blank ();
|
|
|
|
/// assert_eq! (a, b);
|
|
|
|
/// let s2 = a.into_inner ();
|
|
|
|
/// ```
|
|
|
|
|
2020-11-08 18:12:45 +00:00
|
|
|
#[derive (Debug)]
|
|
|
|
pub struct AlwaysEqual <T> {
|
|
|
|
inner: Option <T>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T> AlwaysEqual <T> {
|
|
|
|
pub fn into_inner (self) -> T {
|
|
|
|
match self.inner {
|
|
|
|
Some (x) => x,
|
|
|
|
None => unreachable! (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn testing_blank () -> Self {
|
|
|
|
Self {
|
|
|
|
inner: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T: Default> Default for AlwaysEqual <T> {
|
|
|
|
fn default () -> Self {
|
|
|
|
Self::from (T::default ())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T> From <T> for AlwaysEqual <T> {
|
|
|
|
fn from (inner: T) -> Self {
|
|
|
|
Self {
|
|
|
|
inner: Some (inner),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <T> PartialEq for AlwaysEqual <T> {
|
|
|
|
fn eq (&self, other: &Self) -> bool {
|
|
|
|
self.inner.is_none () || other.inner.is_none ()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg (test)]
|
|
|
|
mod tests {
|
|
|
|
use std::fs::File;
|
|
|
|
|
|
|
|
use super::test::*;
|
|
|
|
|
|
|
|
// Can't impl Clone or PartialEq because of the File
|
|
|
|
type CantCompare = Option <File>;
|
|
|
|
|
|
|
|
#[derive (Debug, Default, PartialEq)]
|
|
|
|
struct MyStruct
|
|
|
|
{
|
|
|
|
file: AlwaysEqual <CantCompare>,
|
|
|
|
name: &'static str,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_1 () {
|
|
|
|
let concrete_1 = MyStruct {
|
|
|
|
file: None.into (),
|
|
|
|
name: "my_struct",
|
|
|
|
};
|
|
|
|
let concrete_2 = MyStruct {
|
|
|
|
file: None.into (),
|
|
|
|
name: "my_struct",
|
|
|
|
};
|
|
|
|
let concrete_bad = MyStruct {
|
|
|
|
file: None.into (),
|
|
|
|
name: "not_my_struct",
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_ne! (concrete_1, concrete_2);
|
|
|
|
assert_ne! (concrete_2, concrete_bad);
|
|
|
|
assert_ne! (concrete_bad, concrete_1);
|
|
|
|
|
|
|
|
let dummy_1 = MyStruct {
|
|
|
|
file: AlwaysEqual::testing_blank (),
|
|
|
|
name: "my_struct",
|
|
|
|
};
|
|
|
|
let dummy_2 = MyStruct {
|
|
|
|
file: AlwaysEqual::testing_blank (),
|
|
|
|
name: "my_struct",
|
|
|
|
};
|
|
|
|
let dummy_bad = MyStruct {
|
|
|
|
file: AlwaysEqual::testing_blank (),
|
|
|
|
name: "not_my_struct",
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq! (dummy_1, dummy_2);
|
|
|
|
assert_ne! (dummy_2, dummy_bad);
|
|
|
|
assert_ne! (dummy_bad, dummy_1);
|
|
|
|
|
|
|
|
assert_eq! (concrete_1, dummy_1);
|
|
|
|
assert_eq! (concrete_bad, dummy_bad);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_2 () {
|
|
|
|
let v1 = Vec::<AlwaysEqual <File>>::new ();
|
|
|
|
let v2 = Vec::<AlwaysEqual <File>>::new ();
|
|
|
|
|
|
|
|
assert_eq! (v1, v2);
|
|
|
|
}
|
|
|
|
}
|