Earlier quoted context omitted.
> The answer isn't generics, Coq, or some fancy type system nobody will understand or use properly. The solution is a formal semantics for unsafe Rust, so that programmers can prove that their unsafe Rust code is safe to use by whatever means they prefer. (Mine would be by hand.) --- Reply to dmix: A formal semantics doesn't have to be particularly fancy, although in Rust's case, it will in most likelihood not be str…
In the meantime, Rust's support for automated tests is great.
Announcing Rust 1.20
251–260 of 277 posts
Re: Announcing Rust 1.20
#252Earlier quoted context omitted.
Is that a fancy system that nobody will understand or use properly? (Honest question)
Probably. I used to do formal proof of correctness work and headed a project to build a verifier.[1] That stuff is very hard. The partially initialized array thing is an issue of expressive power. You can't talk about that in Rust yet. This is a classic issue. The three big headaches in C around memory safety are "how big is it", "who owns it", and "who locks it". The language lacks the syntax to even talk about thos…
Sure, but it doesn't have to be a programming language.
> Preferably the one you're programming in.
Who says so? Programming languages (justifiedly) optimize for the ability to express computation, not the ability to express proof.
In spite of Curry-Howard, there exist important differences between proofs and programs:
(0) Proofs are primarily for humans to understand. Programs are primarily for computers to execute.
(1) Proofs are sometimes allowed to be non-constructive. Even when they aren't, an easily understandable proof beats one that corresponds to an efficient program.
(2) Allowing non-terminating programs is actually a good thing: it allows for proofs of correctness that use techniques not anticipated by the language designer. OTOH, if your logic lets you prove a contradiction, it's the end of the world (for your logic).
Re: Announcing Rust 1.20
#253Earlier quoted context omitted.
Probably. I used to do formal proof of correctness work and headed a project to build a verifier.[1] That stuff is very hard. The partially initialized array thing is an issue of expressive power. You can't talk about that in Rust yet. This is a classic issue. The three big headaches in C around memory safety are "how big is it", "who owns it", and "who locks it". The language lacks the syntax to even talk about thos…
> Before you can even consider verifying something, you have to be able to talk about it in some formal language. Sure, but it doesn't have to be a programming language. > Preferably the one you're programming in. Who says so? Programming languages (justifiedly) optimize for the ability to express computation, not the ability to express proof. In spite of Curry-Howard, there exist important differences between proofs…
defined(arrayname, lowbound, highbound)
is helpful. That goes in assert statements. It's run-time checkable, if you have some extra state in the form of "is defined" flags. But you'd rather prove it once so the run-time checks are unnecessary. With a few simple theorems, such as defined(a,i,j) and defined(a[j+1]) implies defined(a,i,j+1)
and a simple automated prover, you can deal with most of the issues around partially defined arrays.Think of proof support as being an extension to optimization of assertions. Most assertions in programs can be proven easily with an automated prover. Users need never see those proofs. Some will be hard and require more proof support.
Re: Announcing Rust 1.20
#254Earlier quoted context omitted.
> Before you can even consider verifying something, you have to be able to talk about it in some formal language. Sure, but it doesn't have to be a programming language. > Preferably the one you're programming in. Who says so? Programming languages (justifiedly) optimize for the ability to express computation, not the ability to express proof. In spite of Curry-Howard, there exist important differences between proofs…
Proofs and specifications are different things. Users need to be able to read specifications. For example, if you want to talk about definedness for arrays, a predicate defined(arrayname, lowbound, highbound) is helpful. That goes in assert statements. It's run-time checkable, if you have some extra state in the form of "is defined" flags. But you'd rather prove it once so the run-time checks are unnecessary. With a…
And runtime checks help because...?
> But you'd rather prove it once so the run-time checks are unnecessary.
No. I'd rather prove it to rule out the program being wrong. The runtime check is totally besides the point.
> With a few simple theorems, such as (...)
I think you mean “proposition”. It's not a theorem until it has been proven.
> Think of proof support as being an extension to optimization of assertions.
I never use runtime-checked assertions, so I never need to optimize them away.
> Users need never see those proofs.
But, you see, I want to see the proofs. How am I supposed to maintain a program I don't understand?
Re: Announcing Rust 1.20
#255Re: Announcing Rust 1.20
#256Earlier quoted context omitted.
Proofs and specifications are different things. Users need to be able to read specifications. For example, if you want to talk about definedness for arrays, a predicate defined(arrayname, lowbound, highbound) is helpful. That goes in assert statements. It's run-time checkable, if you have some extra state in the form of "is defined" flags. But you'd rather prove it once so the run-time checks are unnecessary. With a…
> That goes in assert statements. It's run-time checkable And runtime checks help because...? > But you'd rather prove it once so the run-time checks are unnecessary. No. I'd rather prove it to rule out the program being wrong. The runtime check is totally besides the point. > With a few simple theorems, such as (...) I think you mean “proposition”. It's not a theorem until it has been proven. > Think of proof suppor…
defined(a,i,j) and defined(a[j+1]) implies defined(a,i,j+1)
is a theorem. It looks like this in Boyer-Moore theory: (PROVE-LEMMA arraytrue-extend-upward-rule (REWRITE)
(IMPLIES (AND (EQUAL (arraytrue A I J) T)
(EQUAL (alltrue (selecta A (ADD1 J))) T))
(EQUAL (arraytrue A I (ADD1 J)) T)))
Name the conjecture *1.
We will try to prove it by induction. There are three plausible
inductions. They merge into two likely candidate inductions. However, only
one is unflawed. We will induct according to the following scheme: (AND (IMPLIES (LESSP J I) (p A I J))
(IMPLIES (AND (NOT (LESSP J I))
(p A (ADD1 I) J))
(p A I J))).
Linear arithmetic informs us that the measure (DIFFERENCE (ADD1 J) I)
decreases according to the well-founded relation LESSP in each induction step
of the scheme. The above induction scheme leads to three new formulas: Case 3. (IMPLIES (AND (LESSP J I)
(ARRAYTRUE A I J)
(EQUAL (ALLTRUE (SELECTA A (ADD1 J)))
T))
(ARRAYTRUE A I (ADD1 J))),
which simplifies, rewriting with ARRAYTRUE-VOID-RULE and SUB1-ADD1, and
opening up ARRAYTRUE and LESSP, to the following eight new conjectures:...
That finishes the proof of *1. Q.E.D.
"arraytrue" is defined recursively:
(DEFN arraytrue (A I J)
(IF (LESSP J I) T -- the null case is true
(AND (EQUAL (alltrue (selecta A I)) T) -- next element is true
(arraytrue A (ADD1 I) J))) -- and rest of array is alltrue
And yes, there's a machine proof that this terminates.Re: Announcing Rust 1.20
#257Earlier quoted context omitted.
If T is a reference type, doesn't Rust do an optimization where >> is implemented by using null pointers?
Yes, but that doesn't help when T is not a reference type.
Re: Announcing Rust 1.20
#258Re: Announcing Rust 1.20
#259Earlier quoted context omitted.
> That goes in assert statements. It's run-time checkable And runtime checks help because...? > But you'd rather prove it once so the run-time checks are unnecessary. No. I'd rather prove it to rule out the program being wrong. The runtime check is totally besides the point. > With a few simple theorems, such as (...) I think you mean “proposition”. It's not a theorem until it has been proven. > Think of proof suppor…
defined(a,i,j) and defined(a[j+1]) implies defined(a,i,j+1) is a theorem. It looks like this in Boyer-Moore theory: (PROVE-LEMMA arraytrue-extend-upward-rule (REWRITE) (IMPLIES (AND (EQUAL (arraytrue A I J) T) (EQUAL (alltrue (selecta A (ADD1 J))) T)) (EQUAL (arraytrue A I (ADD1 J)) T))) Name the conjecture *1. We will try to prove it by induction. There are three plausible inductions. They merge into two likely cand…
Sure. My point is just that a theorem is a proposition equipped with a proof. If the user just enters a proposition into the system, then they aren't entering a theorem. They're entering a proposition that the system can turn into a theorem.
Re: Announcing Rust 1.20
#260Earlier quoted context omitted.
> Before you can even consider verifying something, you have to be able to talk about it in some formal language. Preferably the one you're programming in. I'd personally be excited too see a modern language implement this. I saw the potential for this type of verification in my (hobbyist) dabbling with Haskell. Which subsequently inspired me to relearn math, including a great book on proofs recommended on HN which r…
We built verification into the language in Pascal-F, 30 years ago.[1] That's rarely been done since in real-world imperative languages. It's much easier to keep the verification statements correct if they're in the same file and the same language as the program. But Pascal was a small language. Getting this into today's bloated languages is tough. Back then, we looked at Ada, sized the project, and realized it was co…
You sound like a very interesting person to buy a beer for, assuming you'd be patient enough for my questions :p, I'll check out the paper instead when I get the time.
> It's much easier to keep the verification statements correct if they're in the same file and the same language as the program.
Agreed. Hell even using Dialyzer in Erlang/Elixir for static type checking (which I make the effort to do often in my daily side project hacking) just doesn't feel right, even though it's still appended to function definitions in the original files.
This is one of those things that need to be a core part of the language design IMO, not just a tool built on top - 3rd party, by the core team, or otherwise. But, that said, tooling can still be superior compared to the complete absence of it.
Maybe you can answer a question I've been struggling to find the answer to via Google. Do you know the name of this popular older language/tool used by to Microsoft for doing formal verification? For c/c++ style code. It sounds like tkk or something similar? I can't seem to find it.
My other question I'd ask is if you think the testing analogy applies here as I mentioned in my comment above or do you see it as an entirely different paradigm? Basically changing how you program rather than adding on a tool/skillset.