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
9.1

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.

Dafny

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🔗

  • Creating Sequences:

    Empty sequence

    var s := [];

    With elements

    var s := [1, 2, 3];
    Comprehension sequence (ghost-only)
    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🔗

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🔗

  • Creating Sets Empty set

    var s := set[];
    Set with elements
    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

    
    if k in m {
      ...
    }
    Lookup value (requires proof that k is in domain)
    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

    
     // declaration
    var a: array<int>;        
    
    // allocate length-5 array of ints, default-initialized to 0
    a := new int[5];           
    new int[5] creates an array of length 5.

    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

    
    a[0] := 42;                // assign
    var x := a[0];             // read
    Indexing uses [].

    Dafny checks bounds statically when possible, otherwise requires proofs (0 <= i < a.Length).

  • Length

    var n := a.Length;         // length property

  • Multi-Dimensional Arrays

    var b := new int[3, 4];    // 2D array with dimensions 3x4
    b[2, 3] := 99;
    Multi-dimensional arrays are rectangular, not jagged.

    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
    c[0,0] := 1;

    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
    {
      a := new int[|s|];
      var i := 0;
      while i < |s|
        invariant 0 <= i <= |s|
        invariant forall j :: 0 <= j < i ==> a[j] == s[j]
      {
        a[i] := s[i];
        i := i + 1;
      }
    }

  • 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;
        i := i + 1;
      }
    }

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)