# checked-rs `checked-rs` (referred to as `checked`) is a Rust library that includes a generic type for encoding arbitrary validation logic into the type system along with a proc-macro for generating specialized integer types. > This library was extracted from a larger side-project to make it generally available and showcase Rust skills and knowledge. ## Installation The `checked` library is compatible with `rustc 1.79.0-nightly (a07f3eb43 2024-04-11)` but does not use any opt-in language features. To install `checked-rs`, add the following to your `Cargo.toml`: ```toml [dependencies] checked-rs = "1.0" ``` ## Overview The main components of this library is the the attribute macro `clamped` and the `View` struct _(plus the `Validator` trait)_. Additionally, there are some traits and types such as `Behavior` and `ClampGuard` that either configure how overflow is handled or provide an alternative way to interact with the clamped types. ## `clamped` Functional Macro This proc-macro facilitates the creation of specialized integer types tailored to specific constraints and use cases, enhancing type safety and code clarity in Rust projects. The generated types can be divided into two primary categories: ### 1. Wrapper Type This category creates a type that wraps around any sized integer, preserving the same memory layout. The wrapper type can optionally encode an upper and/or lower bound for the integer value. It further divides into two sub-categories: #### Hard Wrapper - **Invariant:** The value is guaranteed never to exceed the specified bounds. - **Usage:** Ideal for situations where strict boundary enforcement is required at the type level. #### Soft Wrapper - **Flexibility:** The value can be any valid integer for the underlying type. - **Methods:** Implements methods to check whether the value lies within the specified bounds. - **Usage:** Suitable for cases where boundary checks are needed but enforced through runtime methods rather than at the type level. ### 2. Enum Type This category generates an enum type over any sized integer, with variants that describe: - Exact values. - Ranges of values. - Nested enum types. > The top-level enum type also supports the same behavior as the "Hard" wrapper type if the variants imply an upper and/or lower bound. Additionally, this category recursively generates types to support the ranges and nested enums described by the variants. - **Exact Values:** Specific integer values represented as individual variants. - **Ranges:** Variants that encompass a range of values. - **Nested Enums:** Variants that are enums themselves, allowing for complex and hierarchical type definitions. ### Usage and Configuration The category of the type generated by the proc-macro is determined by the kind of language item specified in the input, either `struct` or `enum`. ### Specifying the Target Integer Type The target integer type is provided in an attribute above the language item. The content within the brackets of the attribute is parsed into various configuration options, allowing users to tailor the generated type to their specific needs. Here are some examples of how to use the attribute: > For the remainder of these docs, `int` will be used to refer to the integer type used for the clamped value. ### Generating Wrappers #### Specifying the Range The type should have exactly one unnamed field, but instead of declaring a type, provide the range the type covers. All range forms are allowed. ```rust,ignore #[usize] struct Exclusive(10..100); #[usize] struct Inclusive(10..=100); #[usize] struct OpenEnd(10..); #[usize] struct OpenStart(..100); ``` #### Basic Attribute To specify a basic wrapper type without additional configuration: ```rust,ignore #[usize] ``` #### Specifying Soft or Hard Behavior For wrapper types, you can specify whether the behavior should be `Soft` or `Hard`: - **Soft Behavior:** ```rust,ignore #[usize as Soft] ``` This allows the value to be any valid integer for the underlying type, with methods to check if it is within bounds. - **Hard Behavior:** ```rust,ignore #[usize as Hard] ``` This enforces that the value can never be outside the specified bounds. - **Additional Derive Macros** Additional derive macros can be applied to the generated type to include other traits. For example, to derive the Debug trait in addition to the always-derived Clone and Copy traits: ```rust,ignore #[usize as Soft; derive(Debug)] ``` #### Default Value The proc-macro allows specifying a default value for the generated type. This default value can either be inferred or manually specified. #### Inferred Default Value The default value can be inferred to be the lowest value of the range specified. #### Manually Specified Default Value Alternatively, you can manually specify the default value using an attribute. Here is an example of how to specify a default value: ```rust,ignore #[usize; default = 10] ``` #### Automatically Generated Traits The proc-macro automatically generates various trait definitions for the generated types, ensuring they integrate seamlessly with Rust's type system and standard library. The following traits are implemented: - `PartialEq` against itself and against the underlying type. - `Eq` - `PartialOrd` against itself and against the underlying type. - `Ord` - Both soft and hard versions implement `Deref` and `AsRef`. - Only soft versions implement `DerefMut` and `AsMut`. - Any applicable conversion traits to and from other built-in integer types. - Binary operations: `Add`, `Sub`, `Mul`, `Div`, `Rem`, `BitAnd`, `BitOr`, `BitXor`, and any applicable `___Assign` versions of these traits. #### Example Usage with Traits Here is an example of how the generated types can be used with the automatically implemented traits: ```rust,ignore // Define a soft wrapper type over usize with a default value of 10 #[usize as Soft; default = 10; derive(Debug)] struct MySoftBoundedIntWithDefault(0..100); // Define a hard wrapper type over u32 with a default value of 0 #[u32 as Hard; default = 0] struct MyHardBoundedIntWithDefault(0..100); # fn main() { let a = MySoftBoundedIntWithDefault::default(); let b = MySoftBoundedIntWithDefault::from(15); let c = MyHardBoundedIntWithDefault::default(); // PartialEq and PartialOrd assert!(a != b); assert!(a < b); // Deref and AsRef assert_eq!(*a, 10); assert_eq!(a.as_ref(), &10); // DerefMut and AsMut (soft only) let mut d = b; *d = 20; assert_eq!(*d, 20); assert_eq!(d.as_mut(), &mut 20); // Binary operations let sum = a + d; let product = c * 5; assert_eq!(*sum, 30); assert_eq!(*product, 0); # } ``` ### Generating Enums > documentation in-progress ### `View` The `View` struct is a wrapper around a value that encodes it's validation logic into the wrapper. The `Validator` trait is used to define the validation logic for a `View`. This wrapper is lightweight and can be used in place of the raw value via the `Deref` and/or `AsRef` traits. ```rust # use checked_rs::prelude::*; #[derive(Clone, Copy)] struct NotSeven; impl Validator for NotSeven { type Item = i32; type Error = anyhow::Error; fn validate(item: &Self::Item) -> Result<()> { if *item == 7 { Err(anyhow::anyhow!("Value must not be 7")) } else { Ok(()) } } } let mut item = View::with_validator(0, NotSeven); let mut g = item.modify(); *g = 7; assert_eq!(*g, 7); assert!(g.check().is_err()); *g = 10; assert!(g.commit().is_ok()); // the guard is consumed by commit, so we can't check it again // the `View`'s value should be updated assert_eq!(&*item, &10); ```