3 Foreign Declarations

This section describes the extension of Haskell 98 by foreign declarations. The following production for the nonterminal topdecl extends the same nonterminal from the Haskell 98 Report. All other nonterminals are new.

topdecl -->foreign fdecl
fdecl -->import callconv [safety] impent var :: ftype (define variable)
|export callconv expent var :: ftype (expose variable)
callconv-->ccall | stdcall | cplusplus | jvm | dotnet (calling convention)
|system-specific calling conventions
impent -->[string] (imported external entity)
expent -->[string] (exported entity)
safety -->unsafe | safe

There are two flavours of foreign declarations: import and export declarations. An import declaration makes an external entity, i.e., a function or memory location defined in an external context, available in the Haskell context. Conversely, an export declaration defines a function of the Haskell context as an external entity in an external context. Consequently, the two types of declarations differ in that an import declaration defines a new variable, whereas an export declaration uses a variable that is already defined in the Haskell module.

The external context that contains the external entity is determined by the calling convention given in the foreign declaration. Consequently, the exact form of the specification of the external entity is dependent on both the calling convention and on whether it appears in an import declaration (as impent) or in an export declaration (as expent). To provide syntactic uniformity in the presence of different calling conventions, it is guaranteed that the description of an external entity lexically appears as a Haskell string lexeme. The only exception is where this string would be the empty string (i.e., be of the form ""); in this case, the string may be omitted in its entirety.

3.1 Calling Conventions

The binary interface to an external entity on a given architecture is determined by a calling convention. It often depends on the programming language in which the external entity is implemented, but usually is more dependent on the system for which the external entity has been compiled.

As an example of how the calling convention is dominated by the system rather than the programming language, consider that an entity compiled to byte code for the Java Virtual Machine (JVM) [6] needs to be invoked by the rules of the JVM rather than that of the source language in which it is implemented (the entity might be implemented in Oberon, for example).

Any implementation of the Haskell 98 FFI must at least implement the C calling convention denoted by ccall. All other calling conventions are optional. Generally, the set of calling conventions is open, i.e., individual implementations may elect to support additional calling conventions. In addition to ccall, Table 1 specifies a range of identifiers for common calling conventions.

Identifier Represented calling convention

ccall Calling convention of the standard C compiler on a system
cplusplusCalling convention of the standard C++ compiler on a system
dotnet Calling convention of the .net platform
jvm Calling convention of the Java Virtual Machine
stdcall Calling convention of the Win32 API (matches Pascal conventions)

Table 1: Calling conventions

Implementations need not implement all of these conventions, but if any is implemented, it must use the listed name. For any other calling convention, implementations are free to choose a suitable name.

The present report does only define the semantics of the calling conventions ccall and stdcall. Later versions of the report are expected to cover more calling conventions.

It should be noted that the code generated by a Haskell system to implement a particular calling convention may vary widely with the target code of that system. For example, the calling convention jvm will be trivial to implement for a Haskell compiler generating Java code, whereas for a Haskell compiler generating C code, the Java Native Interface (JNI) [5] has to be targeted.

3.2 Foreign Types

The following types constitute the set of basic foreign types:

A Haskell system that implements the FFI needs to be able to pass these types between the Haskell and the external context as function arguments and results.

Foreign types are produced according to the following grammar:

ftype -->frtype
|fatype -> ftype
fatype-->qtycon atype1 ... atypek (k > 0)

A foreign type is the Haskell type of an external entity. Only a subset of Haskell’s types are permissible as foreign types, as only a restricted set of types can be canonically transferred between the Haskell context and an external context. A foreign type generally has the form at1 -> ... -> atn -> rt where n > 0. It implies that the arity of the external entity is n.

The argument types ati produced by fatype must be marshallable foreign types; that is, each ati is either (1) a basic foreign type or (2) a type synonym or renamed datatype of a marshallable foreign type. Moreover, the result type rt produced by frtype must be a marshallable foreign result type; that is, it is either a marshallable foreign type, the type (), or a type matching Prelude.IO t, where t is a marshallable foreign type or ().

External functions are generally strict in all arguments.

3.3 Import Declarations

Generally, an import declaration has the form foreign import c e v :: t which declares the variable v of type t to be defined externally. Moreover, it specifies that v is evaluated by executing the external entity identified by the string e using calling convention c. The precise form of e depends on the calling convention and is detailed in Section 4. If a variable v is defined by an import declaration, no other top-level declaration for v is allowed in the same module. For example, the declaration

foreign import ccall "string.h strlen" cstrlen :: Ptr CChar -> IO CSize

introduces the function cstrlen, which invokes the external function strlen using the standard C calling convention. Some external entities can be imported as pure functions; for example,

foreign import ccall "math.h sin" sin :: CDouble -> CDouble.

Such a declaration asserts that the external entity is a true function; i.e., when applied to the same argument values, it always produces the same result.

Whether a particular form of external entity places a constraint on the Haskell type with which it can be imported is defined in Section 4. Although, some forms of external entities restrict the set of Haskell types that are permissible, the system can generally not guarantee the consistency between the Haskell type given in an import declaration and the argument and result types of the external entity. It is the responsibility of the programmer to ensure this consistency.

Optionally, an import declaration can specify, after the calling convention, the safety level that should be used when invoking an external entity. A safe call is less efficient, but guarantees to leave the Haskell system in a state that allows callbacks from the external code. In contrast, an unsafe call, while carrying less overhead, must not trigger a callback into the Haskell system. If it does, the system behaviour is undefined. The default for an invocation is to be safe. Note that a callback into the Haskell system implies that a garbage collection might be triggered after an external entity was called, but before this call returns. Consequently, objects other than stable pointers (cf. Section 5.6) may be moved or garbage collected by the storage manager.

3.4 Export Declarations

The general form of export declarations is foreign export c e v :: t Such a declaration enables external access to v, which may be a value, field name, or class method that is declared on the top-level of the same module or imported. Moreover, the Haskell system defines the external entity described by the string e, which may be used by external code using the calling convention c; an external invocation of the external entity e is translated into evaluation of v. The type t must be an instance of the type of v. For example, we may have

foreign export ccall "addInt"   (+) :: Int   -> Int   -> Int  
foreign export ccall "addFloat" (+) :: Float -> Float -> Float

If an evaluation triggered by an external invocation of an exported Haskell value returns with an exception, the system behaviour is undefined. Thus, Haskell exceptions have to be caught within Haskell and explicitly marshalled to the foreign code.