Hacker News new | ask | show | jobs
by afdbcreid 252 days ago
There is more than one formal verification library for Rust, although none is as integrated as SPARK I believe.
1 comments

How does that work?

Why can't other languages have a "formal verification library"?

> How does that work?

Usually taking the IR (MIR) from rustc and translating it to a verifier engine/language, with the help of metadata in the source (attributes) when needed. E.g. Kani, Prusti, Creusot and more.

> Why can't other languages have a "formal verification library"?

I don't think there is a reason that prevents that, and perhaps some have, however it turns out that modelling shared mutability formally is really hard, and therefore the shared xor mutable rule in Rust really helps verification.

Wouldn't C have such a library already if it really was "possible"? You can turn off strict aliasing.
Not a library, but it kinda does: Frama-C will formally verify C code using special comments to write the contracts in, and I hear it can prove more pointer properties than SPARK, although it takes more more effort to maintain a Frama-C codebase due to all the other missing language features.
Does that mean I can use C + Frama-C so I won't have to use Rust (or Ada / SPARK)? :P
Well, I'm not an expert in formal verifications, but there are such libraries, I listed few of them above, you can go and check them. So it is possible.

C doesn't have the shared xor mutable rule - with strict aliasing or without.

I checked them, but I am not convinced and I am not sure why it was brought into this thread.

SPARK has industrial-strength, integrated verification proven in avionics (DO-178C), rail (EN 50128), and automotive (ISO 26262) contexts. Rust's tools are experimental research projects with limited adoption, and they're bolted-on and fundamentally different from SPARK.

SPARK is a designed-for-verification language subset. Ada code is written in SPARK's restricted subset with contracts (Pre, Post, Contract_Cases, loop invariants) as first-class language features. The GNAT compiler understands these natively, and GNATprove performs verification as part of the build process. It's integrated at the language specification level.

Rust's tools retrofit verification onto a language designed for different goals (zero-cost abstractions, memory safety via ownership). They translate existing Rust semantics into verification languages after the fact - architecturally similar to C's Frama-C or VCC (!). The key difference from C is that Rust's type system already guarantees memory safety in safe code, so these tools can focus on functional correctness rather than proving absence of undefined behavior.

Bottom line is that these tools cannot achieve SPARK-level verification for fundamental reasons: `unsafe` blocks create unverifiable boundaries, the trait system and lifetime inference are too complex to model completely, procedural macros generate code that can't be statically verified, interior mutability (`Cell`, `RefCell`) bypasses type system guarantees, and Rust can panic in safe code. Most critically, Rust lacks a formal language specification with mathematical semantics.

SPARK has no escape hatches, so if it compiles in SPARK, the mathematical guarantees hold for the entire program. SPARK's formal semantics map directly to verification conditions. Rust's semantics are informally specified and constantly evolving (async, const generics, GATs). This isn't tooling immaturity though, it's a consequence of language design.

> SPARK has no escape hatches

It does, you can declare a procedure in SPARK but implement it in regular Ada. Ada is SPARK's escape hatch.

This is just as visible in source code as unsafe is in Rust, a procedure body will have SPARK_Mode => Off.

I think I did say:

> although none is as integrated as SPARK I believe

And yes, they're experimental (for now). But some are also used in production. For example, AWS uses Kani for some of their code, and recently launched a program to formally verify the Rust standard library.

Whether the language was designed for it does not matter, as long as it works. And it works well.

> `unsafe` blocks create unverifiable boundaries

Few of the tools can verify unsafe code is free of UB, e.g. https://github.com/verus-lang/verus. Also, since unsafe code should be small and well-encapsulated, this is less of a problem.

> the trait system and lifetime inference are too complex to model completely

You don't need to prove anything about them: they're purely a type level thing. At the level these tools are operating, (almost) nothing remains from them.

> procedural macros generate code that can't be statically verified

The code that procedural macros generate is visible to these tools and they can verify it well.

> interior mutability (`Cell`, `RefCell`) bypasses type system guarantees

Although it's indeed harder, some of the tools do support interior mutability (with extra annotations I believe).

> Rust can panic in safe code

That is not a problem - in fact most of them prove precisely that: that code does not panic.

> Most critically, Rust lacks a formal language specification with mathematical semantics

That is a bit of a problem, but not much since you can follow what rustc does (and in fact it's easier for these tools, since they integrate with rustc).

> Rust's semantics are informally specified and constantly evolving (async, const generics, GATs)

Many of those advancements are completely erased at the levels these tools are operating. The rest does need to be handled, and the interface to rustc is unstable. But you can always pin your Rust version.

> This isn't tooling immaturity though, it's a consequence of language design.

No it's not, Rust is very well amenable to formal verification, despite, as you said, not being designed for it (due to the shared xor mutable rule, as I said), Perhaps even more amenable than Ada.

Also this whole comment seems unfair to Rust since, if I understand correctly, SPARK also does not support major parts of Ada (maybe there aren't unsupported features, but you not all features are fully supported). As I said I know nothing about Ada or SPARK, but if we compare the percentage of the language the tools are supporting, I won't be surprised if that of the Rust tools is bigger (despite Rust being a bigger language). These tools just support Rust really well.