6 releases

0.2.1 Nov 8, 2024
0.2.0 Feb 12, 2024
0.1.3 Feb 9, 2024

#1474 in Rust patterns

Apache-2.0

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?
  • 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 like Result and Option?

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