Better binary packing for Go
Latest commit ae5aa81 Mar 4, 2016 @lunixbochs fix #41
Failed to load latest commit information.
.travis.yml remove coveralls from build for now Feb 13, 2015
LICENSE support struct slices (fix #30) Nov 20, 2015
bench_test.go add benchmark for array of structs Nov 22, 2015
binary.go add support for custom types Feb 6, 2016
custom.go add support for custom types Feb 6, 2016
custom_float16.go add Float16 type Mar 1, 2016
custom_float16_test.go add Float16 type Mar 1, 2016
custom_test.go add support for custom types Feb 6, 2016
field_test.go check errors in all tests May 4, 2015
fields.go add Int3 to example struct as a size, fix #35 more Feb 14, 2016
fields_test.go support uint sizeof fields Jan 10, 2016
legacy.go move WithOrder functions to WithOptions Jan 27, 2016
packable_test.go improve tests and direct packing behavior Nov 7, 2015
packer.go add support for custom types Feb 6, 2016
parse.go add support for custom types Feb 6, 2016
parse_test.go add parse error test for a nested empty struct Feb 13, 2015
struc.go add support for custom types Feb 6, 2016
struc_test.go fix #41 Mar 4, 2016
types.go add support for custom types Feb 6, 2016
types_test.go fix native size/off types and write PtrSize test Jan 27, 2016

Build Status


Struc exists to pack and unpack C-style structures from bytes, which is useful for binary files and network protocols. It could be considered an alternative to encoding/binary, which requires massive boilerplate for some similar operations.

Take a look at an example comparing struc and encoding/binary

Struc considers usability first. That said, it does cache reflection data and aims to be competitive with encoding/binary struct packing in every way, including performance.

Example struct

type Example struct {
    Var   int `struc:"int32,sizeof=Str"`
    Str   string
    Weird []byte `struc:"[8]int64"`
    Var   []int `struc:"[]int32,little"`

Struct tag format

  • Var []int `struc:"[]int32,little,sizeof=StringField"` will pack Var as a slice of little-endian int32, and link it as the size of StringField.
  • sizeof=: Indicates this field is a number used to track the length of a another field. sizeof fields are automatically updated on Pack() based on the current length of the tracked field, and are used to size the target field during Unpack().
  • Bare values will be parsed as type and endianness.

Endian formats

  • big (default)
  • little

Recognized types

  • pad - this type ignores field contents and is backed by a [length]byte containing nulls
  • bool
  • byte
  • int8, uint8
  • int16, uint16
  • int32, uint32
  • int64, uint64
  • float32
  • float64

Types can be indicated as arrays/slices using [] syntax. Example: []int64, [8]int32.

Bare slice types (those with no [size]) must have a linked Sizeof field.

Private fields are ignored when packing and unpacking.

Example code

package main

import (

type Example struct {
    A int `struc:"big"`

    // B will be encoded/decoded as a 16-bit int (a "short")
    // but is stored as a native int in the struct
    B int `struc:"int16"`

    // the sizeof key links a buffer's size to any int field
    Size int `struc:"int8,little,sizeof=Str"`
    Str  string

    // you can get freaky if you want
    Str2 string `struc:"[5]int64"`

func main() {
    var buf bytes.Buffer
    t := &Example{1, 2, 0, "test", "test2"}
    err := struc.Pack(&buf, t)
    o := &Example{}
    err = struc.Unpack(&buf, o)


BenchmarkEncode uses struc. Stdlib benchmarks use equivalent encoding/binary code. Manual encodes without any reflection, and should be considered an upper bound on performance (which generated code based on struc definitions should be able to achieve).

BenchmarkEncode        1000000   1265 ns/op
BenchmarkStdlibEncode  1000000   1855 ns/op
BenchmarkManualEncode  5000000    284 ns/op
BenchmarkDecode        1000000   1259 ns/op
BenchmarkStdlibDecode  1000000   1656 ns/op
BenchmarkManualDecode  20000000  89.0 ns/op