The Dafny compilation process translates Dafny programs into target language source code.
For a simple Dafny-only program, the translation step converts a A.dfy
file into A.cs
;
the build step then produces a A.dll
, which can be used as a library or as an executable.
A multi-language program that combines Dafny and C# code “just” needs to be sure that the translated Dafny code fits in to the C# code. There are two aspects to this:
- ensuring that the names of entities in the translated Dafny code are usable from C#
- ensuring that the types are the same on both sides
Calling C# from Dafny
Calling a C# method from Dafny requires declaring a shim in Dafny that gives a name and types that can be referenced by Dafny code, while still having the same name as in the C# code.
For example, suppose we want to call a C# method Demo.p()
. In Demo.cs
we have
using System;
using System.IO;
public class Demo {
public static void p() { Console.WriteLine("Hi!"); }
}
In Demo1.dfy
we write,
module M {
method {:extern "Demo", "p"} p()
method Main() {
p();
}
}
Note that the declaration of p()
has no body; it is just a declaration because the actual implementation is in C#.
Its extern
attribute has two arguments: the fully-qualified class name and the method name.
Then the above can be built with
dafny build --target:cs Demo1.dfy Demo.cs
and then run as a dotnet program with
dotnet Demo1.dll
Or, in one build-and-run step,
dafny run Demo1.dfy --input Demo.cs
Calling non-static C# methods from Dafny
If the C# methods to be called are not static, then a receiver object must also be created and shared between C# and Dafny. The following example shows how to do this.
In a Demo1a.dfy
file:
module {:extern "demo"} M {
method {:extern "demo.Demo", "newDemo"} newDemo() returns (r: Demo )
class {:extern "Demo" } Demo {
method {:extern "demo.Demo", "p"} p()
}
}
module MM {
import opened M
method Main() {
var d: Demo := newDemo();
d.p();
}
}
In a Demo1x.cs
file
using System;
using System.IO;
namespace demo {
public class Demo {
public static Demo newDemo() { return new Demo(); }
public void p() { Console.WriteLine("Hi!"); }
}
}
In the C# file there are two methods: a static one that returns a new instance of the Demo
class, and a non-static method that does something useful.
In the .dfy
file we have a module M
that has an extern name corresponding
to the namespace of the C# class, a class declaration that associates the
Dafny class Demo
with the C# class demo.Demo
, an instance method
declaration that connects the Dafny method M.Demo.p
to the C# method
demo.Demo.p
, and a static Dafny method M.newDemo
connected to the
static C# method demo.Demo.newDemo
.
The extern declaration for class Demo
is actually not necessary if the
Dafny and C# classes have the same name and the Dafny class is contained in
a module whose extern name is the same as the C# namespace.
Then the Main
method in the Dafny file calls the two Dafny methods, which are
translated to the two C# methods. The combination is built and run using
dafny run --target:cs Demo1a.dfy --input Demo1x.cs
Calling Dafny from C#
The simplest way to call a Dafny method from C# is to place the Dafny
method in a class within a module. The module should be attributed as
{:extern "M"}
, where M is the desired name of the C# namespace in
which the C# class resides. There is no need to give an extern name to the
class or method.
In Demo2.dfy
:
module {:extern "MM"} MM {
class C {
static method p() {
print "Hello, World\n";
}
}
}
module MMM {
class Demo2x {
static method {:extern "Demo2x", "M"} M()
}
}
method Main() { MMM.Demo2x.M(); }
In Demo2x.cs
:
public class Demo2x {
public static void M() {
MM.C.p(); // Calls p() in Dafny
}
}
The above program is run with dafny run --target:cs Demo2.dfy --input Demo2x.cs
.
(Note that having Dafny invoke Main
in the .cs
code is not yet operational.)
Types
If the C# method has input arguments or an output value, then the Dafny declaration must use
corresponding types in Dafny:
Here, T'
for a type parameter T
indicates the C# type corresponding to a Dafny type T
.
Dafny type | C# type |
---|---|
bool | bool |
int | System.Numerics.BigInteger |
int64 | long |
int32 | int |
int16 | short |
int8 | sbyte |
char | char |
bitvector | appropriate unsigned int-based type |
ORDINAL | java.math.BigInteger |
real | Dafny.BigRational |
double | |
float | |
string | Dafny.ISequence |
JavaString | java.lang.String |
C, C? (for class, iterator C) | (class) C |
(trait) T | (interface) T |
datatype, codatatype | (class) C |
subset type | same as base type |
tuple | _System._ITuple_n_ |
array |
T’[] |
seq |
Dafny.ISequence<? extends T’> |
set |
Dafny.ISet<? extends T’> |
multisetset |
Dafny.ISet<? extends T’> |
map<T,U>, imap<T,U> | Dafny.IMap<? extends T’> |
imap<T,U>, imap<T,U> | Dafny.IMap<? extends T’> |
function (arrow) types | Func<T’,U’> |