I wrapped a C function that opens a file. The call itself was easy; deciding where a C string stopped and a Go os.Error began took the rest of the afternoon.

The first useful program was deliberately dull:

package main

/*
#include <fcntl.h>
#include <stdlib.h>

static int open_read_only(const char *name) {
	return open(name, O_RDONLY);
}
*/
import "C"

import (
	"os"
	"unsafe"
)

func open(name string) (int, os.Error) {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	fd, err := C.open_read_only(cname)
	if fd < 0 {
		return -1, err
	}
	return int(fd), nil
}

The comment immediately before import "C" is cgo’s preamble, not decoration. The little C wrapper is necessary because open is variadic and cgo cannot call it directly. C.CString allocates and copies, so the matching C.free stays next to it. The Go string may contain a zero byte, too; a C API will see that as the end of the name. My wrapper rejects such names before this call rather than quietly opening something else.

In a two-result cgo call, cgo captures C’s errno as the os.Error used by this Go snapshot. I test the function’s documented failure result first; a stale nonzero errno after a successful call is not an error. This also keeps C’s zero-terminated error text on the C side of the wrapper rather than exposing a borrowed character pointer to callers.

That translation belongs right here. Returning a bare integer status would make every caller know C’s failure sentinel, when errno is meaningful, and how long the message storage lives. One wrapper is enough bureaucracy.

There are wrinkles beyond this toy. Some functions return errors directly instead of using errno; some return allocated strings that need a library-specific free function; and a successful result can still require cleanup. The C header, not habit, decides each case.

There are practical caveats. The build now needs a C compiler and the library’s headers and linker flags. Types that merely look alike are not automatically interchangeable. Memory allocated by C must be released according to C’s rules, and Go pointers should not be tucked away by C code for later use.

The wrapper is dull now: Go string in, file descriptor or os.Error out. Dull borders are much easier to debug.