linearity & the ergonomics of proof-mode maps #62
tjhance
started this conversation in
Language design
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Setting up the problem
Experience in LinearDafny using
glinear mapdemonstrated a need for much more ergonomic ways to manipulate glinear map objects. ('proof' objects in Verus parlance)Let's start with a few motivating examples (let
Pbe a proof type object). All of these were the sorts of things to come up in our LinearDafny work, but they were very cumbersome to handle.map<int, P>with domain0..n, but we want to 'shift' all the elements by 1, e.g., get a map with domain1..n+1, but with the same elements in it.a: map<K, P>andb: map<K, P>with disjoint domains and want to take the union.map<K, P>and a (proof-mode) function P -> Q, we want to apply that function to all values and obtain a mapmap<K, Q>.In a "spec" world, we would accomplish such operations using simple map comprehensions.
Doing this in the "proof" world, i.e., where
mis a proof variable, suddenly raises questions. If the expression in the "value" position of the mapcomp is proof mode, how do we ensuremis used safely?Example 3 poses additional challenges, where a single variable would apparently be consumed by multiple different statements:
The user wants to write code that looks like the above while having Verus check that it's safe.
Ideally, failure cases would have nice error messages:
Goals
mcan be used more than once, with Verus checking by SMT that no index is consumed more than once.In effect, it would be as if the linearity-checker applied to each element
u.get(i)individually.Proposed implementation
There are a lot of components to the "implementation" of the idea, including the user interaction (how do they activate the feature), how a function like
m.get(i)would even be specified, how does VC gen work, and how do we get Rust's borrow checker to do the right thing.(Since this is all proof (i.e., ghost) code, we don't have to worry about executable code for any of this.)
First, the user would write something like this, using a new Verus primitive called "scatter":
Naturally,
m.getwould need to be some trusted function with some specialverifierattribute so that Verus knows that it's special.m.getwould also need to takemas a normal immutable borrow type, to make sure that the borrow checker doesn't complain. Thescatterline ensures thatmis actually available at that point, and then Verus would have to check thatmis not used at any point afterwards (in non-spec code) except by callingm.get(...)and only in ways that Verus actually knows how to handle.VCs are not too bad, provided we bail out if we encounter loops or anything complex. As we walk through, we collect 'sets' of indices that have been used (these could be explicit set objects, or sets represented implicitly by predicates), and require them to all be disjoint. Map comps also need to check that distinct "iterations" of the map comp don't overlap.
Beta Was this translation helpful? Give feedback.
All reactions