mus: A High-Performance, Flexible Binary Serialization Library for Go

May 6, 2026 · View on GitHub

Go Reference GoReportCard codecov OpenSSF Scorecard

mus is a powerful and versatile Go library for efficient binary serialization.

While originally built for the MUS format, its minimalist architecture and extensive set of serialization primitives make it ideal for implementing other binary formats (including Protobuf-style encoding).

A streaming version is also available: mus-stream.

Why mus?

Performance

  • Optimized for maximum speed (see benchmarks).
  • Offers space-efficient binary representation.
  • Has zero-allocation mode.

Capabilities

In addition to 32/64-bit compatibility and private field access, the library provides:

  • Typed serialization (versioning, oneof).
  • Comprehensive pointers support (handles cyclic graphs and linked lists).
  • Validation during unmarshalling.
  • Data skipping (enables out-of-order and partial unmarshalling).

Contents

Quick Start

Here's an example of how to use mus to serialize a number.

package main

import (
  "fmt"
  "github.com/mus-format/mus-go/varint"
)

func main() {
  num  := 100

  // Pre-allocate a buffer.
  var (
    size = varint.Int.Size(num)
    bs   = make([]byte, size)
  )
  
  // Marshal.
  varint.Int.Marshal(num, bs)
  
  // Unmarshal.
  num, _, err := varint.Int.Unmarshal(bs)
  if err != nil {
    panic(err)
  }
}

Implementing mus serializers manually can be tedious and error-prone. There are two ways to automate this:

How To

To make a type serializable with mus, you need to implement the mus.Serializer interface:

import "github.com/mus-format/mus-go"

// YourTypeMUS is a MUS serializer for YourType.
var YourTypeMUS = yourTypeMUS{}

// yourTypeMUS implements the mus.Serializer interface.
type yourTypeMUS struct{}

func (s yourTypeMUS) Marshal(v YourType, bs []byte) (n int)              {...}
func (s yourTypeMUS) Unmarshal(bs []byte) (v YourType, n int, err error) {...}
func (s yourTypeMUS) Size(v YourType) (size int)                         {...}
func (s yourTypeMUS) Skip(bs []byte) (n int, err error)                  {...}

Then, you can use it as follows:

value := YourType{...}

var (
  size = YourTypeMUS.Size(value) // The number of bytes required to serialize 
  // the value.
  bs = make([]byte, size)
)

n := YourTypeMUS.Marshal(value, bs) // Returns the number of used bytes.

value, n, err := YourTypeMUS.Unmarshal(bs) // Returns the value, the number of 
// used bytes and any error encountered.

// Instead of unmarshalling, the value can be skipped:
n, err := YourTypeMUS.Skip(bs)

Packages

mus offers several encoding options, each in a separate package.

PackageUse CaseKey StrengthTrade-off
varintNumbersSpace efficientSlight CPU cost for encoding
rawNumbers, TimeFast encodingHigher space usage for small numbers
ordPointers, Strings, Slices, MapsVariable-length types supportStandard allocations
unsafeHigh-perf Numbers, Time, Strings, ArraysZero-allocationUses unsafe type conversions
pmPointers, Cyclic Graphs, Linked ListsPreserves pointer equalitySlightly more complex than ord
typedInterface/VersioningTyped serializationRequires DTM definition

varint

This package provides Varint serializers for all uint (e.g., uint64, uint32, ...), int, float, and byte data types. It also includes the PositiveInt serializer (Varint without ZigZag) for efficiently encoding positive int values (negative values are supported as well, though with reduced performance).

raw

This package contains Raw serializers for byte, uint, int, float, and time.Time data types.

More details about Varint and Raw encodings can be found in the MUS format specification. If in doubt, use Varint.

For time.Time, there are several serializers:

  • TimeUnix – encodes a value as a Unix timestamp in seconds.
  • TimeUnixMilli – encodes a value as a Unix timestamp in milliseconds.
  • TimeUnixMicro – encodes a value as a Unix timestamp in microseconds.
  • TimeUnixNano – encodes a value as a Unix timestamp in nanoseconds.

To ensure the deserialized time.Time value is in UTC, either set your TZ environment variable to UTC (e.g., os.Setenv("TZ", "")) or use one of the corresponding UTC serializers (e.g., TimeUnixUTC, TimeUnixMilliUTC).

ord (ordinary)

Contains serializers/constructors for bool, string, byte slice, slice, map, and pointer types.

Variable-length data types (such as string, slice, and map) are encoded as length + data, with customizable binary representations for both parts. By default, the length is encoded using varint.PositiveInt, which limits the length to the maximum value of the int type on your system. Such encoding works well across different architectures. For example, an attempt to unmarshal a string that is too long on a 32-bit system will result in an ErrOverflow.

For slice and map types, only constructors are available (examples).

unsafe

The unsafe package provides maximum performance by using unsafe type conversions. This primarily affects the string type, where modifying the underlying byte slice after unmarshalling will also change the string's contents (example).

Provides serializers for the following data types: byte, bool, string, array, byte slice, time.Time and all uint, int, float.

pm (pointer mapping)

Let's consider two pointers initialized with the same value:

var (
  str = "hello world"
  ptr = &str

  ptr1 *string = ptr
  ptr2 *string = ptr
)

The pm package preserves pointer equality after unmarshalling, ensuring that ptr1 == ptr2, while the ord package does not. This capability enables the serialization of data structures like cyclic graphs or linked lists (examples).

typed (data type metadata support)

The typed package provides DTM support for the mus serializer. It wraps a type serializer and a DTM value, enabling typed data serialization to provide data versioning, the oneof feature, and other capabilities.

Structs Support

mus doesn’t support structs out of the box, which means you’ll need to implement the mus.Serializer interface yourself by choosing the specific encoding for each field (example). This approach provides greater flexibility and keeps mus simple, making it easy to implement in other programming languages.

More Features

  • Validation: Validate data during unmarshalling using custom functions: func(v Type) error (examples).
  • Out-of-order deserialization: Decode fields partially or non-sequentially (example).
  • Zero-allocation: Achieve maximum efficiency by using the unsafe package.

Testing

To run all mus tests, use the following command:

go test ./...

Fuzz Testing

To run fuzz tests use the fuzz.sh script:

./fuzz.sh 10s

For a specific fuzz test:

go test -v -fuzz="^FuzzVarint_Byte$" ./varint -fuzztime 10s

Benchmarks

NAMENS/OPB/SIZEB/OPALLOCS/OP
mus102.9058.0048.001
protobuf531.7069.00271.004
json2779.00150.00600.009

Data sourced from ymz-ncnk/go-serialization-benchmarks.

Why a separate benchmark suite? Standard benchmarks sometimes produce inconsistent results across multiple runs. The ymz-ncnk/go-serialization-benchmarks suite was created to provide more consistent and reproducible measurements.

Contributing & Security

We welcome contributions of all kinds! Please see CONTRIBUTING.md for details on how to get involved.

If you find a security vulnerability, please refer to our Security Policy for instructions on how to report it privately.

Version Compatibility

Check VERSIONS.md for the compatibility matrix of module versions.

Used By

See who is building with MUS in USERS.md.

Already using MUS and want to support the project's growth? Join the list!