4 Dafny Collections
4.1 Sequences
Sequences are a built-in Dafny type that represent ordered collections of elements. They can model data structures such as lists, queues, and stacks. Sequences are immutable value types: once created, their contents cannot be modified. In this respect, they resemble strings in languages like Java or Python, but unlike strings, Dafny sequences can contain elements of any type, not just characters.
seq<int>
Dafny
predicate sorted(s: seq<int>){ forall i,j :: 0 <= i < j < |s| ==> s[i] <= s[j] }
A particularly powerful feature of sequences is that they can be freely created and manipulated in specifications and functions.
For example, sortedness can be expressed recursively: if the first element is less than or equal to the rest, and the remainder of the sequence is sorted, then the entire sequence is sorted.
OCaml REPL
int>){ predicate sorted2(s: seq<0 < |s| ==> (forall i :: 0 < i < |s| ==> s[0] <= s[i]) && 1..]) sorted2(s[ }
The notation s[1..] slices the sequence, creating a new sequence from the first element to the end. Sequences are immutable, so this does not modify s; it produces a new sequence with the same order of elements, minus the first.
The slice notation is written as:
s[i..j]0 <= i <= j <= |s| where
Dafny enforces these index bounds. The slice will contain exactly j - i elements, starting with s[i] and continuing sequentially. The element at index j is excluded, following the same half-open interval convention as regular indexing.
Dafny
method m() { var s := [1, 2, 3]; }
type
[] // the empty sequence, which can be a sequence of any true] // a singleton sequence of type seq<bool> [
Dafny
method m(){ var s := [1, 2, 3, 4, 5]; //access the last elementassert s[|s|-1] == 5; as a singleton //slice just the last element, assert s[|s|-1..|s|] == [5]; // everything but the firstassert s[1..] == [2, 3, 4, 5]; // everything but the lastassert s[..|s|-1] == [1, 2, 3, 4]; // the whole sequenceassert s == s[0..] == s[..|s|] == s[0..|s|]; }
Dafny
method m(){ var s := [1, 2, 3, 4, 5]; assert [1,2,3] == [1] + [2,3]; assert s == s + []; assert forall i :: 0 <= i <= |s| ==> s == s[..i] + s[i..]; }
Dafny
method m(){ assert forall a: seq<int>, b: seq<int>, c: seq<int> :: (a + b) + c == a + (b + c); }
however, the Z3 theorem prover will not recognize this on its own unless explicitly guided by an assertion (see Lemmas/Induction for details on why this is necessary).
Dafny
method m(){ var s := [1, 2, 3, 4, 5]; assert 5 in s; assert 0 !in s; }
Dafny
method m(){ var s := [2,3,1,0]; assert forall i :: i in s ==> 0 <= i < |s|; }
This is a property of each individual element of the sequence. If we wanted to relate multiple elements to each other, we would need to quantify over the indices, as in the first example.
Dafny
method m(){ var s := [1,2,3,4]; assert s[2 := 6] == [1,2,6,4]; }
Dafny
function update(s: seq<int>, i: int, v: int): seq<int> requires 0 <= i < |s| ensures update(s, i, v) == s[i := v] {1..] s[..i] + [v] + s[i+ // This works by concatenating everything that doesn'twith the singleton of the new value. // change }
Dafny
method m() {var a := new int[][42, 43, 44]; // 3 element array of ints 0], a[1], a[2] := 0, 3, -1; a[var s := a[..]; assert s == [0, 3, -1]; }
Dafny
method m(){ var a := new int[][42, 43, 44]; // 3 element array of ints 0], a[1], a[2] := 0, 3, -1; a[assert a[1..] == [3, -1]; assert a[..1] == [0]; assert a[1..2] == [3]; }
Because sequences support in and !in, this operation gives us an easy way to express the “element not in array” property, turning:
forall k :: 0 <= k < a.Length ==> elem != a[k]
into:
in a[..] elem !
Further, bounds are easily included:
forall k :: 0 <= k < i ==> elem != a[k]
is the same as
in a[..i] elem !
4.2 Sequence APIs
Creating Sequences:
Empty sequence
var s := [];
With elements
Comprehension sequence (ghost-only)var s := [1, 2, 3];
ghost var sq := seq(i | 0 <= i < 5, i * i); // [0,1,4,9,16]
Length
var n := |s|;
Indexing (requires proof 0 <= i < |s|)
var x := s[i];
Concatenation
var s3 := s1 + s2;
- Slicing
Slice with start and end indices (0 <= i <= j <= |s|)
var sub := s[i..j];
Prefix (from start up to index i)
var pre := s[..i];
Suffix (from index i to end)
var suf := s[i..];
Updating Elements
Since sequences are immutable, updating creates a new sequence:
var s2 := s[i := v];
Membership
Check if element exists in sequence
assert 2 in [1,2,3];
Quantifiers over Sequences
You can quantify over sequence indices or elements:
Over indices
assert forall i :: 0 <= i < |s| ==> s[i] >= 0;
Over elements
assert forall x :: x in s ==> x >= 0;
Properties
Equality
assert [1,2] == [1,2];
Prefix test
assert s1 <= s2; // s1 is a prefix of s2
- Examples
Dafny
method testSeq{ var s := [1, 2, 3, 4]; assert |s| == 4; assert s[0] == 1; assert s[3] == 4; var sub := s[1..3]; // [2,3] assert sub == [2,3]; var pre := s[..2]; // [1,2] var suf := s[2..]; // [3,4] var s2 := s[2 := 99]; // [1,2,99,4] assert forall i :: 0 <= i < |s| ==> s[i] > 0; }
4.3 Set
set<int>
Dafny
method m(){ var s1 := {}; // the empty set var s2 := {1, 2, 3}; // set contains exactly 1, 2, and 3 assert s2 == {1,1,2,3,3,3,3}; // same as before assert s1 != s2; // sets with different elements are different var s3, s4 := {1,2}, {1,4}; }
The set formed by the display is the expected set, containing just the elements specified. For the case of the empty set, the type of the variable s1 above is not fully known from its initialization. However, later s1 is compared to s2, so it must have the same type as s2, namely set‘. However, in the example below, there is no such use of ‘s1‘, so the type of ‘s1‘ must be specifically stated in its declaration.
Dafny
method m(){ var s1: set<int> := {}; var s2 := {1, 2, 3}; var s3, s4 := {1,2}, {1,4}; assert s2 + s4 == {1,2,3,4}; // set union assert s2 * s3 == {1,2} && s2 * s4 == {1}; // set intersection assert s2 - s3 == {3}; // set difference }
Dafny
method m1(){ // subsetassert {1} <= {1, 2} && {1, 2} <= {1, 2}; or proper, subset // strict, assert {} < {1, 2} && !({1} < {1}); not subsets // assert !({1, 2} <= {1, 4}) && !({1, 4} <= {1}); and non-equality // equality assert {1, 2} == {1, 2} && {1, 3} != {1, 2}; }
Dafny
method m2(){ assert 5 in {1,3,4,5}; assert 1 in {1,3,4,5}; assert 2 !in {1,3,4,5}; assert forall x: int :: x !in {}; }
Sets are used in several annotations, including reads and modifies clauses. In this case, they can be sets of a specific object type (like Nodes in a linked list), or they can be sets of the generic reference type object. Despite its name, this can point to any object or array. This is useful to bundle up all of the locations that a function or method might read or write when they can be different types.
When used in a decreases clause, sets are ordered by proper subset. To use sets in decreases clauses, the successive values must be “related” in some sense, which usually implies that they are recursively calculated, or similar.
You can test if a set is empty by comparing it to the empty set (s == {} is true if and only if s has no elements.)
A useful way to create sets is using a set comprehension. This defines a new set by including f(x) in the set for all x of type T that satisfy p(x):
set x: T | p(x) :: f(x)
This defines a set in a manner reminiscent of a universal quantifier (forall). As with quantifiers, the type can usually be inferred. In contrast to quantifiers, the bar syntax (|) is required to separate the predicate (p) from the bound variable (x). The type of the elements of the resulting set is the type of the return value of f(x). The values in the constructed set are the return values of f(x): x itself acts only as a bridge between the predicate p and the function f. It usually has the same type as the resulting set, but it does not need to. As an example:
method m(){
assert (set x | x in {0,1,2} :: x + 0) == {0,1,2};
}
method m()
{assert (set x | x in {0,1,2,3,4,5} && x < 3) == {0,1,2};
}
method m(){
assert {0*1, 1*1, 2*1} == {0,1,2};
// include this assertion as a lemma to prove the next line
// assert (set x | x in {0,1,2} :: x * 1) == {0,1,2};
}
To help Dafny prove this assertion, you can precede it with the assertion assert {0*1, 1*1, 2*1} == {0,1,2};. This lets Dafny figure out both assertions.
method m(){
assert {0*1, 1*1, 2*1} == {0,1,2};
include this assertion as a lemma to prove the next line
// assert (set x | x in {0,1,2} :: x * 1) == {0,1,2};
}
Without care, a set comprehension could prescribe an infinite number of elements, but a set is only allowed to have a finite number of elements. For example, if you tried writing set x | x % 2 == 0 as the set of all even integers, then you would get an error. (If you really want an infinite set, use the iset type instead. For example, iset x | x % 2 == 0 is legal in ghost contexts.) To ensure that set comprehensions give rise to finite sets, Dafny employs some heuristics. When creating sets of integers, this can be done by bounding the integers in at least one conjunct of the predicate (something like 0 <= x < n). Requiring a bound variable to be in an existing set also works, as in x in {0,1,2} from above.
4.4 Set APIs
Creating Sets Empty set
Set with elementsvar s := set[];
var s := set[1, 2, 3];
Comprehension set (ghost-only)
ghost var evens := set x | 0 <= x < 10 && x % 2 == 0;
Membership
Check if element is in set
if x in s { ... }
Negated membership
if x !in s { ... }
Set Operations
Union
var u := s1 + s2;
Intersection
var i := s1 * s2;
Difference
var d := s1 - s2;
Subset Relations Subset
assert s1 <= s2; // s1 is a subset of s2
Proper subset
assert s1 < s2; // s1 is a proper subset of s2
Superset
assert s1 >= s2;
Size and Cardinality
Cardinality
var n := |s|;
Quantifiers over Sets
You can quantify over elements in sets:
assert forall x :: x in s ==> x >= 0; assert exists x :: x in s && x % 2 == 0;
- Examples
Dafny
method testSet() { var s1 := set[1, 2, 3]; var s2 := set[3, 4, 5]; assert 1 in s1; assert 4 !in s1; var u := s1 + s2; assert u == set[1, 2, 3, 4, 5]; var i := s1 * s2; assert i == set[3]; var d := s1 - s2; assert d == set[1, 2]; assert |s1| == 3; assert s1 < u;}
4.5 Map
In Dafny, a map is an immutable key–value collection. Maps are mathematical objects and cannot be mutated directly. Instead, operations return new maps.
Creating Maps
Empty map
var m := map[];
With key–value pairs
var m := map[1 := "one", 2 := "two"];
Membership and Lookup Check if key exists
Lookup value (requires proof that k is in domain)if k in m { ... }
var v := m[k];
Domain and Keys
Domain (set of keys)
var dom := m.Keys; // returns a set of keys
Values are not directly retrievable as a set, but you can work with them by iterating over keys.
Adding / Updating Bindings
Since maps are immutable, updating creates a new map:
var m2 := m[k := v];
Removing Bindings
Remove one key
var m2 := m - k;
Remove multiple keys
var m3 := m - {k1, k2};
Union override (+):
Keys in the right map override keys in the left:
var m3 := m1 + m2;
Difference (-):
Remove a set of keys:
var m2 := m1 - {k1, k2};
Properties and Quantifiers
You can reason about maps using Dafny’s quantifiers:
assert forall k :: k in m ==> m[k] >= 0;
- Complete Example
Dafny
method Demo() { var m := map[1 := "a", 2 := "b"]; assert 1 in m; assert m[1] == "a"; var m2 := m[3 := "c"]; assert 3 in m2; var dom := m2.Keys; assert dom == {1,2,3}; var m3 := m2 - 2; assert 2 !in m3; var m4 := m3 + map[2 := "bb"]; assert m4[2] == "bb";}
4.6 Map APIs
expression |
| result type |
| description |
t in m |
| bool |
| map domain membership |
t !in m |
| bool |
| map domain non-membership |
|fm| |
| nat |
| map cardinality |
m[d] |
| U |
| map selection |
m[t := u] |
| map<T,U> |
| map update |
m.Keys |
| set<T> |
| the domain of m |
m.Values |
| set<U> |
| the range of m |
m.Items |
| set<(T,U)> |
| set of pairs (t,u) in m |
4.7 Arrays
In Dafny, an array is a mutable, fixed-length, heap-allocated sequence of elements. Arrays differ from sequences (seq<T>), which are immutable mathematical objects.
Here’s a rundown of Dafny arrays:
Declaring Arrays
new int[5] creates an array of length 5.// declarationvar a: array<int>; -5 array of ints, default-initialized to 0 // allocate lengthnew int[5]; a :=
All elements are automatically initialized with the default value of the type (0 for int, false for bool, null for references, etc.).
Accessing and Assigning
Indexing uses [].0] := 42; // assign a[var x := a[0]; // read
Dafny checks bounds statically when possible, otherwise requires proofs (0 <= i < a.Length).
Length
var n := a.Length; // length property
Multi-Dimensional Arrays
Multi-dimensional arrays are rectangular, not jagged.var b := new int[3, 4]; // 2D array with dimensions 3x4 2, 3] := 99; b[
You can check length along each dimension with .Length(i):
var rows := b.Length(0); var cols := b.Length(1);
Array Literals (since Dafny 4.0)
var c := new int[][2, 3]; // 2D array 0,0] := 1; c[
Unlike seq, arrays don’t have a simple literal syntax like [1,2,3]. You must allocate with new.
Converting Between seq and array
Since arrays are mutable and seq are immutable: Array to Seq:
ghost var s := a[..]; // turn array into a sequence snapshot
a[..] creates a sequence of the array’s contents. Seq to Array:Dafny
method SeqToArray(s: seq<int>) returns (a: array<int>) ensures a[..] == s {new int[|s|]; a := var i := 0; while i < |s| invariant 0 <= i <= |s| invariant forall j :: 0 <= j < i ==> a[j] == s[j] { a[i] := s[i];1; i := i + } }
- Arrays in Specifications You can use framing to indicate what arrays are modified:
Dafny
method Fill(a: array<int>, v: int) requires a != null modifies a ensures forall i :: 0 <= i < a.Length ==> a[i] == v {var i := 0; while i < a.Length invariant 0 <= i <= a.Length invariant forall j :: 0 <= j < i ==> a[j] == v { a[i] := v;1; i := i + } }
array<T> = mutable, runtime structure.
seq<T> = immutable, mathematical object for proofs.
4.8 Summary
Feature |
| Array (array<T>) |
| Sequence (seq<T>) |
| Set (set<T>) |
| Map (map<K,V>) |
Mutable? |
| ✅ |
| ❌ |
| ❌ |
| ❌ |
Runtime object? |
| ✅ |
| ❌ (math only) |
| ❌ |
| ❌ |
Ordered? |
| ✅ |
| ✅ |
| ❌ |
| ❌ |
Indexed? |
| ✅ |
| ✅ |
| ❌ |
| ✅ (By key) |
Duplicates? |
| ✅ |
| ✅ |
| ❌ |
| ❌(unique keys) |