Skip to content

Commit 3a03861

Browse files
committed
2026-02-24: Creusot 0.9.0
1 parent a54bfff commit 3a03861

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

content/2026-02-24.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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.

content/_index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
+++
2+
sort_by = "date"
3+
paginate_by = 5
4+
+++

0 commit comments

Comments
 (0)