3 Getting Started with Dafny
3.1 Methods and Specification
Dafny
method Triple(x: int) returns (r: int) { var y := 2 * x; r := x + y; }
Dafny
method Caller() { var t := Triple(18); //sets t to 54 }
We can add assrt statements to explicitly state the fact that it returns the value 3 * x.
Dafny
method Triple(x: int) returns (r: int) { var y := 2 * x; r := x + y;assert r == 3 * x; }
Dafny
method Triple2(x: int) returns (r: int) { var y := 2 * x; r := x + y;assert r == 10 * x; //assert r < 5; // Very surprising this one is accepted by Dafny! Do you see why? //assert false; // }
The first assertion may not always hold. However, if the first assertion does hold — where
10 * x ==> 3 * x == 10 * x ==> x == 0 ==> r < 5 r ==
3.2 Control Paths
Dafny
method Triple3(x: int) returns (r: int) { if x == 0 { 0; r := else { } var y := 2 * x; r := x + y; }assert r == 3 * x; }
3.3 Method Contracts
Dafny
method Caller() { var t := Triple(18); assert t < 100; }
We know from the implementation of Triple that t == 54. However, the assertion fails because the caller should not be able to see the implementation of Triple. This encapsulation allows Triple to change its implementation without affecting the caller.
A method can include specifications, which describe the behavior of the method. A specification essentially defines the contract between the caller and the method, outlining what the method is expected to do.
Dafny
method Triple4(x: int) returns (r: int) ensures r == 3 * x {var y := 2 * x; r := x + y; } method Caller2() { var t := Triple4(18); assert t < 100; }
Dafny
method Triple5(x: int) returns (r: int) requires x % 2 == 0 ensures r == 3 * x {var y := 2 * x; r := x + y; }
Dafny
method Triple5(x: int) returns (r: int) requires x % 2 == 0 ensures r == 3 * x {var y := x / 2; 6 * y; r := }
3.4 Underspecification
In the following example, the specification does not which number between 0 and n is returned.
We say "0 to n" to mean the values in the half-open interval [0, n), which includes 0, but does not include n.
Dafny
method Index(n: int) returns (i: int) requires 1 <= n ensures 0 <= i < n {2; i := n / }
Dafny
method Index2(n: int) returns (i: int) requires 1 <= n ensures 0 <= i < n {0; i := }
Dafny
method Doesn'tWork() { var x := Index(50); var y := Index(50); assert x == y; }
3.5 Multiple Postconditions
Dafny
method Min0(x: int, y: int) returns (m: int) ensures m <= x && m <= y method Min(x: int, y: int) returns (m: int) ensures m <= x && m <= y ensures m == x || m == y
3.6 Functions
Dafny
function Average(a: int, b: int): int { 2 (a + b) / } method Triple'(x: int) returns (r: int) ensures Average(r, 3 * x) == 3 * x { r := x + x + x; }
Dafny
function Average2(a: int, b: int): int requires 0 <= a && 0 <= b {2 (a + b) / } method Triple6(x: int) returns (r: int) ensures r == 3 * x {if 0 <= x { 2 * x, 4 * x); r := Average(else { } -2 * x, -4 * x); r := -Average( } }
3.7 Compiled vs Ghost
The precondition construct requires, the postcondition construct ensures, and assert are used to specify the behavior of a program. These are used by the Dafny verifier but are erased when the program is compiled. Any statement, variable, or declaration that is used only for specification is called a ghost. To declare a method, function, or variable as a ghost, simply add the keyword ghost in front. Compiled code should not rely on ghost constructs.
Dafny
method IllegalAssignment() returns (y: int) { /*ghost var x := 10; 2 * x; y := }*/ ghost method DoubleQuadruple(x: int) returns (a: int, b: int) ensures a == 2 * x && b == 4 * x {2 * x; a := 2 * a; b := } method Triple7(x: int) returns (r: int) ensures r == 3 * x {var y := 2 * x; r := x + y;ghost var a, b := DoubleQuadruple(x); assert a <= r <= b || b <= r <= a; }