Crates.io | checked-rs |
lib.rs | checked-rs |
version | 1.0.0 |
source | src |
created_at | 2024-06-11 16:22:31.102899 |
updated_at | 2024-07-21 18:42:40.916802 |
description | A library for encoding validation semantics into the type system. |
homepage | |
repository | https://github.com/HoodieCollin/checked-rs |
max_upload_size | |
id | 1268339 |
size | 66,547 |
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.
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
:
[dependencies]
checked-rs = "1.0"
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 MacroThis 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:
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:
This category generates an enum type over any sized integer, with variants that describe:
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.
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
.
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.
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.
#[usize]
struct Exclusive(10..100);
#[usize]
struct Inclusive(10..=100);
#[usize]
struct OpenEnd(10..);
#[usize]
struct OpenStart(..100);
To specify a basic wrapper type without additional configuration:
#[usize]
For wrapper types, you can specify whether the behavior should be Soft
or Hard
:
#[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.
#[usize as Hard]
This enforces that the value can never be outside the specified bounds.
#[usize as Soft; derive(Debug)]
The proc-macro allows specifying a default value for the generated type. This default value can either be inferred or manually specified.
The default value can be inferred to be the lowest value of the range specified.
Alternatively, you can manually specify the default value using an attribute. Here is an example of how to specify a default value:
#[usize; default = 10]
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
Deref
and AsRef<int>
.DerefMut
and AsMut<int>
.Add
, Sub
, Mul
, Div
, Rem
, BitAnd
, BitOr
, BitXor
, and any applicable ___Assign
versions of these traits.Here is an example of how the generated types can be used with the automatically implemented traits:
// 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);
# }
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.
# 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);