Crates.io | adhesion |
lib.rs | adhesion |
version | 0.5.0 |
source | src |
created_at | 2017-07-07 06:41:33.389615 |
updated_at | 2018-03-06 22:29:19.492032 |
description | A set of macros for design by contact in Rust. The design of this library was inspired by D's contract programming facilities. |
homepage | |
repository | https://github.com/ErichDonGubler/adhesion-rs |
max_upload_size | |
id | 22296 |
size | 67,373 |
A set of macros for design by contact in Rust. The design of this library was inspired by D's contract programming facilities. Here's a quick example:
use std::i32;
contract! {
fn add_one_to_odd(x: i32) -> i32 {
post(y) {
assert!(y - 1 == x, "reverse operation did not produce input");
}
body {
x + 1
}
pre {
assert!(x != i32::MAX, "cannot add one to input at max of number range");
assert!(x % 2 != 0, "evens ain't appropriate here");
}
}
}
assert!(add_one_to_odd(3) == 4);
assert!(add_one_to_odd(5) == 6);
assert_that!(add_one_to_odd(2), panics);
assert_that!(add_one_to_odd(i32::MAX), panics);
In the above example, pre
runs before body
, and post
, which has the
return value of this function bound to y
, runs after. We can also define
checks with the double_check
block, which will be checked before and
after body
has run:
struct Counter {
count: u32,
max: u32
}
contract! {
fn increment_counter(c: &mut Counter) {
double_check {
assert!(c.count <= c.max, "counter max has been exceeded");
}
body {
c.count += 1;
}
}
}
let mut counter = Counter { count: 0, max: 3 };
macro_rules! assert_incremented_eq {
($e: expr) => ({
increment_counter(&mut counter);
assert!(counter.count == $e, format!("expected counter to be {}, got {}", $e, counter.count));
})
}
assert_incremented_eq!(1);
assert_incremented_eq!(2);
assert_incremented_eq!(3);
assert_incremented_eq!(4); // panics!
Actually, the above example can use a top-level double_check
block inside of
an impl
block instead, so that invariants can be maintained for each method
without needing to duplicate code:
struct Counter {
count: u32,
max: u32
}
impl Counter {
contract! {
double_check {
assert!(self.count <= self.max, "counter max has been exceeded");
}
fn increment(&mut self) {
body {
self.count.checked_add();
}
}
}
}
let mut counter = Counter { count: 0, max: 3 };
macro_rules! assert_incremented_eq {
($e: expr) => ({
counter.increment();
assert!(counter.count == $e, format!("expected counter to be {}, got {}", $e, counter.count));
})
}
assert_incremented_eq!(1);
assert_incremented_eq!(2);
assert_incremented_eq!(3);
assert_incremented_eq!(4); // panics!
Nifty, right? Check out the docs if you want more detail about this crate and what you can do with it.
This library is called "Adhesion" in reference to a particular type of contract called a "contract of adhesion", also known as a "take-it-or-leave-it" contract. Assertions in programming are definitely "take it or leave it" -- if an assertion is failing, you either have to fix the conditions of the assertion, or change the assertion itself. It sounded appropriate!
invariant
been renamed to double_check
?After the v0.2.0 release, @eternaleye pointed out in this Reddit thread
that technically an "invariant" connotes a strong guarantee that must be
rigorously maintained between ALL operations in code. This sort of guarantee is
NOT provided by the behavior D's invariant
block, as demonstrated by the
link that
@eternaleye provided.
Semantics are important, especially in systems that attempt to introduce more
rigor to software development like design by contract. For this reason, the
combined pre- and post-check block that D calls invariant
is called
double_check
in this library.
This project is dual-licensed under your choice of the MIT license or the Apache 2.0 license.
double_check
divergence from D's invariant
.