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.
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.
Yes, SPARK provides an explicit escape hatch, but it is meaningfully different from Rust's `unsafe`.
In SPARK: you keep the spec in the verifiable world (a SPARK-visible subprogram declaration) and place the implementation outside the proof boundary (for example in a body or unit compiled with `pragma SPARK_Mode(Off)`). The body is visible in source but intentionally opaque to the prover; callers are still proved against the spec and must rely on that contract.
In Rust: `unsafe` is a lexical, language-level scope or function attribute that disables certain compiler/borrow checks for the code inside the block or function. The unchecked code remains inline and visible; the language/borrow-checker no longer enforces some invariants there, so responsibility shifts to the programmer at the lexical site.
Practical differences reviewers should understand:
- Granularity: `unsafe` is lexical (blocks/functions); SPARK's hatch is modular/procedural (spec vs body).
- Visibility: Rust's unchecked code is written inline and annotated with `unsafe`; SPARK's unchecked implementation is placed outside the prover but the spec remains visible and provable.
- Enforcement model: Rust's safety holes are enforced/annotated by the compiler's type/borrow rules (caller and callee responsibilities are explicit at the site). SPARK enforces sound proofs on the interface; the off-proof body must be shown (by review/tests/docs) to meet the proven contract.
- Best practice: keep off-proof bodies tiny, give strong pre/postconditions on the SPARK spec, minimize the exposed surface, and rigorously review and test the non-SPARK implementation.
TL;DR: `unsafe` = "this code block bypasses language checks"; SPARK's escape hatch = "this implementation is deliberately outside the prover, but the interface is still what we formally prove against."
I'm not sure I see much of a difference still. Here's what I think is a more correct Rust comparison:
> In [Rust]: you keep the spec in the verifiable world (a [type checker-visible function signature]) and place the implementation outside the proof boundary (for example in a body or unit compiled with `unsafe`). The body is visible in source but intentionally opaque to the [type checker; callers are still proved against the spec and must rely on that contract.
These "practical differences" seem questionable to me as well.
> Granularity: `unsafe` is lexical (blocks/functions); SPARK's hatch is modular/procedural (spec vs body).
`unsafe` can trivially be expanded from a block to a function body, thereby transforming it to a "modular/procedural" hatch. `unsafe` on function signatures is more akin to SPARK's `Assume`, albeit with the precise assumption (hopefully) in the documentation.
> Visibility: Rust's unchecked code is written inline and annotated with `unsafe`
This... doesn't seem like a difference to me? According to you, you can place SPARK's unchecked code "in a body [] compiled with `pragma SPARK_Mode(Off)`". That sure sounds like writing unchecked code "inline" and annotated with SPARK's equivalent of `unsafe` to me.
> SPARK's unchecked implementation is placed outside the prover but the spec remains visible and provable.
You can say an analogous thing about Rust: The unchecked implementation is placed outside the [type checker] but the [function signature] remains visible and [checkable].
> Enforcement model: Rust's safety holes are enforced/annotated by the compiler's type/borrow rules (caller and callee responsibilities are explicit at the site).
This is... confusing? I'm honestly not sure what you're trying to get at.
> SPARK enforces sound proofs on the interface; the off-proof body must be shown (by review/tests/docs) to meet the proven contract.
The Rust equivalent is a non-`unsafe` function signature with an `unsafe` body, and what you say is equally as applicable.
> Best practice: keep off-proof bodies tiny, give strong pre/postconditions on the SPARK spec, minimize the exposed surface, and rigorously review and test the non-SPARK implementation.
This... doesn't describe a difference at all? It's equally as applicable to the analogous Rust construct.
> `unsafe` = "this code block bypasses language checks"
Technically wrong? Bit of a nitpick, but `unsafe` technically does not disable any language checks; it gives you access to unchecked capabilities. It's a slight difference but an important one, since it means that if you have code that doesn't compile and doesn't use unchecked capabilities (e.g., a borrow checking error with regular references), wrapping the code in `unsafe` will not allow your code to compile.
> SPARK's escape hatch = "this implementation is deliberately outside the prover, but the interface is still what we formally prove against."
Again, you can say basically the same thing for non-`unsafe` functions in Rust.
I think you're conflating surface similarity with semantic equivalence - Rust's `unsafe` is not the same as SPARK's off-proof/`SPARK_Mode(Off)` hatch.
> `unsafe` can trivially be expanded from a block to a function body, thereby transforming it to a "modular/procedural" hatch. `unsafe` on function signatures is more akin to SPARK's `Assume`, albeit with the precise assumption (hopefully) in the documentation.
Not exactly. The key difference is semantic, not syntactic. SPARK's modular/procedural hatch is proof-driven: the spec (pre/postconditions, invariants) remains fully visible to the prover, generating verification conditions for callers. The off-proof body is intentionally opaque and becomes an external assurance artifact. Rust's `unsafe` merely lifts compiler restrictions on the enclosed code; it does not automatically generate proof obligations, regardless of whether it is block- or function-scoped.
> This... doesn't seem like a difference to me? According to you, you can place SPARK's unchecked code "in a body [] compiled with `pragma SPARK_Mode(Off)`". That sure sounds like writing unchecked code "inline" and annotated with SPARK's equivalent of `unsafe` to me.
Superficially yes, but the semantics differ. In SPARK, the spec is verified for all callers; the body is hidden but the interface guarantees are enforced. In Rust, the `unsafe` body is visible to the compiler and only relaxes type/borrow checks. No automatic verification of functional behavior occurs across the boundary without external tools.
> You can say an analogous thing about Rust: The unchecked implementation is placed outside the [type checker] but the [function signature] remains visible and [checkable].
Not really. Rust enforces type and ownership correctness, not behavioral correctness. SPARK's prover generates semantic proof obligations for each call based on the spec. Rust's function signature alone does not provide that level of guarantee.
> This is... confusing? I'm honestly not sure what you're trying to get at.
The point is that SPARK enforces caller correctness automatically via the prover using the spec. The off-proof body is a separate assurance item. In Rust, `unsafe` transfers responsibility to the implementer; the compiler does not enforce semantic contracts. The difference is enforcement model: SPARK's model gives machine-checked guarantees for safe clients, Rust does not.
> The Rust equivalent is a non-`unsafe` function signature with an `unsafe` body, and what you say is equally as applicable.
Not quite. Without an external verifier and additional annotations, Rust does not automatically prove functional correctness across an `unsafe` boundary. SPARK's off-proof body still guarantees that callers satisfy the verified spec, which is enforced at compile time.
To reiterate in a way that may be understood better: without additional external verification, Rust does not automatically prove anything about functional behavior across the `unsafe` boundary. SPARK does, by design: callers are verified against the spec even if the body is off-proof. Rust requires a separate verifier (Prusti, Creusot, Kani, Verus) plus annotations to reach the same level of guarantee[1].
> This... doesn't describe a difference at all? It's equally as applicable to the analogous Rust construct.
Best practices may overlap (small unchecked regions, rigorous review), but that doesn't make them equivalent. SPARK's off-proof body is part of a proof-integrated model; Rust's `unsafe` is merely a compiler capability marker. The guarantees differ.
> Technically wrong? Bit of a nitpick, but `unsafe` technically does not disable any language checks; it gives you access to unchecked capabilities. It's a slight difference but an important one, since it means that if you have code that doesn't compile and doesn't use unchecked capabilities (e.g., a borrow checking error with regular references), wrapping the code in `unsafe` will not allow your code to compile.
Correct, that nuance is valid. It does not change the core distinction though: SPARK produces verifier-checked obligations for the interface, whereas Rust's `unsafe` simply gives the programmer unchecked access without automated semantic proofs.
> Again, you can say basically the same thing for non-`unsafe` functions in Rust.
No. Non-`unsafe` Rust functions are type-safe, but they do not automatically generate semantic verification conditions for callers. SPARK does, and this is the fundamental difference: proof-driven interface vs. capability marker.
TL;DR: SPARK's "escape hatch" is a proof-driven verification boundary: the spec is machine-checked and callers are proved against it; the implementation may be off-proof but the interface guarantees remain enforced. Rust's `unsafe` is a language capability marker that grants low-level operations and transfers responsibility to the programmer; it does not, by itself, produce verifier-checked behavioral contracts (you need external tools like Prusti/Creusot/Kani/Verus for that).
I can re-explain the differences in a more digestible manner if that would help, instead of going back and forth.
> 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.
> Whether the language was designed for it does not matter, as long as it works. And it works well.
It matters fundamentally. "Works well" for research projects or limited AWS components is not equivalent to DO-178C Level A certification where mathematical proof is required. The verification community distinguishes between "we verified some properties of some code" and "the language guarantees these properties for all conforming code."
> Few of the tools can verify unsafe code is free of UB
With heavy annotation burden, for specific patterns. SPARK has no unsafe - the entire language is verifiable. That's the difference between "can be verified with effort" and "is verified by construction."
> You don't need to prove anything about [traits/lifetimes]: they're purely a type level thing
Trait resolution determines which code executes (monomorphization). Lifetimes affect drop order and program semantics. These aren't erased - they're compiled into the code being verified. SPARK's type system is verifiable; Rust's requires the verifier to trust the compiler's type checker.
> The code that procedural macros generate is visible to these tools
The macro logic is unverified Rust code executing at compile time. A bug in the macro generates incorrect code that may pass verification. SPARK has no equivalent escape hatch.
> some of the tools do support interior mutability (with extra annotations I believe)
Exactly - manual annotation burden. SPARK's verification is automatic for all conforming code. The percentage of manual proof effort is a critical metric in formal verification.
> That is not a problem - in fact most of them prove precisely that: that code does not panic
So they're doing what SPARK does automatically - proving absence of runtime errors. But SPARK guarantees this for the language; Rust tools must verify it per codebase.
> you can follow what rustc does (and in fact it's easier for these tools, since they integrate with rustc)
"Follow the compiler's behavior" is not formal semantics. Formal verification requires mathematical definitions independent of implementation. This is why SPARK has an ISO standard with formal semantics, not "watch what GNAT does."
> Rust is very well amenable to formal verification [...] Perhaps even more amenable than Ada
Then why doesn't it have DO-178C, EN 50128, or ISO 26262 certification toolchains after a decade? SPARK achieved this because verification was the design goal. Rust's design goals were different - and valid - but they weren't formal verifiability.
> SPARK also does not support major parts of Ada
Correct - by design. SPARK excludes features incompatible with efficient verification (unrestricted pointers, exceptions in contracts, controlled types). This is intentional subsetting for verification. Claiming Rust tools "support more of Rust" ignores that some Rust features are fundamentally unverifiable without massive annotation burden.
The core issue: you're comparing research tools that can verify some properties of some Rust programs with significant manual effort, to a language designed so that conforming programs are automatically verifiable with mathematical guarantees. These are different categories of assurance.
> Lifetimes affect drop order and program semantics. These aren't erased - they're compiled into the code being verified.
mrustc is existential proof that this statement is wrong. mrustc is a bootstrap compiler for Rust written in C++ that notably ignores lifetimes since it assumes that the code it is compiling (old rustc versions) will have correct lifetimes. And despite ignoring lifetimes, its output is bit-for-bit identical to what rustc produces, so clearly those lifetimes can't have been that important :P
(More seriously, lifetimes affect program correctness, but do not affect program semantics, let alone drop order. They are definitely erased as well. Not sure how you got this so wrong).
Conversely, the Rustonomicon has this page that says that Rust just uses C++20's memory model for its atomics. Yet I do not believe that memory model is defined anywhere in FLS.
It is certainly incomplete. Virtually all specifications for programming languages are. It is good enough for safety critical work, which is a higher bar than most.
The idea is that you adopt them and improve them over time. It is more complete than the reference, which is the previous project in this area.
Rust does have a qualified compiler: Ferrocene. It supports ISO 26262 (ASIL D), IEC 61508 (SIL 4) and IEC 62304 currently, with more on the way, including plans for DO-178 I believe. It’s being driven by actual adoption, so they’ve started in automotive and will move to other industries eventually.
> I think it's important to note, the certification is only for a subset of the run-time, which means some language features will not be available. Also, the certification is only to SIL-2 level, so any projects requiring SIL-3 or 4 will still not be able to use the Ferrocine compiler!
> It matters fundamentally. "Works well" for research projects or limited AWS components is not equivalent to DO-178C Level A certification where mathematical proof is required
As said in a sibling comment, certification to Rust starts to appear. Rust is young and its usage in regulated industries is just barely beginning. Ada and SPARK are old and mature. It's not a fair comparison - but that doesn't mean Rust couldn't get there.
> > Few of the tools can verify unsafe code is free of UB
>
> With heavy annotation burden, for specific patterns
> > some of the tools do support interior mutability (with extra annotations I believe)
>
> Exactly - manual annotation burden.
SPARK does not support the equivalent (shared mutable pointers) at all. Rust verifies do with a heavy annotation burden. What's better?
> Trait resolution determines which code executes (monomorphization). Lifetimes affect drop order and program semantics. These aren't erased - they're compiled into the code being verified. SPARK's type system is verifiable; Rust's requires the verifier to trust the compiler's type checker.
Has the Ada compiler formally verified? No. So you're trusting the Ada type checker just as well.
The Ada specification was, if I understand correctly, formally defined. But there are efforts to do that to Rust as well (MiniRust, a-mir-formality, and in the past RustBelt).
> The macro logic is unverified Rust code executing at compile time. A bug in the macro generates incorrect code that may pass verification. SPARK has no equivalent escape hatch.
And if you have a script that generates some boilerplate code into your Ada project, is the script logic verified? The outputted code is, and that's what important. Even with full formal verification, proving that the program fulfills its goals, you don't need to verify helpers like this - only the code they generate. If it works, then even if the script is buggy, who cares.
> So they're doing what SPARK does automatically - proving absence of runtime errors
Exactly - that's the point, to prove free of runtime errors.
I'm not sure what you mean by "SPARK guarantees this for the language; Rust tools must verify it per codebase" - does SPARK not need to verify separate codebases separately? Does it somehow work magically for all of your codebases at once?
It's clear at this point that neither of us will get convinced, and I think I said everything I had about this already.
> Certification to Rust starts to appear. Rust is young and its usage in regulated industries is just barely beginning.
Ferrocene has ISO 26262 qualification for the compiler, not verification tools. That's compiler correctness, not formal verification of programs. SPARK's GNATprove is qualified for proving program properties - fundamentally different.
> SPARK does not support the equivalent (shared mutable pointers) at all. Rust verifies do with a heavy annotation burden. What's better?
SPARK supports controlled aliasing through ownership aspects and borrow/observ annotations - but these are designed into the verification framework, not bolted on. The key difference: SPARK's aliasing controls are part of the formal semantics and verified by GNATprove automatically. Rust's unsafe shared mutability requires external verification tools with manual proof burden. SPARK deliberately restricts aliasing patterns to those that remain efficiently verifiable: it's not "can't do it" it's "only allow patterns we can verify".
> Has the Ada compiler formally verified? No. So you're trusting the Ada type checker just as well.
Qualified compilers (GNAT Pro) undergo qualification per DO-178C/ED-12C. The difference: SPARK's semantics are formally defined independent of the compiler. Rust verification tools must trust rustc's implementation because Rust has no formal specification. When rustc changes behavior (happens frequently), verification tools break. SPARK's specification is stable.
> And if you have a script that generates some boilerplate code into your Ada project, is the script logic verified?
External build scripts are different from language features. Procedural macros are part of Rust's language definition and can access compiler internals. If you use external code generation with SPARK, you're explicitly stepping outside the language's guarantees - and safety standards require justification. Rust embeds this escape hatch in the language itself.
> I'm not sure what you mean by "SPARK guarantees this for the language; Rust tools must verify it per codebase"
SPARK: If your code compiles in the SPARK subset, overflow/division-by-zero/array bounds are automatically proven impossible by language rules. You can add contracts for functional correctness.
Rust tools: You must annotate code, write invariants, and run verification per program. The language provides memory safety via the type system, but not freedom from arithmetic errors or functional correctness. These must be proven per codebase with tools.
The distinction: language-level guarantees vs. per-program verification effort.
> It's clear at this point that neither of us will get convinced
Fair enough, but the fundamental difference is whether verification is a language property or a tool property. Both approaches have merit for different use cases.
> 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.
I would like to add a few clarifications that I may not have mentioned in my other reply.
You are correct that Rust's ownership/borrow model simplifies many verification tasks: the borrow checker removes a great deal of aliasing complexity, and that has enabled substantial research and tool development (RustBelt, Prusti, Verus, Creusot, Aeneas, etc.). That point is valid.
However, it is misleading to claim Rust is plainly more amenable to formal verification than Ada. SPARK is a deliberately restricted subset of Ada designed from the ground up for static proof: it ships with an integrated, industrial-strength toolchain (GNATprove) and established workflows for proving AoRTE and other certification-relevant properties. Rust's type system gives excellent leverage for many proofs, but SPARK/Ada today provide a more mature, production-proven path for whole-program safety and certification. Which is preferable therefore depends on what you need to verify - research or selected components versus whole-program, auditable certification evidence.
SPARK/Ada is used in many mission-critical industries (avionics, rail, space, nuclear, medical devices) for a reason: the language subset, toolchain, and development practices are engineered to produce certifiable evidence and demonstrable assurance cases.
Rust brings superior language ergonomics and strong compile-time aliasing guarantees, but it faces structural barriers that make SPARK's level of formal verification fundamentally unreachable. These are not matters of tooling immaturity, but of language design and semantics:
- Rust allows pervasive unsafe code, which escapes the borrow checker's guarantees. Every unsafe block must be modelled and verified separately, defeating whole-program reasoning. SPARK forbids such unchecked escape hatches within the verified subset.
- Rust's semantics include undefined behavior and panics, which cannot be statically ruled out by the compiler. SPARK, by contrast, can prove statically that such run-time errors are impossible.
- Rust's rich features (lifetimes, traits, interior mutability, macros, async, etc.) greatly complicate formal semantics. SPARK deliberately restricts such constructs to preserve provable determinism.
- Rust lacks a single, formally specified, stable verification subset. SPARK's subset is precisely defined and stable, with a formal semantics that proofs can rely on across versions.
- Rust's verification ecosystem is fragmented and research-oriented (Prusti, Verus, Creusot, RustBelt), whereas SPARK's GNATprove toolchain is unified, production-proven, and already qualified for use in DO-178C, EN-50128, and IEC-61508 workflows.
- Certification for Rust toolchains (qualified compilers, MC/DC coverage, auditable artifacts) is only beginning to emerge; SPARK/Ada toolchains have delivered such evidence for decades.
In short, Rust's design - allowing unsafe code, undefined behavior, and a complex evolving feature set - makes SPARK-level whole-program, certifiable formal verification structurally impossible. SPARK is not merely a verifier bolted onto Ada: it is a rigorously defined, verifiable subset with an integrated proof toolchain and an industrial certification pedigree that Rust simply cannot replicate within its current design philosophy.
If your objective is immediately auditable, whole-program AoRTE proofs accepted by certifying authorities today, SPARK is the practical choice.
I hope this sheds some light on why SPARK's verification model remains unique and why Rust, by design, cannot fully replicate it.
I'm a long term Ada developer who has also used SPARK in the past (SPARK was/is great). I've been looking into Rust out of curiosity. I will dispute that "Rust brings superior language ergonomics". To me, Rust's syntax is unnecessarily cryptic and ugly. In particular, Ada's syntax and semantics for exact, low-level data representation and hardware interfacing is much more ergonomic than Rust's.
> However, it is misleading to claim Rust is plainly more amenable to formal verification than Ada. SPARK is a deliberately restricted subset of Ada designed from the ground up for static proof
Hold on, there's some slight of hand going on here. The person you're responding to was comparing Rust and Ada, both languages not intentionally originally designed for formal verification. You, on the other hand, are comparing Rust, a language that was not originally designed for formal verification, and Ada/SPARK, which is designed for formal verification by definition. That's not a proper comparison for what GP was claiming!
A correct comparison would probably involve identifying "a deliberately restricted subset of [Rust] designed from the ground up for static proof" and comparing its capabilities against what SPARK offers as well as (somehow) comparing how "easy"/"hard" producing that subset was compared to defining SPARK from Ada.
> Hold on, there's some slight of hand going on here. [...] A correct comparison would probably involve identifying "a deliberately restricted subset of [Rust] designed from the ground up for static proof" and comparing its capabilities against what SPARK offers as well [...]
I understand your point, comparing a hypothetical Rust subset specifically designed for formal verification to SPARK is a fairer apples-to-apples framing. My earlier point was contrasting full Rust and SPARK to highlight that SPARK’s integrated, industrial-strength proof model is unmatched in terms of whole-program, auditable guarantees.
Rust's ownership and borrow system indeed provides leverage for verification tools, and subsets verified with Prusti, Creusot, Kani, or Verus can achieve interesting results. However, the structural design of Rust (unsafe blocks, interior mutability, macros, undefined behavior, evolving semantics) means that these subsets require careful annotations and external verification; they do not automatically produce the same machine-checked, whole-program, caller-verified obligations that SPARK/Ada guarantees today.
TL;DR: Yes, restricted Rust subsets can be verified and are amenable to research-level proofs, but SPARK provides industrial-strength, auditable, whole-program proofs by design, which Rust cannot replicate without extensive external tooling. The comparison isn't about Rust being "bad" at verification - it's about the integrated guarantees SPARK provides that Rust's language design fundamentally does not.
The takeaway for the readers here is that SPARK's off-proof body is like a verified contract with an opaque implementation; Rust's unsafe body is like a permission slip that gives the programmer access without automatic enforcement.