zlib

package module
v0.0.0-...-786844a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: BSD-3-Clause Imports: 10 Imported by: 0

README

go-ruby-zlib/zlib

zlib — go-ruby-zlib

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's zlib standard library — the MRI 4.0.5 Zlib module. The DEFLATE engine is klauspost/compress (its drop-in compress/flate, compress/zlib and compress/gzip packages) — a pure-Go, CGO=0, build-from-source dependency that is far faster than the standard library's flate (≈10× on Deflate); the checksums are SIMD-accelerated — CRC-32 folds with a carryless-multiply kernel (go-simd/crc32, arm64 PMULL fold-by-eight; amd64/ppc64le/s390x defer to the hardware-assisted standard library) and Adler-32 uses go-simd/adler32, each bit-identical to hash/crc32 / hash/adler32. It offers deflate / inflate, gzip, the CRC-32 and Adler-32 checksums (and their combine forms), and a streaming compressor / decompressor, so a host such as go-embedded-ruby can serve require "zlib" with no C extension and a static, CGO=0 binary.

It is the zlib backend for go-embedded-ruby but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-yaml (Psych), and go-ruby-erb (the ERB compiler).

Byte-exactness — what is and isn't guaranteed. The checksums (Crc32, Adler32, and their combine forms) are byte-exact with MRI: a value computed here equals the integer MRI prints for the same input. The compressors round-trip and interoperate with MRI in both directions (we inflate MRI's deflate output, MRI inflates ours), but zlib never promises a canonical encoding, so the exact deflate byte stream is implementation-defined and need not equal MRI's. For gzip the same holds, and additionally the header carries an mtime/OS byte; GzipCompress fixes the mtime to zero for determinism, but you should compare the decompressed payload (and its CRC), not the raw gzip bytes.

Features

Faithful port of the Zlib surface, validated against the ruby binary on every platform that has one:

  • Deflate / Inflate — zlib-stream compression at any level (Zlib::Deflate.deflate / Zlib::Inflate.inflate).
  • GzipCompress / GzipDecompress — gzip round trip (Zlib::GzipWriter / Zlib::GzipReader), with a deterministic (zero) header mtime.
  • Crc32 / Adler32 and Crc32Combine / Adler32Combine — byte-exact with MRI, with seeded (running) checksums.
  • Streaming Deflater / Inflater — the Zlib::Deflate.new(level).deflate(s, flush).finish / Zlib::Inflate.new.inflate(s) idiom, with the flush modes (NoFlush/SyncFlush/FullFlush/Finish) and the TotalIn / TotalOut / Adler / Finished accessors.
  • Constants — compression levels (NoCompressionDefaultCompression), strategies, flush modes, and Version / ZlibVersion.
  • Errors — an *Error family carrying MRI's class names (Zlib::StreamError, Zlib::BufError, Zlib::DataError, Zlib::GzipFile::Error) so a host maps them straight onto the Ruby exceptions; all wrap to Error via errors.Is.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three OSes (Linux, macOS, Windows).

Install

go get github.com/go-ruby-zlib/zlib

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-zlib/zlib"
)

func main() {
	// Deflate / Inflate — Zlib::Inflate.inflate(Zlib::Deflate.deflate(s)) == s.
	comp, _ := zlib.Deflate([]byte("hello world"), zlib.BestCompression)
	out, _ := zlib.Inflate(comp)
	fmt.Printf("%s\n", out) // hello world

	// Byte-exact checksums (seed 0 for CRC, 1 for Adler — the MRI identities).
	fmt.Println(zlib.Crc32([]byte("hello world"), 0))   // 222957957
	fmt.Println(zlib.Adler32([]byte("hello world"), 1)) // 436929629

	// Streaming, like Zlib::Deflate.new(level).deflate(a, SYNC_FLUSH)…finish.
	d := zlib.NewDeflater(zlib.BestCompression)
	p1, _ := d.Deflate([]byte("hello "), zlib.SyncFlush)
	p2, _ := d.Deflate([]byte("world"), zlib.NoFlush)
	tail, _ := d.Finish()
	stream := append(append(p1, p2...), tail...)
	got, _ := zlib.Inflate(stream)
	fmt.Printf("%s in=%d out=%d adler=%d\n", got, d.TotalIn(), d.TotalOut(), d.Adler())
}

API

// One-shot
func Deflate(data []byte, level int) ([]byte, error) // Zlib::Deflate.deflate
func Inflate(data []byte) ([]byte, error)            // Zlib::Inflate.inflate
func GzipCompress(data []byte, level int) ([]byte, error)
func GzipDecompress(data []byte) ([]byte, error)

// Checksums (byte-exact with MRI)
func Crc32(data []byte, seed uint32) uint32
func Adler32(data []byte, seed uint32) uint32
func Crc32Combine(crc1, crc2 uint32, len2 int64) uint32
func Adler32Combine(adler1, adler2 uint32, len2 int64) uint32
func Crc32Table() *crc32.Table

// Streaming
type Deflater struct{ /* … */ }
func NewDeflater(level int) *Deflater
func NewDeflaterLevel(level int) (*Deflater, error)
func (d *Deflater) Deflate(data []byte, flush int) ([]byte, error)
func (d *Deflater) Finish() ([]byte, error)
func (d *Deflater) TotalIn() int64
func (d *Deflater) TotalOut() int64
func (d *Deflater) Adler() uint32
func (d *Deflater) Finished() bool

type Inflater struct{ /* … */ }
func NewInflater() *Inflater
func (i *Inflater) Inflate(data []byte) ([]byte, error)
func (i *Inflater) Finish() ([]byte, error)
func (i *Inflater) TotalIn() int64
func (i *Inflater) TotalOut() int64
func (i *Inflater) Adler() uint32
func (i *Inflater) Finished() bool

// Errors — *Error carries the MRI exception Class name; all wrap to Error.
type Error struct{ Class, Msg string /* … */ }
var ErrStream, ErrBuf, ErrData, ErrGzipFile *Error
Constants
Go Ruby value
NoCompression Zlib::NO_COMPRESSION 0
BestSpeed Zlib::BEST_SPEED 1
BestCompression Zlib::BEST_COMPRESSION 9
DefaultCompression Zlib::DEFAULT_COMPRESSION -1
DefaultStrategy / Filtered / HuffmanOnly / RLE / Fixed Zlib::*_STRATEGY / … 0..4
NoFlush / SyncFlush / FullFlush / Finish Zlib::*_FLUSH / Zlib::FINISH 0/2/3/4
Version / ZlibVersion Zlib::VERSION / Zlib::ZLIB_VERSION "3.2.3" / "1.2.12"¹

¹ Version is the Ruby binding version (stable). ZlibVersion stands in for the linked C zlib library version, which in MRI varies by host build ("1.2.12", "1.3", …); this pure-Go port has no C zlib and reports a representative constant.

Performance

The DEFLATE engine is klauspost/compress rather than the standard library's compress/flate, whose DefaultCompression match-finder is markedly slower on realistic data. Go-level throughput on a 1 MiB semi-compressible payload (go test -bench, Apple M-class, Go 1.26.4):

Operation stdlib compress/flate klauspost Speedup
Deflate (default level) 30 MB/s 299 MB/s ≈10×
Inflate 470 MB/s 521 MB/s ≈1.1×
Crc32 (arm64 PMULL fold-by-8) 10.4 GB/s ≈51 GB/s ≈4.3×
deflate + inflate + crc32 28 MB/s 187 MB/s ≈6.7×

The combined deflate+inflate+crc32 workload — the one previously measured ~6.8× slower than MRI from rbgo — is now ≈6.7× faster than the old stdlib path, which closes essentially all of that gap. CRC-32 additionally gained a carryless-multiply kernel: on arm64 the standard library's IEEE path is a latency-bound serial CRC32X, and the fold-by-eight PMULL kernel here runs ≈4.3× faster (≈51 GB/s), now beating MRI's C zlib and YJIT on the library-level benchmark (see docs/performance). Wire compatibility is unaffected: the output is still a standard zlib/gzip/raw-DEFLATE stream that MRI's Zlib inflates, validated by the differential MRI oracle below.

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle (gated to RUBY_VERSION >= "4.0"): checksums and combine values are compared byte-exact with the system ruby, and the deflate / gzip / streaming paths are round-tripped through MRI in both directions — we inflate MRI's output and MRI inflates ours, with gzip compared by decompressed payload + CRC (not raw bytes). The oracle scripts $stdout.binmode and $stdin.binmode so Windows text-mode never pollutes the binary payloads, and skip themselves where ruby is absent or older than 4.0.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-zlib/zlib authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package zlib is a pure-Go (no cgo) reimplementation of the public surface of Ruby's `zlib` standard library (the MRI 4.0.5 `Zlib` module). The DEFLATE engine is github.com/klauspost/compress (its drop-in compress/flate, compress/zlib and compress/gzip packages) — a pure-Go, CGO=0, build-from-source dependency that is substantially faster than the standard library's flate. The checksums are SIMD-accelerated: CRC-32 folds with the host's carryless-multiply unit (go-simd/crc32, PCLMULQDQ/PMULL) and Adler-32 uses go-simd/adler32, each a bit-exact drop-in for the standard library's kernel. So a host such as go-embedded-ruby can offer `require "zlib"` with no C extension and a static, CGO=0 binary.

The checksums (Crc32 / Adler32 and their combine forms) are byte-exact with MRI: a value computed here equals the value MRI prints for the same input. The compressors round-trip and interoperate with MRI (Inflate(MRIdeflate) and MRI-inflate of Deflate output both succeed), but the exact deflate byte stream is implementation-defined and need not equal MRI's — zlib never promises a canonical encoding, and Go's flate writer differs from zlib's. For gzip the same holds, plus the header carries an mtime/OS byte; compare the decompressed payload (and its CRC), not the raw gzip bytes.

Index

Constants

View Source
const (
	NoCompression      = flate.NoCompression      // 0
	BestSpeed          = flate.BestSpeed          // 1
	BestCompression    = flate.BestCompression    // 9
	DefaultCompression = flate.DefaultCompression // -1
)

Compression levels — the values MRI exposes as Zlib::NO_COMPRESSION etc. They coincide with the compress/flate constants.

View Source
const (
	DefaultStrategy = 0 // Zlib::DEFAULT_STRATEGY
	Filtered        = 1 // Zlib::FILTERED
	HuffmanOnly     = 2 // Zlib::HUFFMAN_ONLY
	RLE             = 3 // Zlib::RLE
	Fixed           = 4 // Zlib::FIXED
)

Compression strategies (Zlib::*_STRATEGY / Zlib::FILTERED …). Go's flate does not act on a strategy, so these are accepted for parity and validation but do not change the output; DefaultStrategy is the only one that compresses normally, the others are tolerated.

View Source
const (
	NoFlush   = 0 // Zlib::NO_FLUSH
	SyncFlush = 2 // Zlib::SYNC_FLUSH
	FullFlush = 3 // Zlib::FULL_FLUSH
	Finish    = 4 // Zlib::FINISH
)

Flush modes for streaming Deflate/Inflate (Zlib::NO_FLUSH …). The numeric values match MRI (and zlib): NO_FLUSH=0, SYNC_FLUSH=2, FULL_FLUSH=3, FINISH=4.

View Source
const (
	Version     = "3.2.3"  // Zlib::VERSION (Ruby binding)
	ZlibVersion = "1.2.12" // Zlib::ZLIB_VERSION (representative C-lib version)
)

Version strings a host surfaces as Zlib::VERSION / Zlib::ZLIB_VERSION. VERSION is the Ruby zlib binding version (stable across MRI 4.0 builds). ZlibVersion stands in for the linked C zlib library version, which in MRI varies by host build (e.g. "1.2.12" or "1.3"); this port has no C zlib, so it reports a representative constant rather than a build-specific one.

Variables

View Source
var (
	// ErrStream is Zlib::StreamError — an invalid argument such as a bad
	// compression level.
	ErrStream = &Error{Class: "Zlib::StreamError", Msg: "stream error"}
	// ErrBuf is Zlib::BufError — a buffer/flush condition (e.g. a stream that
	// produced no progress).
	ErrBuf = &Error{Class: "Zlib::BufError", Msg: "buffer error"}
	// ErrData is Zlib::DataError — corrupt or invalid compressed input.
	ErrData = &Error{Class: "Zlib::DataError", Msg: "incorrect header check"}
	// ErrGzipFile is Zlib::GzipFile::Error — a malformed gzip stream.
	ErrGzipFile = &Error{Class: "Zlib::GzipFile::Error", Msg: "not in gzip format"}
)

The sentinel leaf errors. They carry MRI's class name and default message; the wrap* helpers below clone them with the underlying library error attached so errors.Is still matches while errors.Unwrap reaches the cause.

Functions

func Adler32

func Adler32(data []byte, seed uint32) uint32

Adler32 returns the Adler-32 checksum of data continued from seed, mirroring Zlib.adler32(data, seed). The MRI default seed is 1 (the Adler-32 identity); callers wanting MRI's zero-argument behaviour pass 1. The value is byte-exact with MRI. From the identity seed it delegates to hash/adler32; from any other running value it continues the sum directly, since the standard library exposes only the from-scratch form.

func Adler32Combine

func Adler32Combine(adler1, adler2 uint32, len2 int64) uint32

Adler32Combine combines two Adler-32 checksums as if the two byte runs had been checksummed as one, mirroring Zlib.adler32_combine(adler1, adler2, len2).

func Crc32

func Crc32(data []byte, seed uint32) uint32

Crc32 returns the CRC-32 (IEEE) checksum of data continued from seed, mirroring Zlib.crc32(data, seed). With seed 0 and the default arguments it is the plain checksum; passing a running value lets a caller checksum a stream in pieces. The value is byte-exact with MRI.

func Crc32Combine

func Crc32Combine(crc1, crc2 uint32, len2 int64) uint32

Crc32Combine combines two CRC-32 checksums as if the two byte runs had been checksummed as one, mirroring Zlib.crc32_combine(crc1, crc2, len2). len2 is the byte length of the second run.

func Crc32Table

func Crc32Table() *crc32.Table

Crc32Table exposes the IEEE polynomial table for callers that need it (analogous to giving access to MRI's internal table); it is the same table hash/crc32 uses.

func Deflate

func Deflate(data []byte, level int) ([]byte, error)

Deflate compresses data into a zlib stream at the given level, mirroring Zlib::Deflate.deflate(data, level). level is DefaultCompression or 0..9; an out-of-range level returns ErrStream (MRI's Zlib::StreamError). The output is a valid zlib stream that Inflate and MRI both decode, though its exact bytes are not guaranteed to equal MRI's.

func GzipCompress

func GzipCompress(data []byte, level int) ([]byte, error)

GzipCompress compresses data into a gzip stream at the given level, mirroring a Zlib::GzipWriter round trip. The gzip header's mtime field is fixed at zero so the output is deterministic across runs (MRI defaults it to the current time); an out-of-range level returns ErrStream. The exact bytes still differ from MRI's (header OS byte and flate encoding); decode and compare the payload.

func GzipDecompress

func GzipDecompress(data []byte) ([]byte, error)

GzipDecompress decompresses a gzip stream, mirroring a Zlib::GzipReader read of the whole file. A bad gzip header or corrupt body (including a checksum mismatch) returns ErrGzipFile (MRI's Zlib::GzipFile::Error family).

func Inflate

func Inflate(data []byte) ([]byte, error)

Inflate decompresses a zlib stream, mirroring Zlib::Inflate.inflate(data). A bad zlib header or truncated/corrupt body returns ErrData (MRI's Zlib::DataError).

Types

type Deflater

type Deflater struct {
	// contains filtered or unexported fields
}

Deflater is the streaming compressor, mirroring a Zlib::Deflate instance:

z := zlib.NewDeflater(zlib.BestCompression)
a, _ := z.Deflate([]byte("hello "), zlib.SyncFlush)
b, _ := z.Deflate([]byte("world"), zlib.NoFlush)
tail, _ := z.Finish()
stream := append(append(a, b...), tail...) // a valid zlib stream

Bytes produced are accumulated internally; each Deflate / Finish call returns only the bytes that became available since the previous call (as MRI's Zlib::Deflate#deflate does). TotalIn / TotalOut / Adler / Finished track the stream as MRI's accessors do.

func NewDeflater

func NewDeflater(level int) *Deflater

NewDeflater creates a streaming compressor at the given level, falling back to DefaultCompression when level is out of range. Use NewDeflaterLevel to detect an invalid level (which MRI reports as Zlib::StreamError) instead of defaulting it.

func NewDeflaterLevel

func NewDeflaterLevel(level int) (*Deflater, error)

NewDeflaterLevel creates a streaming compressor, returning ErrStream for an out-of-range level (MRI raises Zlib::StreamError from Zlib::Deflate.new).

func (*Deflater) Adler

func (d *Deflater) Adler() uint32

Adler reports the running Adler-32 of the input (Zlib::Deflate#adler).

func (*Deflater) Deflate

func (d *Deflater) Deflate(data []byte, flush int) ([]byte, error)

Deflate feeds data into the stream and returns the compressed bytes that became available, mirroring Zlib::Deflate#deflate(data, flush). flush is one of NoFlush / SyncFlush / FullFlush / Finish. With Finish the stream is closed (as Zlib::Deflate#deflate(data, Zlib::FINISH) does) and Finished becomes true.

func (*Deflater) Finish

func (d *Deflater) Finish() ([]byte, error)

Finish closes the stream and returns any remaining compressed bytes, mirroring Zlib::Deflate#finish. After Finish, Finished reports true and further Deflate calls return ErrStream. Calling Finish again on an already-finished Deflater is tolerated and returns an empty slice (no error), matching MRI, whose Zlib::Deflate#finish returns "" when re-invoked rather than raising.

func (*Deflater) Finished

func (d *Deflater) Finished() bool

Finished reports whether the stream has been finished (Zlib::Deflate#finished?).

func (*Deflater) TotalIn

func (d *Deflater) TotalIn() int64

TotalIn reports the number of uncompressed bytes fed in (Zlib::Deflate#total_in).

func (*Deflater) TotalOut

func (d *Deflater) TotalOut() int64

TotalOut reports the number of compressed bytes produced (Zlib::Deflate#total_out).

type Error

type Error struct {
	// Class is the MRI exception class name a host should raise, e.g.
	// "Zlib::DataError"; it lets the binding pick the exact Ruby class.
	Class string
	// Msg is the message MRI uses for the condition.
	Msg string
	// contains filtered or unexported fields
}

Error is the base of the Zlib error family, mirroring Zlib::Error (a StandardError subclass in MRI). Every error this package returns wraps to Error via errors.Is, so a host can map the whole family to Zlib::Error and the leaf classes (StreamError / BufError / DataError / GzipFile::Error) to the matching Ruby exceptions.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface with MRI's message text.

func (*Error) Is

func (e *Error) Is(target error) bool

Is lets errors.Is(err, ErrXxx) match by MRI class name, so a caller can test errors.Is(err, ErrData) regardless of the wrapped library error.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap exposes the underlying standard-library error for errors.Is/As.

type Inflater

type Inflater struct {
	// contains filtered or unexported fields
}

Inflater is the streaming decompressor, mirroring a Zlib::Inflate instance. inflate accumulates the compressed input and decodes as much as it can; the MRI streaming idiom Zlib::Inflate.new.inflate(stream) is supported by feeding a complete stream in one call.

func NewInflater

func NewInflater() *Inflater

NewInflater creates a streaming decompressor (Zlib::Inflate.new).

func (*Inflater) Adler

func (inf *Inflater) Adler() uint32

Adler reports the running Adler-32 of the output (Zlib::Inflate#adler).

func (*Inflater) Finish

func (inf *Inflater) Finish() ([]byte, error)

Finish completes decompression, mirroring Zlib::Inflate#finish; for this one-shot streaming model it is a no-op that marks the inflater finished and returns no further bytes.

func (*Inflater) Finished

func (inf *Inflater) Finished() bool

Finished reports whether the stream has been fully inflated (Zlib::Inflate#finished?).

func (*Inflater) Inflate

func (inf *Inflater) Inflate(data []byte) ([]byte, error)

Inflate feeds compressed data and returns the decompressed bytes decoded so far, mirroring Zlib::Inflate#inflate(data). The accumulated input must form a complete zlib stream by the time output is required; a complete stream (the common one-shot streaming use) decodes fully and marks the inflater finished. Corrupt input returns ErrData.

Calling Inflate on an already-finished Inflater is tolerated and returns an empty slice (no error), matching MRI: once the stream end has been reached, Zlib::Inflate#inflate returns "" for any further input (including non-empty data) rather than raising.

func (*Inflater) TotalIn

func (inf *Inflater) TotalIn() int64

TotalIn reports the number of compressed bytes consumed (Zlib::Inflate#total_in).

func (*Inflater) TotalOut

func (inf *Inflater) TotalOut() int64

TotalOut reports the number of decompressed bytes produced (Zlib::Inflate#total_out).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL