6 releases
0.2.1 | Nov 8, 2024 |
---|---|
0.2.0 | Feb 12, 2024 |
0.1.3 | Feb 9, 2024 |
#1474 in Rust patterns
10KB
51 lines
runtime-contracts
: Structured, understandable runtime contracts for Rust.
For background, context, and usage examples, please see the crate documentation.
Bugs
If you find a problem, please open an issue. Suggestions are welcome!
Roadmap
- Simple contracts expressable via straightforward utlity functions.
- Contracts as functions/closures.
- Would it be as simple as
type RuntimeContractFunction<T> = dyn Fn(T) -> Result<T>
or would we need more?
- Would it be as simple as
- Contract composition (assume we at least want monoidal composition).
- If contracts are functions, can we just use function composition?
- Do we need or want a
RuntimeContract
struct to encapsulate contract specifics and provide combinators likeResult
andOption
?
lib.rs
:
Structured, understandable runtime contracts.
While many languages have contract libraries, many opt to compile them only in debug and test builds. The reasoning behind this choice seems to be that they don't wish to incur a performance penalty in production. A notable exception is Racket's contracts module, itself a work of art. In this library, we eschew this concern in the name of both runtime safety and program correctness.
This crate wishes to make it easier for practitioners building software to use and understand Programming-by-Contract. The philosophy is directly inspired by the Design-by-Contract (DbC) concept expressed by noted Computer Scientist, Dr. Betrand Meyer when designing the Eiffel programming language in 1986.
Additionally, much thanks goes to the contracts
crate which implements contacts
as procedural macros. Definitely check it out!
Examples
Though this example uses the crate's own error type, you can substitute whatever you wish so long as it works.
use runtime_contracts::{check, ensures, requires, error::RuntimeContractError, Result};
fn refund_loyalty_points(account_id: &str, point_amount: usize) -> Result<usize> {
requires(|| account_id.len() == 32, "malformed account ID")?;
requires(|| point_amount % 2 == 0, "attempting to refund an odd number of points")?;
let account = load_account(account_id);
let starting_balance = account.balance;
let closing_balance = account.add_to_balance(point_amount)?;
ensures(closing_balance, |balance| balance - point_amount == starting_balance, "points were not added to account")
}
Dependencies
~235–690KB
~16K SLoC