immutable

command
v0.0.0-...-2fb256d Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 10 Imported by: 0

README

immutable

immutable generates an immutable (read-only) twin of an existing Go struct: a value type whose fields cannot be mutated after construction, with getters for every exported field, copy-on-write setters that return a new value, and conversions to and from the original mutable struct.

It is the code-generation companion to the github.com/gostdlib/base/values/immutable package, whose Map and Slice types it uses for map and slice fields so they can't be mutated through a shared reference.

Install

The tool is registered in this module's go.mod tool block, so it runs via:

go tool github.com/gostdlib/base/values/generators/immutable -type StructName

In another module, add it as a tool dependency first:

go get -tool github.com/gostdlib/base/values/generators/immutable

Usage

Add a //go:generate directive in the package that declares the struct, then run go generate ./...:

//go:generate go tool github.com/gostdlib/base/values/generators/immutable -type User

type User struct {
	ID    uint64
	Name  string
	Tags  map[string]string
	Roles []string
	secret string
}

The tool scans every non-generated *.go file in its directory for the named struct, so run go generate from that directory (or ./... from the module root).

Flags
Flag Description
-type (required) Name of the struct to make immutable. Output is written to <StructName>_immutable.go.

One struct per invocation — add a directive for each struct you want an immutable version of.

Generated code

The directive above produces User_immutable.go:

type ImUser struct {
	id     uint64
	name   string
	tags   immutable.Map[string, string]
	roles  immutable.Slice[string]
	secret string
}

func (r *ImUser) GetID() uint64
func (r *ImUser) SetID(value uint64) ImUser
func (r *ImUser) GetName() string
func (r *ImUser) SetName(value string) ImUser
func (r *ImUser) GetTags() immutable.Map[string, string]
func (r *ImUser) SetTags(value immutable.Map[string, string]) ImUser
func (r *ImUser) GetRoles() immutable.Slice[string]
func (r *ImUser) SetRoles(value immutable.Slice[string]) ImUser

func (r *ImUser) Mutable() User    // back to the mutable struct
func (r *User) Immutable() ImUser  // into the immutable twin

Key properties of the generated type:

  • Every field is unexported, so it can only be read through a Get<Field> method and replaced through a Set<Field> method. There is no way to mutate an ImUser in place.
  • Setters are copy-on-write. Set<Field> returns a new value with that one field changed and leaves the receiver untouched.
  • map and slice fields become immutable. A map[K]V field becomes an immutable.Map[K, V] and a []T field becomes an immutable.Slice[T], so callers can't reach in and mutate the backing collection. Mutable() copies them back out into ordinary maps and slices.
  • Unexported fields are preserved through Immutable() / Mutable() but get no accessors (e.g. secret above).
  • Methods are carried over. Any method declared on the original struct is copied onto the immutable type.
  • Generics are supported — the struct's type parameters are copied to the generated type.
Constraints

The tool refuses to generate (and exits with an error) when it can't preserve immutability:

  • A copied method that mutates a receiver field (assigns to r.<field>) is rejected — it would defeat the immutability guarantee. Make the method return a new value, or remove it, before generating.
  • A public field that collides with an existing private field once lower-cased (e.g. both Hello and hello) is rejected, since both would map to the same unexported field.

Usage example

u := User{ID: 1, Name: "Alice"}.Immutable() // hand out an ImUser; callers can't mutate it

fmt.Println(u.GetName()) // "Alice"

u2 := u.SetName("Bob")   // u is unchanged; u2 is a new value
fmt.Println(u.GetName())  // "Alice"
fmt.Println(u2.GetName()) // "Bob"

m := u.Mutable() // back to a mutable User when you need one
m.Name = "Carol"

Claude Code skill

This package includes a SKILL.md that teaches Claude Code how to generate go:generate directives for this tool. When installed, Claude suggests the correct directive when you ask for an immutable version of a struct.

To install, copy the file into your personal or project skills directory:

# Personal (applies to all your projects)
mkdir -p ~/.claude/skills/immutable
cp SKILL.md ~/.claude/skills/immutable/SKILL.md

# Or project-level (applies to one project)
mkdir -p .claude/skills/immutable
cp SKILL.md .claude/skills/immutable/SKILL.md

You can also invoke it manually in Claude Code with /immutable.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
generate
Package generate contains types and functions that can be used to generate immutable types from existing Go struct types.
Package generate contains types and functions that can be used to generate immutable types from existing Go struct types.

Jump to

Keyboard shortcuts

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