D-inspired contract programming in Rust using macros
invariant
block has now been renamed to double_check
(issue #24). The README
has been updated to explain why this library now diverges from its original
inspiration in D.fn
definition, which is MUCH more ergonomic for public interfaces in structs and traits.CONTRIBUTING.md
) adapted from the Contributor Covenant 1.4.pub
and its variants) were not accepted before this point. Now they are.This just in: generics are now supported in Adhesion! With the help of macros drawn from the parse-generics-shim
crate, you can now write functions kind of like the ones you can find in the new tests associated with this release:
contract! {
fn add_together1<T: ::std::ops::Add>(left: T, right: T) -> <T as std::ops::Add>::Output {
// ...
}
}
contract! {
fn add_together2<T>(left: T, right: T) -> T::Output where T: ::std::ops::Add {
// ...
}
}
There are some limitations associated with the current handling of generics in macros, but they should accommodate a majority of use cases until a better solution is provided by Rust itself for generics parsing in macros.
@DanielKeep and his parse-generics-shim
crate has been essential in making this release possible. Thank you so much!
This particular release fills me with pride, because it's my second publish into the crates.io
ecosystem and my first attempt to really polish a crate. I've learned WAY MORE about Rust macros by example than I planned, but the journey to getting this crate somewhat usable has been a blast. I'm excited to tell you about what's changed since 0.1.0!
Most of this release has been getting the contract!
macro up to snuff with most usecases. Previously, the contract!
macro was pretty stiff and restrictive with the fn
declarations it accepted, and required you to specify every single block of a contract in order:
contract! {
fn do_something() -> () { // Yep, the `()` was necessary
// So were each of these blocks, even if you didn't need them
pre {}
body {}
post(result) {} // Param binding here non-negotiable
invariant {}
}
}
Now, many more valid fn
declarations are accepted, and every contract block is optional -- even the post
block's parameter can be omitted! This makes things MUCH more usable, and hopefully the entire new suite of tests, examples, README updates, and new documentation speak for themselves! Even so, here's some examples:
The function we previously had to write everything out for:
contract! {
fn do_something() {} //
}
One of the new examples called square_root
, which was derived from the original inspiration of this library here:
#[macro_use]
extern crate adhesion;
contract! {
fn square_root(x: i64) -> i64 {
pre {
assert!(x >= 0);
}
post(result) { // Look ma, `post` came before `body`!
assert!((result * result) <= x && (result + 1) * (result + 1) > x);
}
body {
(x as f64).sqrt() as i64
}
}
}
fn main() {
assert!(square_root(0) == 0);
assert!(square_root(1) == 1);
assert!(square_root(25) == 5);
}
There's still two major limitations that I hope can be overcome with some development and learning on my part; hopefully they'll progress quickly:
Any help or guidance you can offer would be greatly appreciated! It's awesome to me that this is what's left for me to consider Adhesion to have most of the features I originally envisioned for it. Whoohoo!
I'll be focusing on overcoming the limitations mentioned previously, and figuring out if there's a way to get invariant
blocks into struct
definitions. I'll prioritize struct
support and ergonomics for v0.3.0, which hopefully will come over the course of the next month as time allows.
cargo
made it a breeze! I've appreciated more and more the tooling that comes with Rust, and am excited to keep developing Adhesion and making it something nice to use.skeptic
crate made by @brson, which lets you include tests in Markdown documents like the examples in the README written in this release.