← O-Reilly Learning Go

Here Be Dragons-Reflect, Unsafe, and Cgo

Golang

Overview

when the type of the data can’t be determined at compile time, you can use the reflection support in the reflect package to interact with and even construct data. When you need to take advantage of the memory layout of data types in Go, you can use the unsafe package. And if there is functionality that can be provided only by libraries written in C, you can call into C code with cgo.

Reflect

Using reflect to check the types on runtime but it will slow down the program for a bit.
Reflection can be used for various things. Such as checking fields from the struct, values types and also do setting data to them.

Unsafe

Unsafe is to bypass the go type safety and memory protections. We can use this to directly manipulate the memory address.
There are two common patterns in unsafe code. The first is a conversion between two types of variables that are normally not convertible. This is performed using a series of type conversions with unsafe.Pointer in the middle. The second is reading or modifying the bytes in a variable by converting a variable to an unsafe.Pointer, converting the unsafe.Pointer to a pointer, and then copying or manipulating the underlying bytes. Both of these techniques require you to know the size (and possibly the location) of the data being manipulated. The Sizeof and Offsetof functions in the unsafe package provide this information.

For a struct, the size is the sum of the sizes of the fields, plus some adjustments for alignment. Computers like to read and write data in regular-sized chunks and they really don’t want a value to start in one chunk and end in another. To make this happen, the compiler adds padding between fields so they line up properly. The compiler also wants the entire struct to be properly aligned. On a 64-bit system, it will add padding at the end of the struct to bring its size up to a multiple of 8 bytes.

type BoolIntBool struct { 
	b bool // takes 1 byte but the compiler added padding for memory alignment take 8 bytes
	i int64 // takes 8 bytes
	b2 bool //  takes 1 byte but the compiler added padding for memory alignment take 8 bytes
} 


type BoolBoolInt struct { 
	b bool 
	b2 bool 
	i int64 
} 
	
type IntBoolBool struct { 
	i int64 
	b bool 
	b2 bool 
}

// 24 0 8 16
// 16 0 1 8
// 16 0 8 9

Note about memory alignment

Two separate rules

Rule 1 — field placement: each field starts at an offset that is a multiple of its own alignment. Not the largest field's — its own.

Rule 2 — total size: the struct's alignment = the largest alignment among its fields. The total size is rounded up to a multiple of that (end padding).

So "largest variable" only decides the total size, not where each field sits.

Examples

type BoolIntBool struct {
b bool // offset 0
_ [7] // padding 1–7 (align i to 8)
i int64 // offset 8–15
b2 bool // offset 16
_ [7] // padding 17–23 (round size up to 8)
} // total: 24 bytes

type BoolIntBool struct {
b bool // offset 0
_ [3] // padding 1–3 (align i to 4
i int32 // offset 4–7
b2 bool // offset 8
_ [3] // padding 9–11 (round size up to 4)
}

Summary

Each field is padded to satisfy its own alignment; the whole struct is padded at the end to satisfy the largest field's alignment.

Notes

Allocating memory on the heap is almost always slower than using memory on the stack.