The Go documention is littered with warnings against using package unsafe
to needlessly defeat Go's type system. unsafe.Pointer
is clearly identified as unsafe for a reason.
Package unsafe
Package unsafe contains operations that step around the type safety of
Go programs.
Packages that import unsafe may be non-portable and are not protected
by the Go 1 compatibility guidelines.
type Pointer
Pointer therefore allows a program to defeat the type system and read
and write arbitrary memory. It should be used with extreme care.
Command cgo
Passing pointers
Go is a garbage collected language, and the garbage collector needs to
know the location of every pointer to Go memory. Because of this,
there are restrictions on passing pointers between Go and C.
[These] rules are checked dynamically at runtime.
It is possible to defeat this enforcement by using the unsafe package,
and of course there is nothing stopping the C code from doing anything
it likes. However, programs that break these rules are likely to fail
in unexpected and unpredictable ways.
Let's review a realistic example.
Here's a C function that receives a bytes buffer.
void printbuf(size_t len, unsigned char *buf)
In Go, using cgo and preserving type safety with matching types, we could write,
var buf []byte
C.printbuf(C.size_t(len(buf)), (*C.uchar)(&buf[0]))
However, that's still unsafe, buf[0]
will be out of range if len(buf) == 0
. When buf
is initialized to it's zero-value the array pointer will also be nil
. We can neatly encapsulate the integrity checks in a Go function, which the Go gc optimizing compiler will inline.
func cbuf(buf []byte) (size C.size_t, ptr *C.uchar) {
var bufptr *byte
if cap(buf) > 0 {
bufptr = &(buf[:1][0])
}
return C.size_t(len(buf)), (*C.uchar)(bufptr)
}
and
bufsize, bufptr := cbuf(buf)
C.printbuf(bufsize, bufptr)
Defeating the type system by using unsafe.Pointer
is unsafe. For example,
C.printbuf(C.size_t(len(buf)), (*C.uchar)(unsafe.Pointer(&buf[0])))
The buf
type could be any indexed type: array, pointer to array, slice, string or map. Even worse, the size, if not one byte, will be wrong. Now it gets get really ugly,
C.printbuf(C.size_t(len(buf)*int(unsafe.Sizeof(buf[0]))), (*C.uchar)(unsafe.Pointer(&buf[0])))
And we haven't taken into account nil pointers and out of range values.
Next the code review: code should be correct, maintainable, robust, reasonably efficient, and, most importantly, readable. Don't expect the code review of unsafe.Pointer
usage to go well.
Let's hear your rationale for the use of unsafe.Pointer for this purpose.
Sample code:
printbuf.go
:
package main
/*
#include <stdio.h>
void printbuf(size_t len, unsigned char *buf) {
printf("%lu [", len);
if (!buf) {
len = 0;
}
size_t maxwidth = 16;
size_t width = len <= maxwidth ? len : maxwidth;
for (size_t i = 0; i < width; i++) {
if (i > 0) {
printf(" ");
}
printf("%02X", buf[i]);
}
if (width < len) {
printf(" ...");
}
printf("]\n");
}
*/
import "C"
import (
"unsafe"
)
// NOTE: -gcflags='-m' : can inline cbuf : inlining call to cbuf
func cbuf(buf []byte) (size C.size_t, ptr *C.uchar) {
var bufptr *byte
if cap(buf) > 0 {
bufptr = &(buf[:1][0])
}
return C.size_t(len(buf)), (*C.uchar)(bufptr)
}
func main() {
var buf []byte // zero-value = nil, len = 0, cap = 0
bufsize, bufptr := cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 0) // len = 0, cap = 0
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 0, 32) // len = 0
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
buf = make([]byte, 32) // len > 0
for i := range buf {
buf[i] = byte(i)
}
bufsize, bufptr = cbuf(buf)
C.printbuf(bufsize, bufptr)
if len(buf) > 0 {
C.printbuf(C.size_t(len(buf)), (*C.uchar)(&buf[0]))
C.printbuf(C.size_t(len(buf)), (*C.uchar)(unsafe.Pointer(&buf[0])))
C.printbuf(C.size_t(len(buf)*int(unsafe.Sizeof(buf[0]))), (*C.uchar)(unsafe.Pointer(&buf[0])))
}
}
Output:
0 []
0 []
0 []
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]
32 [00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ...]