runtime_contracts/lib.rs
1//! Structured, understandable runtime contracts.
2//!
3//! While many languages have contract libraries, many opt to compile them only in debug and test builds. The reasoning behind
4//! this choice seems to be that they don't wish to incur a performance penalty in production.
5//! A notable exception is [Racket's contracts module](https://docs.racket-lang.org/reference/contracts.html), itself a [work of art](https://docs.racket-lang.org/guide/contracts.html).
6//! In this library, we eschew this concern in the name of both runtime safety and program correctness.
7//!
8//! This crate wishes to make it easier for practitioners building software to use and understand Programming-by-Contract.
9//! The philosophy is directly inspired by the [Design-by-Contract](https://en.wikipedia.org/wiki/Design_by_contract) (DbC)
10//! concept expressed by noted Computer Scientist, [Dr. Betrand Meyer](https://en.wikipedia.org/wiki/Bertrand_Meyer) when
11//! designing the [Eiffel programming language](https://en.wikipedia.org/wiki/Eiffel_(programming_language)) in 1986.
12//!
13//! Additionally, much thanks goes to the [`contracts`](https://crates.io/crates/contracts) crate which implements contacts
14//! as procedural macros. Definitely check it out!
15//!
16//! # Examples
17//!
18//! Though this example uses the crate's own error type, you can substitute whatever you wish so long as it works.
19//!
20//! ```
21//! use runtime_contracts::{check, ensures, requires, error::RuntimeContractError, Result};
22//!
23//! # struct Account {
24//! # pub balance: usize,
25//! # }
26//! # impl Account {
27//! # pub fn add_to_balance(&self, amount: usize) -> Result<usize> {
28//! # Ok(self.balance + amount)
29//! # }
30//! # }
31//! # fn load_account(i: &str) -> Account {
32//! # Account { balance: 613 }
33//! # }
34//!
35//! fn refund_loyalty_points(account_id: &str, point_amount: usize) -> Result<usize> {
36//! requires(|| account_id.len() == 32, "malformed account ID")?;
37//! requires(|| point_amount % 2 == 0, "attempting to refund an odd number of points")?;
38//!
39//! let account = load_account(account_id);
40//! let starting_balance = account.balance;
41//! let closing_balance = account.add_to_balance(point_amount)?;
42//!
43//! ensures(closing_balance, |balance| balance - point_amount == starting_balance, "points were not added to account")
44//! }
45//! ```
46
47pub mod error;
48
49pub type Result<T, E = error::RuntimeContractError> = core::result::Result<T, E>;
50
51pub type RuntimeContractFunction<T> = dyn Fn(T) -> Result<T>;
52
53/// Checks an arbitrary condition expressed by the given predicate. This is most useful for validating arguments at the _start_
54/// of a function. You must provide an error message, so it often makes sense to call `requires` once for each argument. This allows
55/// for passing more specific error messages back to the caller.
56///
57/// # Examples
58///
59/// Though these example use the crate's own error type, you can substitute whatever you wish so long as it works.
60///
61/// ```
62/// use runtime_contracts::{requires, error::RuntimeContractError};
63///
64/// fn add_two(i: i32, j: i32) -> Result<i32, RuntimeContractError> {
65/// requires(|| i > 0, "i must be greater than 0")?;
66/// requires(|| j > 0, "j must be greater than 0")?;
67///
68/// Ok(i + j)
69/// }
70///
71/// assert!(add_two(2, 3).is_ok());
72/// ```
73///
74/// The above example seem a bit silly since the usage of `i32` could just as easily be changed to `u32` to prevent passing
75/// in a negative number literal. For example, the following fails to compile:
76///
77/// ```compile_fail
78/// use runtime_contracts::{requires, error::RuntimeContractError};
79///
80/// fn add_two(i: u32, j: u32) -> Result<u32, RuntimeContractError> {
81/// requires(|| i > 0, "i must be greater than 0")?;
82/// requires(|| j > 0, "j must be greater than 0")?;
83///
84/// Ok(i + j)
85/// }
86///
87/// assert!(add_two(-2, 3).is_ok());
88/// ```
89///
90/// However, what if the number in question is obtained from an external source? In this case, the external source may provide
91/// malformed input! For this reason, it is especially useful to use `requires` to validate input. You can even use the provided
92/// combinator on Rust's Result type to chain contracts into a single statement:
93///
94/// ```
95/// use runtime_contracts::{requires, error::RuntimeContractError};
96///
97/// fn add_two(i: i32, j: i32) -> Result<i32, RuntimeContractError> {
98/// requires(|| i > 0, "i must be greater than 0")
99/// .and_then(|_| requires(|| j > 0, "j must be greater than 0"))?;
100///
101/// Ok(i + j)
102/// }
103///
104/// assert!(add_two(2, 3).is_ok());
105/// ```
106pub fn requires<F, M>(pred: F, message: M) -> Result<()>
107where
108 F: Fn() -> bool,
109 M: std::fmt::Display,
110{
111 if pred() {
112 Ok(())
113 } else {
114 let err = error::RuntimeContractError::RequiresFailure(message.to_string());
115
116 Err(err)
117 }
118}
119
120/// Checks an arbitrary condition expressed in a predicate run against a given value. If the condition is satisfied(read: if the
121/// predicate evaluates to true) this function yields the value passed to it. Ergo, it is most useful for checking return values
122/// at the _end_ of a function. You must provide an error message in case of failure.
123///
124/// # Examples
125///
126/// Though these example use the crate's own error type, you can substitute whatever you wish so long as it works.
127///
128/// ```
129/// use runtime_contracts::{ensures, error::RuntimeContractError};
130///
131/// fn add_two(i: i32, j: i32) -> Result<i32, RuntimeContractError> {
132/// ensures(i + j, |sum| *sum > 0, "the sum of i and j must be greater than 0")
133/// }
134///
135/// let eleven_result = add_two(5, 6);
136/// assert!(eleven_result.is_ok());
137/// assert_eq!(eleven_result.unwrap(), 11);
138///
139/// let five_result = add_two(10, -5);
140/// assert!(five_result.is_ok());
141/// assert_eq!(five_result.unwrap(), 5);
142///
143/// // In the below, the output value doesn't satisfy the contract since `5 + - 5 = 5 - 5` is not greater than 0.
144/// assert!(add_two(5, -5).is_err());
145/// ```
146///
147pub fn ensures<T, F, M>(value: T, predicate: F, message: M) -> Result<T>
148where
149 T: Clone,
150 F: FnOnce(&T) -> bool,
151 M: std::fmt::Display,
152{
153 if predicate(&value) {
154 Ok(value)
155 } else {
156 let err = error::RuntimeContractError::EnsuresFailure(message.to_string());
157
158 Err(err)
159 }
160}
161
162/// Verifies than an arbitrary condition is met, intended to verify preservation of an invariant at runtime.
163/// Think of this as a `requires` designed to be used anywhere in control flow.
164
165pub fn check<F, M>(pred: F, message: M) -> Result<()>
166where
167 F: FnOnce() -> bool,
168 M: std::fmt::Display,
169{
170 if pred() {
171 Ok(())
172 } else {
173 let err_msg = format!("invariant violated: {message}",);
174 let err = error::RuntimeContractError::CheckFailure(err_msg);
175
176 Err(err)
177 }
178}