|
| 1 | ++++ |
| 2 | +title = "Creusot 0.10.0: February update" |
| 3 | +date = 2026-02-24 |
| 4 | +author = "The Creusot Team" |
| 5 | +description = "Assumptions, workspaces, and nightly shenanigans" |
| 6 | ++++ |
| 7 | + |
| 8 | +## What's new in Creusot? |
| 9 | + |
| 10 | +This February's release brings a couple of QOL improvements. |
| 11 | + |
| 12 | +### Assume (trusted asserts) |
| 13 | + |
| 14 | +The `proof_assert!` macro now supports the `#[trusted]` attribute. |
| 15 | + |
| 16 | +```rust |
| 17 | +#[trusted] |
| 18 | +proof_assert! { answer == 42 }; |
| 19 | +``` |
| 20 | + |
| 21 | +It removes the proof obligation that would come from the assertion, |
| 22 | +so it effectively becomes an *assumption* available to |
| 23 | +the rest of the program after it. |
| 24 | + |
| 25 | +We could have called it `assume!` or `proof_assume!`, |
| 26 | +but reusing `#[trusted]` makes it convenient to grep for all |
| 27 | +assumptions in a project. |
| 28 | + |
| 29 | +This is useful as a placeholder à la `todo!()`, letting you |
| 30 | +stub out parts of a proof while you're focusing on other aspects |
| 31 | +of it. |
| 32 | + |
| 33 | +### Wildcard patterns on integers |
| 34 | + |
| 35 | +In `match` expressions with integer patterns, |
| 36 | +the wildcard branch now remembers the values that |
| 37 | +the integer does **not** equal. |
| 38 | + |
| 39 | +```rust |
| 40 | +match n { |
| 41 | + 42 => proof_assert! { n == 42 }, |
| 42 | + _ => proof_assert! { n != 42 }, // FIXED |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +Thanks to Eric Jackson for [the contribution](https://github.com/creusot-rs/creusot/pull/1899)! |
| 47 | + |
| 48 | +### Multi-package workspaces |
| 49 | + |
| 50 | +We added options to select packages in workspaces. |
| 51 | + |
| 52 | +The command line option `-p` (or `--package`) selects a package to build |
| 53 | +with `cargo creusot` and to run provers on with `cargo creusot prove`. |
| 54 | +This is basically the same option as in `cargo build`, but to call `why3find` |
| 55 | +and run provers, we must additionally find the output Coma files corresponding |
| 56 | +to these packages. |
| 57 | + |
| 58 | +As the Creusot compiler is basically a modified `rustc`, the unit of compilation is a crate. |
| 59 | +A package defines multiple crates, and `cargo` does not build all of them by default: |
| 60 | +only libraries and binaries, excluding tests, examples, and benches. |
| 61 | +Creusot only supports that default target selection for now (that is libraries and binaries), |
| 62 | +until the need for others arises. |
| 63 | + |
| 64 | +You can also choose a default set of packages to build with `cargo creusot`, |
| 65 | +using the `default-member` field in the `[workspace.metadata.creusot]` |
| 66 | +section of your workspace `Cargo.toml`: |
| 67 | + |
| 68 | +```toml |
| 69 | +[workspace.metadata.creusot] |
| 70 | +default-members = ["PKG1", "PKG2"] |
| 71 | +``` |
| 72 | + |
| 73 | +This falls back to the existing `default-member` field in `[workspace]` |
| 74 | +for regular `cargo build`. Having a separate field for Creusot lets you |
| 75 | +designate packages specifically as targets for formal verification, |
| 76 | +next to other buildable but unverified packages. |
| 77 | + |
| 78 | +### Story of a toolchain update |
| 79 | + |
| 80 | +Creusot depends on a nightly Rust toolchain. With this release, |
| 81 | +we upgraded our toolchain from nightly-2025-11-13 to nightly-2026-01-29. |
| 82 | + |
| 83 | +We do toolchain upgrades regularly to keep up with the fast-moving |
| 84 | +`rustc` API. Ideally we do this at the beginning of each release cycle |
| 85 | +to give time to potentially subtle issues to surface by the next release. |
| 86 | + |
| 87 | +This time, there was one change that required a bit of thinking to deal with: |
| 88 | +the orphan check now panics when it encounters an unnameable type |
| 89 | +(typically, a function type). |
| 90 | + |
| 91 | +#### The orphan check and unnameable types |
| 92 | + |
| 93 | +The orphan check is what enforces "trait coherence" in Rust: that there is at most |
| 94 | +one implementation of a trait for any given type. The orphan check basically answers |
| 95 | +the question "might another crate implement this trait `Tr` for this type `Ty`?" |
| 96 | + |
| 97 | +In `rustc`, this check is performed whenever you implement a trait. |
| 98 | +Since you must spell out the type for the `impl` block, |
| 99 | +there is normally no way to encounter an unnameable type there |
| 100 | +(unnameable types are types of functions, closures, and coroutines). |
| 101 | +However `rustc` did have an experimental feature "`typeof`" which could |
| 102 | +cause the orphan check to encounter unnameable types, allegedly |
| 103 | +(I'm not familiar with the details). |
| 104 | +[That feature was removed](https://github.com/rust-lang/rust/pull/148256) |
| 105 | +in late November, and the now unreachable code in the orphan check |
| 106 | +that dealt with unnameable types was replaced with panics. |
| 107 | + |
| 108 | +In Creusot, we use the orphan check in the implementation of *type invariants*. |
| 109 | +This feature lets you specify a property that should be satisfied by all values of a type. |
| 110 | +For example, you can define a type of integers bounded by 42 as a `struct` with |
| 111 | +an `Invariant` impl: |
| 112 | + |
| 113 | +```rust |
| 114 | +struct Bounded42(usize); |
| 115 | + |
| 116 | +impl Invariant for Bounded42 { |
| 117 | + #[logic] |
| 118 | + fn invariant(self) -> bool { |
| 119 | + pearlite! { self.0 <= 42usize } |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +All functions that take a `Bounded42` argument can assume the type invariant, |
| 125 | +and all functions that return a `Bounded42` must prove the type invariant. |
| 126 | + |
| 127 | +Any type `Ty` has a type invariant, which we determine as follows: |
| 128 | + |
| 129 | +- if there is an instance `Ty: Invariant`, that is a *user-provided* type invariant; |
| 130 | +- if there is definitely no such instance, then we fall back to a *structural* type invariant |
| 131 | + derived from the type definition (it basically says that all fields satisfy |
| 132 | + their type invariant); |
| 133 | +- if there is no instance `Ty: Invariant` in this context, |
| 134 | + but we cannot rule it out definitely |
| 135 | + (an instance may appear after instantiating some type variables, |
| 136 | + possibly using implementations provided by an unknown crate), |
| 137 | + then the type invariant is an undefined predicate. |
| 138 | + |
| 139 | +The orphan check is what distinguishes between the last two cases. |
| 140 | +Furthermore, because type invariants apply to all types, this also concerns |
| 141 | +unnameable types: for example if you pass a closure as an argument to a function, |
| 142 | +then Creusot will look for its type invariant, involving an orphan check. |
| 143 | + |
| 144 | +The update to the orphan check caused it to panic in this case. |
| 145 | + |
| 146 | +The fix is simple in hindsight: we replace unnameable types (function types) |
| 147 | +with the unit type `()` before invoking the orphan check. |
| 148 | +Indeed, these types should behave the same as far as the orphan check |
| 149 | +is concerned: both the unit type and function types from the current crate |
| 150 | +or its dependencies are "external types" from the point of view of a |
| 151 | +sibling or descendant crate. |
| 152 | + |
| 153 | +It is also possible to fix the orphan check in `rustc` itself to not |
| 154 | +panic in those cases even though they don't happen within `rustc`. |
| 155 | +But our workaround works and we just don't have an incentive to tinker |
| 156 | +with it further, at least until that becomes a bigger issue. |
0 commit comments