invariant-reference 0.1.0

Helps code authors define and use invariants
Documentation
# Intro

When writing Rust code that contains `.expect()` calls, we need to provide a message that explains
why the panic is impossible here, e.g.:

```rust
fn main() {
    let items = make_items();
    let first = items.get(0).expect("`items` is initialized to 3 items");
}

fn make_items() -> Vec<i32> {
    vec![1, 2, 3]
}
```

The use of string message for `.expect()` is not ideal, as its contents is not checked by the compiler,
and it is hard to provide a compact message with a comprehensive explanation.

We can make some important observations:
- the `.expect()` call is actually a use of some invariant
- some other place in code must ensure that this invariant holds.

E.g., for the example code:
- the invariant is "`make_items` returns a non-empty vector"
- this invariant is ensured in the body of `make_items` function
- this invariant is used in the body of `main` function

This library aims to improve this by providing at least some of degree of compiler checking for `.expect()` calls.
We let the Rust's type system to check that all used references are proven in some other place of code.

With the help of `invariant_reference`, the example code is written as:
```rust
fn main() {
    let items = make_items();
    let first = items.get(0).unwrap_under_invariant::<MakeItemsReturnsNonEmpty>();
}

#[derive(Invariant)]
#[invariant(message = "make_items() returns non-empty vector")]
struct MakeItemsReturnsNonEmpty;

fn make_items() -> Vec<i32> {
    invariant_established!(MakeItemsReturnsNonEmpty, why = "vec with 3 items is non-empty");
    vec![1, 2, 3]
}
```

This style of code has several advantages:
- the invariants are explicitly defined in code
- the developer is force to provide some explanation of what the invariant means and why it is upheld
- the `invariant_established!` is placed close to the code that ensures that the invariant holds;
  during code review it is easier to spot mistakes in such code
- we can use "Find All References" function to find all places that depend on an invariant

# How to use

1. Add references to `invariant_reference` and `invariant_reference_derive` crates
2. Define the invariant as a struct with the `#[derive(Invariant)]` macro:
   ```
   #[derive(Invariant)]
   #[invariant(message = "the xyz vector is not empty", num_proofs = 1)]
   struct XyzVecIsNotEmpty;
   ```
   - provide the `message` that explains the essence of the invariant, i.e. what condition must hold true
   - optionally, if an invariant is established in multiple places (e.g., a struct has multiple constructors),
     specify the number of such code paths in the `num_proofs` attribute.
3. Invoke the `invariant_established!(XyzVecIsNotEmpty, why = "...")` macro in the specific code place where it makes sense
   (e.g., in the body of a struct constructor)
   - use `invariant_established!(XyzVecIsNotEmpty[0], why = "...")` macro if there are multiple such places
4. Invoke `OptionExt::unwrap_under_invariant::<XyzVecIsNotEmpty>()` or `ResultExt::unwrap_under_invariant::<XyzVecIsNotEmpty>()` instead of `.expect()`

# Notes and limitations

- The code will fail to compile if `invariant_established!` is not invoked (even if the invariant is not referenced) or invoked multiple times for the same invariant (unless using different indices)
- `invariant_established!` may only be called within the same crate, as this libraries relies on traits.