# Aeneas Overview for Proof Engineers

## 1. What is Aeneas?

Aeneas is a verification toolchain for Rust programs. It translates Rust code
into pure functional programs in proof assistants — primarily **Lean**, but also
F\*, Coq, and HOL4 — by leveraging Rust's ownership discipline to eliminate
memory reasoning entirely. Rather than reasoning about pointers, lifetimes, and
aliasing, the proof engineer works with clean, pure functional code.

### How It Works

```
Rust source
    │
    ▼
  Charon  (Rust compiler plugin)
    │
    ▼
  LLBC    (Low-Level Borrow Calculus — intermediate representation)
    │
    ▼
  Aeneas  (translation engine)
    │
    ▼
  Pure functional code  (Lean / F* / Coq / HOL4)
```

**Key properties of the translation:**

- **Ownership-driven:** Rust's borrow checker guarantees that mutable references
  are unique. Aeneas exploits this to turn references into plain values and
  generate backward continuations that propagate updates — no heap model needed.
- **Modular:** Each function is translated independently. There is no
  whole-program analysis.
- **Sound:** The generated code faithfully models the semantics of the original
  Rust program, including panics and integer overflow (via the `Result` monad).

**Backend maturity:** Lean is the most mature and actively maintained backend.
F\*, Coq, and HOL4 backends exist but receive less attention.

---

## 2. The Translation Model

### 2.1 Forward Functions

A simple Rust function with no returned borrows translates directly into a pure
function wrapped in the `Result` monad.

**Rust:**
```rust
fn incr(x: &mut i32) -> i32 {
    *x = *x + 1;
    *x
}
```

**Lean (generated):**
```lean
def incr (x : I32) : Result I32 :=
  x + 1#i32
```

The `&mut i32` parameter becomes a plain `I32` value. Arithmetic is checked:
overflow produces `fail`.

### 2.2 Backward Continuations (The Key Insight)

When a Rust function returns a mutable borrow, the caller retains the ability to
write through that borrow. When the borrow ends, the update must propagate back
to the original owner. Aeneas models this by returning **both** a value **and**
a backward continuation (a closure that propagates the update).

**Rust:**
```rust
fn choose<'a, T>(b: bool, x: &'a mut T, y: &'a mut T) -> &'a mut T {
    if b { x } else { y }
}
```

**Lean (generated):**
```lean
def choose {T : Type} (b : Bool) (x : T) (y : T) :
    Result (T × (T → T × T)) :=
  if b
  then ok (x, fun z => (z, y))
  else ok (y, fun z => (x, z))
```

**Reading the result type:** `Result (T × (T → T × T))` means:
- The function may fail (`Result`).
- On success it returns a pair: the selected value (`T`) and a backward
  continuation (`T → T × T`).
- The backward continuation takes the final value written through the borrow and
  returns the updated `x` and `y`.

**Analogy with lenses:** The forward function is a "get" that extracts a view.
The backward continuation is a "put" that writes the modified value back into
the original structure.

**How callers use this:**
```lean
-- Rust: let r = choose(true, &mut a, &mut b); *r += 1;
-- After translation, the caller:
--   1. Calls `choose` to get (value, back)
--   2. Computes the new value (value + 1)
--   3. Calls `back (value + 1)` to get updated (a', b')
```

### 2.3 The Result Error Monad

All translated functions return `Result T` to model potential runtime failures:

| Constructor | Meaning                                                        |
|-------------|----------------------------------------------------------------|
| `ok v`      | Success with value `v`                                         |
| `fail e`    | Error (panic, overflow, out-of-bounds, division by zero, etc.) |
| `div`       | Divergence (non-termination)                                   |

Monadic combinators:
- **`do` notation** — monadic bind for sequencing computations
- **`massert cond`** — monadic assertion; produces `fail` when `cond` is false

**Example with `do` notation:**
```lean
def sum_three (a b c : U32) : Result U32 := do
  let ab ← a + b
  ab + c
```

Each `←` is a monadic bind: if the addition overflows, the whole computation
short-circuits to `fail`.

### 2.4 Loop Translation

Rust loops (`loop`, `while`, `for`) are translated to **auxiliary `_loop` functions**. The parent function calls the `_loop` function to enter the loop. For example, a Rust function `zero` with a loop becomes `zero` (which calls `zero_loop`) and `zero_loop` (which contains the loop body).

**Always write a separate theorem for each `_loop` function.** The parent function's spec can then use the loop theorem via `step`.

There are two translation patterns:

#### Pattern A: Recursive `_loop` function

The `_loop` function calls itself recursively, with Lean's termination checker verifying termination:

```lean
-- Generated by Aeneas:
def zero_loop (x : alloc.vec.Vec U32) (i : Usize) : Result (alloc.vec.Vec U32) := do
  if i < alloc.vec.Vec.len x then
    let x1 := ...  -- update element
    zero_loop x1 (i + 1)   -- recursive call
  else ok x
partial_fixpoint

def zero (x : alloc.vec.Vec U32) : Result (alloc.vec.Vec U32) := do
  zero_loop x 0#usize
```

**Proving the spec:** The loop invariant appears as both the precondition and postcondition of the `_loop` theorem. Use `unfold`, `split`, and `step` (which applies the theorem recursively on the recursive call). Add a `termination_by` clause:

```lean
@[step]
theorem zero_loop_spec (x : alloc.vec.Vec U32) (i : Usize) (h : i.val ≤ x.length) :
  zero_loop x i ⦃ x' =>
    x'.length = x.length ∧
    (∀ j, j < i.val → x'[j]! = x[j]!) ∧
    (∀ j, i.val ≤ j → j < x.length → x'[j]! = 0#u32) ⦄ := by
  unfold zero_loop
  simp; split
  · step ...    -- recursive case: step applies zero_loop_spec
  · simp; scalar_tac -- base case
termination_by x.length - i.val
decreasing_by scalar_decr_tac
```

#### Pattern B: `_loop` function using the `loop` fixed-point combinator

Loops use the `loop` combinator instead of direct recursion (typically common with `for`
loops which use iterators):

```lean
def loop (body : α → Result (ControlFlow α β)) (x : α) : Result β
```

The `body` returns `ControlFlow.cont x'` to continue or `ControlFlow.done y` to break.

**Proving the spec:** Use `loop.spec_decr_nat` (or `loop.spec` for general well-founded measures):

- **`loop.spec_decr_nat`** — requires a `Nat` termination measure, a loop invariant, and a postcondition
- **`loop.spec`** — general version with an arbitrary well-founded termination measure

---

## 3. How to Read Aeneas-Generated Code

### 3.1 Generated File Layout

When Aeneas translates a Rust crate, it produces several Lean files:

| File                               | Contents                                                                                    |
|------------------------------------|---------------------------------------------------------------------------------------------|
| **Types.lean**                     | Rust type definitions (structs, enums) as Lean inductive types (imports TypesExternal.lean) |
| **Funs.lean**                      | Rust function bodies as Lean definitions (imports Types.lean and FunsExternal.lean)         |
| **TypesExternal_Template.lean**    | Template for hand-written models of opaque/external types                                   |
| **FunsExternal_Template.lean**     | Template for hand-written models of external functions (e.g., from Rust's standard library) |
| **TypesExternal.lean**             | User-maintained file with type models (never overwritten by Aeneas)                         |
| **FunsExternal.lean**              | User-maintained file with function models (never overwritten by Aeneas)                     |

> **Tip:** Use the `-split-files` option to have Aeneas generate one file per
> declaration group as detailed in the above table. This is helpful for large crates.

### 3.2 Scalar Types

Aeneas maps Rust integer types to bounded scalar types with checked arithmetic:

| Rust                                    | Lean                                   |
|-----------------------------------------|----------------------------------------|
| `i8` / `i16` / `i32` / `i64` / `i128`   | `I8` / `I16` / `I32` / `I64` / `I128`  |
| `u8` / `u16` / `u32` / `u64` / `u128`   | `U8` / `U16` / `U32` / `U64` / `U128`  |
| `isize` / `usize`                       | `Isize` / `Usize`                      |

Scalar literals use the `#type` suffix notation: `1#i32`, `0#u64`, etc.

Each scalar value `s` has a `.val` field that extracts the underlying natural number
(or integer), and arithmetic operations return `Result` to account for overflow.

### 3.3 Collections and Containers

| Rust                         | Lean                          |
|------------------------------|-------------------------------|
| `[T; N]` (fixed-size array)  | `Array T n`                   |
| `Vec<T>`                     | `Vec T`                       |
| `Box<T>`                     | `T` (indirection is erased)   |

### 3.4 Conventions

- **Mutable reference parameters** become plain value parameters.
- **Returned mutable borrows** produce a tuple of (value, backward continuation).
- **Shared borrows (`&T`)** become plain `T` parameters (the value may be copied).
- **Pattern matching** maps directly to `match` in Lean.
- **Recursive functions** become Lean recursive functions (may require termination proofs).
- **Traits** are translated to Lean type classes.

---

## 4. The Spec/Proof Workflow

The standard workflow for verifying Aeneas-generated code follows a
specification-then-proof pattern.

### 4.1 Writing a Specification

Specifications are theorems decorated with `@[step]`:

```lean
@[step]
theorem my_function_spec (args...) (preconditions...) :
    my_function args ⦃ result => postcondition result ⦄ := by
  -- proof goes here
```

#### The Weakest-Precondition Notation `⦃ ... ⦄`

The `⦃ ... ⦄` notation expresses weakest preconditions over `Result` values:

- **For `Result T`:** `f ⦃ x => P x ⦄` means "`f` succeeds with value `x`
  and `P x` holds."
- **For `Result (T × (T → ...))`:** You can name both the returned value and
  the backward continuation:
  `f ⦃ x back => P x back ⦄`

This notation lets you state that the function succeeds and its result satisfies
the postcondition.

### 4.2 Registering Specs with `@[step]`

The `@[step]` attribute registers a theorem in a database so that the
`step` tactic can automatically apply it when verifying callers. This is
what makes proofs compositional: once you prove a spec for `foo`, every function
that calls `foo` can use that spec automatically via `step`.

### 4.3 Proof Tactics

The main tactics used in Aeneas proofs:

| Tactic               | Purpose                                                     |
|----------------------|-------------------------------------------------------------|
| `unfold f`           | Expand the definition of `f`                                |
| `step`               | Apply a registered `@[step]` spec for the next monadic call |
| `step*`              | Repeatedly apply `step`                                     |
| `scalar_tac`         | Discharge scalar arithmetic goals (bounds, overflow)        |
| `simp` / `simp_all`  | Simplification                                              |
| `agrind`             | Arithmetic + congruence closure                             |

### 4.4 Simple Example: Addition

```rust
fn add(a: u32, b: u32) -> u32 {
    a + b
}
```

**Generated Lean:**
```lean
def add (a : U32) (b : U32) : Result U32 :=
  a + b
```

**Specification and proof:**
```lean
@[step]
theorem add_spec (a b : U32) (h : a.val + b.val < U32.max) :
    add a b ⦃ c => c.val = a.val + b.val ⦄ := by
  unfold add
  step
```

**Reading this proof:**
1. The precondition `h` states the addition won't overflow.
2. `unfold add` expands the definition.
3. `step` applies the spec for `U32` addition, using `h` to discharge the
   overflow check, and concludes that `c.val = a.val + b.val`.

### 4.5 Example with Backward Functions: Mutable List Indexing

```lean
@[step]
theorem list_nth_mut1_spec {T : Type} [Inhabited T] (l : CList T) (i : U32)
    (h : i.val < l.toList.length) :
    list_nth_mut1 l i ⦃ x back =>
      x = l.toList[i.val]! ∧
      ∀ x', (back x').toList = l.toList.set i.val x' ⦄ := by
  unfold list_nth_mut1 list_nth_mut1_loop
  step*
  simp_all
```

**Reading this spec:**
- `list_nth_mut1 l i` returns a value `x` and a backward function `back`.
- `x = l.toList[i.val]!` — the returned value is the `i`-th element.
- `∀ x', (back x').toList = l.toList.set i.val x'` — calling `back` with a new
  value `x'` produces a list where position `i` has been replaced by `x'`.

This is the lens pattern: `x` is the "get", and `back` is the "set".

### 4.6 Typical Proof Workflow

1. **Start with `unfold`** to expose the function body.
2. **Use `step`** at each monadic bind (`←`). The tactic looks up the
   `@[step]` spec for the called function and introduces hypotheses about
   the result.
3. **Discharge side conditions** (overflow bounds, index bounds) using
   `scalar_tac`, `agrind`, or hypotheses in context.
4. **Conclude** with `simp`, `simp_all`, or direct term-level reasoning.

For recursive functions, use induction on the decreasing argument and apply
the same pattern in each case.

---

## 5. Project Setup for Lean Backend

### 5.1 Prerequisites

- A working Lean 4 installation (matching the toolchain in `backends/lean`)
- Charon (Rust compiler plugin for producing LLBC)
- Aeneas binary

### 5.2 Step-by-Step Setup

**1. Create a Lean package:**
```bash
lake new my_proofs
cd my_proofs
```

**2. Use the correct Lean toolchain:**

Copy (or symlink) the `lean-toolchain` file from `backends/lean` into your
project root to ensure version compatibility:
```bash
cp PATH_TO_AENEAS_REPO/backends/lean/lean-toolchain ./lean-toolchain
```

**3. Add Aeneas as a dependency:**

In your `lakefile.toml`:
```toml
[[require]]
name = "aeneas"
path = "PATH_TO_AENEAS_REPO/backends/lean"
```

Or in `lakefile.lean`:
```lean
require aeneas from FileSystem.FilePath.mk "PATH_TO_AENEAS_REPO/backends/lean"
```

**4. Run Charon on your Rust crate:**
```bash
charon cargo --preset=aeneas
```

This produces an `.llbc` file containing the serialized intermediate
representation.

**5. Run Aeneas to generate Lean files:**
```bash
./bin/aeneas -backend lean [OPTIONS] YOUR_CRATE.llbc
```

Useful options:
- `-split-files` — one file per declaration group
- `-dest DIR` — output directory for generated files

**6. Import and verify:**

Import the generated files in your Lean project and write specifications as
described in Section 4.

---

## 6. Key Concepts Summary

### Rust-to-Lean Translation Reference

| Rust Concept             | Lean Translation                                             |
|--------------------------|--------------------------------------------------------------|
| `&mut T` parameter       | `T` parameter (passed by value)                              |
| Returning `&'a mut T`    | Returns `Result (T × (T → ...))` with backward continuation  |
| `&T` (shared borrow)     | `T` parameter (immutable, may be copied)                     |
| `panic!()`               | `fail` in the `Result` monad                                 |
| Integer overflow         | `fail` (all arithmetic is checked)                           |
| `Box<T>`                 | `T` (heap indirection erased)                                |
| Pattern matching         | `match` in Lean                                              |
| Recursive functions      | Lean recursive functions (may need termination proofs)       |
| Traits                   | Type classes in Lean                                         |
| `Vec<T>`                 | `Vec T`                                                      |
| `[T; N]`                 | `Array T n`                                                  |

### Mental Model

Think of the translation as follows:

1. **Values, not references.** Every `&mut T` becomes a `T`. Rust's ownership
   guarantees there is exactly one owner, so passing by value is semantically
   equivalent.

2. **Backward continuations = lenses.** When a function hands out a mutable
   borrow, it also hands out a way to "put back" the modified value. This is
   the backward continuation.

3. **Result monad = checked execution.** Any operation that might fail at
   runtime (overflow, out-of-bounds, panic) is wrapped in `Result`. Your
   preconditions establish that these failures don't occur.

4. **`@[step]` = composable specs.** Each function's spec is registered so
   that callers can use it automatically. Proofs compose bottom-up.
