On this page:
4.1 Sequences
4.2 Sequence APIs
4.3 Set
4.4 Set APIs
4.5 Map
4.6 Map APIs
4.7 Arrays
4.8 Summary
8.17

4 Dafny Collections🔗

    4.1 Sequences

    4.2 Sequence APIs

    4.3 Set

    4.4 Set APIs

    4.5 Map

    4.6 Map APIs

    4.7 Arrays

    4.8 Summary

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.

For a sequence of integers, the type of a sequence is written as:
seq<int>
For example, this function takes a sequence as a parameter:

Dafny

predicate sorted(s: seq<int>){
  forall i,j :: 0 <= i < j < |s| ==> s[i] <= s[j]
}

The length of a sequence is denoted |s|, as shown in the quantifier above. Individual elements are accessed using square brackets, just like arrays.

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

predicate sorted2(s: seq<int>){
  0 < |s| ==> (forall i :: 0 < i < |s| ==> s[0] <= s[i]) &&
               sorted2(s[1..])
}

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]
where 0 <= i <= j <= |s|

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.

Sequences can also be constructed from their elements, using display notation:

Dafny

method m() {
  var s := [1, 2, 3];
}

In this example, we define a sequence variable in imperative code containing the elements 1, 2, and 3. Type inference determines that the sequence consists of integers. Using this notation, we can also construct empty sequences and singleton sequences.

  [] // the empty sequence, which can be a sequence of any type
  [true] // a singleton sequence of type seq<bool>
Slice notation and display notation can be used to check properties of sequences:

Dafny

method m(){
  var s := [1, 2, 3, 4, 5];

  //access the last element
  assert s[|s|-1] == 5;

  //slice just the last element, as a singleton
  assert s[|s|-1..|s|] == [5];

  // everything but the first
  assert s[1..] == [2, 3, 4, 5];

  // everything but the last
  assert s[..|s|-1] == [1, 2, 3, 4];

  // the whole sequence
  assert s == s[0..] == s[..|s|] == s[0..|s|];
}

The most common sequence operations are accessing the first or last element, or taking all but the first or last element—patterns often used in recursive functions like sorted2 above. Beyond deconstruction through access or slicing, sequences can also be concatenated with the + operator.

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..];
}

The final assertion shows how concatenation relates to slicing. Since slicing is inclusive on one end and exclusive on the other, each element appears exactly once in the concatenation. Also note that concatenation is associative:

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).

Sequences also support the in and !in operators, which test for containment within a sequence:

Dafny

method m(){
  var s := [1, 2, 3, 4, 5];
  assert 5 in s;
  assert 0 !in s;
}

This also allows us an alternate means of quantifying over the elements of a sequence, when we don’t care about the index. For example, we can require that a sequence only contains elements which are indices into the sequence:

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.

Sometimes we would like to emulate the updatable nature of arrays using sequences. While we can’t change the original sequence, we can create a new sequence with the same elements everywhere except for the updated element:

Dafny

method m(){
  var s := [1,2,3,4];
  assert s[2 := 6] == [1,2,6,4];
}

Of course, the index i has to be an index into the array. This syntax is just a shortcut for an operation that can be done with regular slicing and access operations. Can you fill in the code below that does this?

Dafny

function update(s: seq<int>, i: int, v: int): seq<int>
  requires 0 <= i < |s|
  ensures update(s, i, v) == s[i := v]
{
  s[..i] + [v] + s[i+1..]
  // This works by concatenating everything that doesn't
  // change with the singleton of the new value.
}

You can also form a sequence from the elements of an array. This is done using the same “slice” notation as above:

Dafny

method m()
{
  var a := new int[][42, 43, 44]; // 3 element array of ints
  a[0], a[1], a[2] := 0, 3, -1;
  var s := a[..];
  assert s == [0, 3, -1];
}

To extract just part of the array, the bounds can be given just like in a regular slicing operation:

Dafny

method m(){
  var a := new int[][42, 43, 44]; // 3 element array of ints
  a[0], a[1], a[2] := 0, 3, -1;
  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:

elem !in a[..]

Further, bounds are easily included:

forall k :: 0 <= k < i ==> elem != a[k]

is the same as

elem !in a[..i]

4.2 Sequence APIs🔗
4.3 Set🔗

Sets of various types form one of the core tools of verification for Dafny. Sets represent an orderless collection of elements, without repetition. Like sequences, sets are immutable value types. This allows them to be used easily in annotations, without involving the heap, as a set cannot be modified once it has been created. A set has the type:
set<int>
for a set of integers, for example. In general, sets can be of almost any type, including objects. Concrete sets can be specified by using display notation:

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.

Above we also see that equality is defined for sets. Two sets are equal if they have exactly the same elements. New sets can be created from existing ones using the common set operations:

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
}

the union does not count repeated elements more than once. These operators will result in a finite set if both operands are finite, so they cannot generate an infinite set. Unlike the arithmetic operators, the set operators are always defined. In addition to set forming operators, there are comparison operators with their usual meanings:

Dafny

method m1(){
  // subset
  assert {1} <= {1, 2} && {1, 2} <= {1, 2};

  // strict, or proper, subset
  assert {} < {1, 2} && !({1} < {1});

  // not subsets
  assert !({1, 2} <= {1, 4}) && !({1, 4} <= {1});

  // equality and non-equality
  assert {1, 2} == {1, 2} && {1, 3} != {1, 2};
}

Sets, like sequences, support the in and !in operators, to test element membership. For example:

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};
}
If the function is the identity, then the expression can be written with a particularly nice form:

method m()
{
  assert (set x | x in {0,1,2,3,4,5} && x < 3) == {0,1,2};
}
To reason about general, non-identity functions in set comprehensions, Dafny may need some help. For example, the following is true, but Dafny cannot prove it:

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🔗
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.

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:

The key distinction between array and sequence are:
  • 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)