Go

Fortran can be invoked directly from Go code by using the cgo language extension. A simple example is provided in the Golang source tree, which is quite helpful. If the Fortran source is in the same directory as a Go library, cgo automatically builds the Fortran files and includes them.

We define the Go package example in the golang/src directory. The Fortran source and the C headers are side-by-side with the Go package:

$ tree golang/src/example/
golang/src/example/
├── example.f90 -> ../../../fortran/example.f90
├── example.go
└── example.h -> ../../../c/example.h

0 directories, 3 files

Package

Within the package, we first declare the package to use cgo and include the relevant header file:

// #include "example.h"
import "C"

We start by defining user-friendly equivalents of C.struct_UserDefined and C.struct_DataContainer

type UserDefined struct {
	Buzz    float64
	Broken  float64
	HowMany int32
}

type DataContainer struct {
	Data *[8]float64
}

func (udf *UserDefined) String() string {
	return fmt.Sprintf(
		"%T(%f, %f, %d)",
		udf, udf.Buzz, udf.Broken, udf.HowMany,
	)
}

Adding just_print() to the package interface is just a matter of making C.just_print public:

func JustPrint() {
	C.just_print()
}

When passing in *float64 (i.e. pointer types), the underlying values can be passed along to C.make_udf without having to copy any data, e.g. via (*C.double)(buzz). The foreign call will populate the fields of a C.struct_UserDefined, which will need to be converted to normal Go types when a UserDefined value is created for the return. To avoid copying when constructing a UserDefined object, we dereference a field value (e.g. &quuz.buzz), convert the reference to a non-C pointer type (e.g. (*float64)(&quuz.buzz)) and then dereference the value:

func MakeUDF(buzz, broken *float64, howMany *int32) *UserDefined {
	var quuz C.struct_UserDefined
	C.make_udf(
		(*C.double)(buzz),
		(*C.double)(broken),
		(*C.int)(howMany),
		&quuz,
	)
	return &UserDefined{
		*(*float64)(&quuz.buzz),
		*(*float64)(&quuz.broken),
		*(*int32)(&quuz.how_many),
	}
}

When dealing with array types, the first value in a slice is dereferenced and then converted into a C pointer (e.g. (*C.double)(&val[0])):

func FooArray(size *int32, val []float64) []float64 {
	twoVal := make([]float64, len(val), cap(val))
	C.foo_array(
		(*C.int)(size),
		(*C.double)(&val[0]),
		(*C.double)(&twoVal[0]),
	)
	return twoVal
}

When calling udf_ptr(), a UserDefined value must be created, dereferenced, cast to unsafe.Pointer and then cast again to uintptr:

madeIt := example.UserDefined{}
ptrAsInt := uintptr(unsafe.Pointer(&madeIt))
example.UDFPtr(&ptrAsInt)

Only then can the uintptr be converted to a C.intptr_t:

func UDFPtr(ptrAsInt *uintptr) {
	C.udf_ptr(
		(*C.intptr_t)(unsafe.Pointer(ptrAsInt)),
	)
}

func MakeContainer(contained []float64) *DataContainer {
	var container C.struct_DataContainer
	C.make_container(
		(*C.double)(&contained[0]),
		&container,
	)
	dataPtr := (*[8]float64)(unsafe.Pointer(&container.data))
	return &DataContainer{dataPtr}
}

In the case of view_knob(), the mangled name must be used:

func ViewKnob() int32 {
	knobValue := C.__example_MOD_view_knob()
	return (int32)(knobValue)
}

func TurnKnob(newValue *int32) {
	C.turn_knob(
		(*C.int)(newValue),
	)
}

Output

The Go example can be run via go run. As with C and C++, the gfortran search path may need to be explicitly provided (with -L flags). This can be done with the CGO_LDFLAGS environment variable.

$ go run golang/main.go
------------------------------------------------------------
quux = foo(1.000000, 16.000000) = 61.000000
------------------------------------------------------------
quuz = make_udf(1.250000, 5.000000, 1337)
     = *example.UserDefined(1.250000, 5.000000, 1337)
------------------------------------------------------------
foo_array(
    4,
    [[3.000000, 4.500000],
     [1.000000, 1.250000],
     [9.000000, 0.000000],
     [-1.000000, 4.000000]],
) =
    [[6.000000, 9.000000],
     [2.000000, 2.500000],
     [18.000000, 0.000000],
     [-2.000000, 8.000000]]
------------------------------------------------------------
ptrAsInt = &madeIt
ptrAsInt = 842350544096  // 0xc4200144e0
udf_ptr(&ptrAsInt)  // Set memory in ``madeIt``
&madeIt = *example.UserDefined(3.125000, -10.500000, 101)
------------------------------------------------------------
contained =
  [[0.000000, 4.000000],
   [1.000000, 9.000000],
   [1.000000, 2.000000],
   [3.000000, 1.000000]]
container = make_container(contained)
container.Data =
  [[0.000000, 4.000000],
   [1.000000, 9.000000],
   [1.000000, 2.000000],
   [3.000000, 1.000000]]
&contained      = 842350560256  // 0xc420018400
&container      = 842350518320  // 0xc42000e030
 container.Data = 842350560320  // 0xc420018440
------------------------------------------------------------
just_print()
 ======== BEGIN FORTRAN ========
 just_print() was called
 ========  END  FORTRAN ========
------------------------------------------------------------
view_knob() = 1337
turn_knob(42)
view_knob() = 42