or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cmp-core.mdcmp-options.mdcmp-path.mdcmpopts.mdindex.md
tile.json

cmp-core.mddocs/

Core Comparison

The cmp package provides two fundamental functions for comparing values: Equal for boolean equality checks and Diff for generating human-readable difference reports.

Import

import "github.com/google/go-cmp/cmp"

Core Functions

Equal

func Equal(x, y interface{}, opts ...Option) bool

Equal reports whether x and y are equal by recursively applying comparison rules to x, y, and all their sub-values.

Parameters:

  • x interface{} - First value to compare
  • y interface{} - Second value to compare
  • opts ...Option - Optional configuration options

Returns:

  • bool - True if values are equal according to comparison rules

Comparison Rules (applied in order):

  1. Options Evaluation: Applies Ignore, Transformer, and Comparer options that match the current path and values. Panics if multiple conflicting options match.

  2. Equal Method: If values have an Equal method of form (T) Equal(T) bool or (T) Equal(I) bool, uses that result (even if x or y is nil).

  3. Basic Kinds: Compares based on kind:

    • Booleans, integers, floats, complex numbers, strings, channels: Uses == operator
    • Functions: Equal only if both nil
    • Structs: Recursively compares all fields. Panics on unexported fields unless handled by options.
    • Slices: Both must be nil or both non-nil. Recursively compares elements. Empty non-nil and nil slices are NOT equal.
    • Maps: Both must be nil or both non-nil. Recursively compares entries. Keys compared with ==. Empty non-nil and nil maps are NOT equal.
    • Pointers/Interfaces: Both must be nil or both non-nil with same concrete type. Recursively compares underlying values.

Cycle Detection: Before recursing into pointers, slices, or maps, checks if address was already visited. Cycles are equal only if both addresses were visited at same path step.

Example:

type Config struct {
    Host string
    Port int
}

cfg1 := Config{Host: "localhost", Port: 8080}
cfg2 := Config{Host: "localhost", Port: 8080}
cfg3 := Config{Host: "localhost", Port: 9000}

cmp.Equal(cfg1, cfg2) // returns true
cmp.Equal(cfg1, cfg3) // returns false

Example with Options:

import "github.com/google/go-cmp/cmp/cmpopts"

type Person struct {
    Name string
    age  int // unexported
}

p1 := Person{Name: "Alice", age: 30}
p2 := Person{Name: "Alice", age: 30}

// Would panic due to unexported field
// cmp.Equal(p1, p2)

// Ignore unexported fields
cmp.Equal(p1, p2, cmpopts.IgnoreUnexported(Person{})) // returns true

Diff

func Diff(x, y interface{}, opts ...Option) string

Diff returns a human-readable report of the differences between two values: y - x. Returns an empty string if and only if Equal returns true for the same inputs and options.

Parameters:

  • x interface{} - First value (baseline)
  • y interface{} - Second value (comparison)
  • opts ...Option - Optional configuration options (same as Equal)

Returns:

  • string - Human-readable diff in pseudo-Go syntax, or empty string if equal

Output Format:

  • Lines prefixed with "-" indicate elements removed from x
  • Lines prefixed with "+" indicate elements added from y
  • Lines without prefix indicate elements common to both
  • If values implement fmt.Stringer or error interface, uses those methods (prefixed with 's' or 'e')

Note: Output format is not stable and should not be parsed programmatically. Use custom Reporter for programmatic interpretation.

Example:

type User struct {
    Name  string
    Email string
    Age   int
}

got := User{
    Name:  "Alice",
    Email: "alice@example.com",
    Age:   30,
}

want := User{
    Name:  "Alice",
    Email: "alice@newdomain.com",
    Age:   31,
}

diff := cmp.Diff(want, got)
if diff != "" {
    fmt.Printf("User mismatch (-want +got):\n%s", diff)
}

// Output might look like:
//   main.User{
//       Name: "Alice",
// -     Email: "alice@newdomain.com",
// +     Email: "alice@example.com",
// -     Age: 31,
// +     Age: 30,
//   }

Example in Tests:

func TestProcessData(t *testing.T) {
    got := ProcessData(input)
    want := ExpectedResult{
        Count: 5,
        Items: []string{"a", "b", "c"},
    }

    if diff := cmp.Diff(want, got); diff != "" {
        t.Errorf("ProcessData() mismatch (-want +got):\n%s", diff)
    }
}

Basic Options

AllowUnexported

func AllowUnexported(types ...interface{}) Option

AllowUnexported returns an Option that allows Equal to forcibly introspect unexported fields of the specified struct types.

Parameters:

  • types ...interface{} - Struct types to allow unexported field access (pass values of the struct types)

Returns:

  • Option - Configuration option

Note: Prefer using Exporter or cmpopts.IgnoreUnexported for more controlled access. See Exporter documentation for proper use.

Example:

type Internal struct {
    publicField  string
    privateField int
}

i1 := Internal{publicField: "test", privateField: 42}
i2 := Internal{publicField: "test", privateField: 42}

// Allow comparing unexported fields
cmp.Equal(i1, i2, cmp.AllowUnexported(Internal{})) // returns true

Exporter

func Exporter(f func(reflect.Type) bool) Option

Exporter returns an Option that specifies whether Equal is allowed to introspect into unexported fields of certain struct types.

Parameters:

  • f func(reflect.Type) bool - Function that returns true if type's unexported fields can be accessed

Returns:

  • Option - Configuration option

Safety Warning: Comparing unexported fields from external packages is not safe since internal implementation changes may cause unexpected results. For external types, prefer custom Comparers that define equality based on public API.

When to Use:

  • Types defined in internal packages where you control the semantic meaning
  • Types where unexported fields are part of the logical equality

Recommended Alternatives:

  • Custom Comparer based on public API (e.g., for reflect.Type, *regexp.Regexp)
  • cmpopts.IgnoreUnexported to ignore all unexported fields

Example:

import "reflect"

// Allow unexported fields only for types in our package
isOurType := func(t reflect.Type) bool {
    return strings.HasPrefix(t.PkgPath(), "mycompany.com/mypackage")
}

cmp.Equal(x, y, cmp.Exporter(isOurType))

Example with Comparer (preferred for external types):

import (
    "reflect"
    "regexp"
)

// Compare reflect.Type by pointer equality
typeComparer := cmp.Comparer(func(x, y reflect.Type) bool {
    return x == y
})

// Compare regexp by string representation
regexpComparer := cmp.Comparer(func(x, y *regexp.Regexp) bool {
    return x.String() == y.String()
})

cmp.Equal(x, y, typeComparer, regexpComparer)