Expand description

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")
}

Modules§

  • This module contains the crate’s own error type. It can hold other error-related data/logic as needed.

Functions§

  • Verifies than an arbitrary condition is met, intended to verify preservation of an invariant at runtime. Think of this as a requires designed to be used anywhere in control flow.
  • Checks an arbitrary condition expressed in a predicate run against a given value. If the condition is satisfied(read: if the predicate evaluates to true) this function yields the value passed to it. Ergo, it is most useful for checking return values at the end of a function. You must provide an error message in case of failure.
  • Checks an arbitrary condition expressed by the given predicate. This is most useful for validating arguments at the start of a function. You must provide an error message, so it often makes sense to call requires once for each argument. This allows for passing more specific error messages back to the caller.

Type Aliases§