15 releases (breaking)
0.12.1 | Sep 7, 2024 |
---|---|
0.12.0 | Jun 17, 2024 |
0.11.2 | May 14, 2024 |
0.10.0 | Mar 6, 2024 |
0.5.0 | Dec 27, 2023 |
#345 in Rust patterns
13KB
127 lines
strong-type
strong-type
is a Rust crate that offers macros to easily create strongly typed and named primitive and string types. Strong typing helps in making code more expressive and less prone to errors, ensuring that each type is used in its intended way.
use strong_type::StrongType;
#[derive(StrongType)]
struct Timestamp(i64);
let timestamp = Timestamp::new(1701620628123456789);
println!("{}", timestamp); // Timestamp(1701620628123456789)
Features
-
Derive trait
StrongType
: Create a named strong type.- The macro automatically implement common traits like
Clone
,Debug
,Default
,PartialEq
,PartialOrd
,Send
, andSync
. It also implementsDisplay
by default, unless overridden by the custom_display attribute. - Conditionally, based on the underlying data type, traits like
Copy
,Eq
,Ord
,Hash
may also be implemented. For primitive data types likei32
orbool
, these additional traits will be automatically included. - Numeric types, both integer and floating-point, also implement constants
MIN
,MAX
,INFINITY
,NEG_INFINITY
, andZERO
. Additionally, for floating-point types,NAN
is implemented.
- The macro automatically implement common traits like
-
Attributes:
- Adding the following attributes to
#[strong_type(...)]
allows for additional features:auto_operators
: Automatically implements relevant arithmetic (for numeric types) or logical (for boolean types) operators.addable
: Automatically implements theAdd
,Sub
, and other relevant traits. The attribute is a strict subset ofauto_operators
.scalable
: Automatically implements theMul
,Div
,Rem
, and other relevant traits between a strong typed struct and its primitive type. Note that the attribute is not a subset ofauto_operators
.custom_display
: Allows users to manually implement theDisplay
trait, providing an alternative to the default display format.conversion
: Automatically implementsFrom
andInto
traits for the underlying type. This is optional since conversion may make strong types less distinct.underlying
: Specifies the underlying primitive type for nested strong types.
- Adding the following attributes to
Installation
Add strong-type
to your Cargo.toml
:
[dependencies]
strong-type = "0.12"
Supported underlying types:
- Integer types:
i8
,i16
,i32
,i64
,i128
,isize
- Unsigned integer types:
u8
,u16
,u32
,u64
,u128
,usize
- Floating-point types:
f32
,f64
- Boolean type:
bool
char
String
- Strong types of the above types
Examples
Creating a named strong type:
With a private field:
use strong_type::StrongType;
#[derive(StrongType)]
struct Tag(String);
let tag = Tag::new("dev");
const TAG: Tag = Tag::const_new("prod");
With a public field:
use strong_type::StrongType;
#[derive(StrongType)]
struct Timestamp(pub i64);
let timestamp = Timestamp(1701620628123456789);
println!("{}", timestamp); // Timestamp(1701620628123456789)
Demonstrating type distinctiveness:
use strong_type::StrongType;
use std::any::Any;
#[derive(StrongType)]
struct Second(i32);
#[derive(StrongType)]
struct Minute(i32);
let x = Second::new(2);
let y = Second::new(3);
let z = Minute::new(3);
assert_eq!(x.type_id(), y.type_id()); // Same type: Second
assert_ne!(y.type_id(), z.type_id()); // Different types: Second versus Minute
Utilizing Hashability:
use std::collections::HashSet;
#[derive(StrongType)]
struct Tag(String);
let mut map = HashSet::<Tag>::new();
map.insert(Tag::new("dev"));
map.insert(Tag::new("prod"));
assert_eq!(map.len(), 2);
Named integer type with arithmetic operations:
use strong_type::StrongType;
#[derive(StrongType)]
#[strong_type(auto_operators)]
struct Nanosecond(u32);
let x = Nanosecond::new(2);
let y = Nanosecond::new(3);
let z = Nanosecond::default();
assert_eq!(x.value(), 2);
assert_eq!(y.value(), 3);
assert_eq!(z.value(), 0);
assert!(x < y);
assert!(y >= x);
assert_eq!(x + y, Nanosecond(5));
#[derive(StrongType)]
#[strong_type(scalable)]
struct Millisecond(u32);
let x = Millisecond::new(2);
assert_eq!(x * 3, Millisecond(6));
Named bool type with logical operations:
use strong_type::StrongType;
#[derive(StrongType)]
#[strong_type(auto_operators)]
struct IsTrue(bool);
let x = IsTrue::new(true);
let y = IsTrue::new(false);
assert_eq!(x & y, IsTrue::new(false));
assert_eq!(x | y, IsTrue::new(true));
assert_eq!(x ^ y, IsTrue::new(true));
assert_eq!(!x, IsTrue::new(false));
Custom display implementation with custom_display
:
use std::fmt::{Display, Formatter, Result};
use strong_type::StrongType;
#[derive(StrongType)]
#[strong_type(custom_display)]
struct Second(f64);
impl Display for Second {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "Second({:.2})", &self.0)
}
}
println!("{}", Second::new(std::f64::consts::E)); // "Second(2.72)"
println!("{:?}", Second::new(std::f64::consts::E)); // "Second { value: 2.718281828459045 }"
Nested strong types:
#[derive(StrongType)]
#[strong_type(auto_operators)]
struct Dollar(i32);
#[derive(StrongType)]
#[strong_type(auto_operators, underlying = i32)]
struct Cash(Dollar);
#[derive(StrongType)]
#[strong_type(underlying = i32)]
struct Coin(Cash);
Caveats:
- When using
#[derive(StrongType)]
, the traitsEq
andPartialEq
are implemented withimpl
. As a result,StructuralEq
andStructuralPartialEq
remain unimplemented, preventing pattern matching with strong-typed primitives. #[strong_type(scalable)]
does not work for nested strong types.
Dependencies
~220–660KB
~16K SLoC