| 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. [1] See https://news.ycombinator.com/item?id=45508022 as well. |
I'm not going to respond directly to most of what you say because most of those responses would basically be "I said 'analogous' for a reason", but there are a few additional notes:
> 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.
The proof bit is missing the point. What I was trying to get at is that `unsafe` in the function body, like `SPARK_Mode(Off)`, does not affect the part of the function that interacts with external code. `SPARK_Mode(Off)` does not affect how the prover uses the spec, and `unsafe` does not affect how rustc uses the function signature. The use of `SPARK_Mode(Off)`/`unsafe` is contained entirely within the function body; i.e., they is "opaque".
> 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.
I'm not sure this is entirely accurate. Again, `unsafe` in a function body does not affect how the compiler uses the function signature in the rest of the program; in this way, `unsafe` works similarly to `SPARK_Mode(Off)` in that "the [function signature] is verified for all callers; the body is hidden but the interface guarantees are enforced". Obviously SPARK can guarantee more than Rust, but I'm not trying to argue otherwise.
> 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.
This is a bad wording on my part; "The Rust equivalent" should have been "The analogous Rust construct". Again, I'm trying to show that `unsafe` is not that different from `SPARK_Mode(Off)` for analogous language constructs.