Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

Working in collaboration with a different team/squad within a company can be complicated if you do not use the same language. In my squad, we are working with Go as a language for a project and we need the collaboration of the data science team who work with Python. They have provided us some formulas that we need to translate in our project in Go.

Once this formula is implemented, we need to get validation from this team in order to confirm that everything is correct and properly implemented. Letting them test and create their own data set of tests would definitely be the best way to do. Luckily, there is a way to achieve that with Go.

Cgo and shared objects

The build command go build has a parameter that allows you to build your Go packages into a C shared library:

-buildmode=c-shared

Build the listed main package, plus all packages it imports,

into a C shared library. The only callable symbols will

be those functions exported using a cgo //export comment.

Requires exactly one main package to be listed.

This mode will allow you to build a shared library, “.so”, that you can directly use from other languages like C, Java, Ruby, or Python. However, this mode will only work with Cgo, which allows you to write and call C code from your Go package. Based on that, you will be able to distribute a version of your library which can be used by the other team with their own language.

Implementation

The implementation of the gateway between Go and the shared object looks pretty easy. The first thing you will have to do is add the comment //export MyFunction on each function you want to make available publicly. Then you have to declare your C structs as a preamble before the mandatory import of “C”. Here is a simplified example of our code:

import (

/*

typedef struct{

int from_bedroom;

int to_bedroom;

int from_price;

int to_price;

int from_size;

int to_size;

int types[5];

} lead;



typedef struct{

int bedroom;

int price;

int size;

int type_id;

} property;

*/

"C"

) //export NewProperty

func NewProperty(b int, p int, s int, t int) C.property {

// business logic



return C.property{

bedroom: C.int(b),

price: C.int(p),

size: C.int(s),

type_id: C.int(t),

}

} //export NewLead

func NewLead(fb int, tb int, fp int, tp int, fs int, ts int, t []int) C.lead {

// business logic return C.lead{

from_bedroom: C.int(fb),

to_bedroom: C.int(tb),

from_price: C.int(fp),

to_price: C.int(tp),

from_size: C.int(fs),

to_size: C.int(ts),

types: types,

}

} //export CalculateDistance

func CalculateDistance(l C.lead, p C.property) {

// business logic here

}

Since you cannot export Go structs, you will need to deal with C struct as parameters and result parameters. Once your code is written, you can compile it thanks to the command go build -o main.so -buildmode=c-shared main.go . In order to make the build successful, you should have a main package along a main function in your Go code. Then, you can write your Python script:

#!/usr/bin/env python

from ctypes import *



# loading shared object

matching = cdll.LoadLibrary("main.so")



# go type

class GoSlice(Structure):

_fields_ = [("data", POINTER(c_void_p)), ("len", c_longlong), ("cap", c_longlong)]



class Lead(Structure):

_fields_ = [('from_bedroom', c_int),

('to_bedroom', c_int),

('from_price', c_int),

('to_price', c_int),

('from_size', c_int),

('to_size', c_int),

('types', GoSlice)]



class Property(Structure):

_fields_ = [('bedroom', c_int),

('price', c_int),

('size', c_int),

('type_id', c_int)]



#parameters definition

matching.NewLead.argtypes = [c_int, c_int, c_int, c_int, c_int, c_int, GoSlice]

matching.NewLead.restype = Lead



matching.NewProperty.argtypes = [c_int, c_int, c_int, c_int]

matching.NewProperty.restype = Property



matching.CalculateDistance.argtypes = [Lead, Property]



lead = lib.NewLead(

# from bedroom, to bedroom

1, 2,

# from price, to price

80000, 100000,

# from size, to size

750, 1000,

# type

GoSlice((c_void_p * 5)(1, 2, 3, 4, 5), 5, 5)

)

property = lib.NewProperty(2, 90000, 900, 1)



matching.CalculateDistance(lead, property)

Each method available in your shared object should be described in your Python file: the types and order of the parameters as well as result parameters.

Then, you can run your Python script python3 main.py .

Ease of use

It looks easy at first sight, but it may take time to make it work. There is not much documentation/examples about that in Python, and it is pretty hard to debug. A bad description of your exposed methods via .argtypes or .restype will always lead to an unexpected behavior or a segmentation fault error message that does not give much information.

This approach for Go/Python works well for a cross-team testing, but I would not go with that solution for a bigger project or for a feature to be shipped in production. The development is not straightforward and can quickly become time-consuming.