Basic Usage
Common patterns for working with atom.
Registration
Single Type
atomizer, err := atom.Use[User]()
if err != nil {
log.Fatalf("failed to register User: %v", err)
}
Multiple Types
Register all types at startup for predictable initialization:
func init() {
types := []func() error{
func() error { _, err := atom.Use[User](); return err },
func() error { _, err := atom.Use[Order](); return err },
func() error { _, err := atom.Use[Product](); return err },
}
for _, register := range types {
if err := register(); err != nil {
log.Fatalf("type registration failed: %v", err)
}
}
}
Handling Errors
Registration fails for unsupported types:
type Invalid struct {
Data map[string]any // Maps are not supported
}
_, err := atom.Use[Invalid]()
// err: "type Invalid: field "Data": map types are not supported"
Atomization
Basic Atomize
user := &User{Name: "Alice", Age: 30}
atom := atomizer.Atomize(user)
Creating Empty Atoms
Use NewAtom() to create a properly sized empty atom:
atom := atomizer.NewAtom()
atom.Strings["Name"] = "Bob"
atom.Ints["Age"] = 25
Reading Fields
Access fields through type-specific maps:
atom := atomizer.Atomize(user)
name := atom.Strings["Name"]
age := atom.Ints["Age"]
active := atom.Bools["Active"]
created := atom.Times["CreatedAt"]
Checking Field Existence
if name, ok := atom.Strings["Name"]; ok {
fmt.Println("Name:", name)
}
Deatomization
Basic Deatomize
restored, err := atomizer.Deatomize(atom)
if err != nil {
log.Printf("deatomize failed: %v", err)
return
}
Handling Errors
Deatomization can fail for overflow:
type Small struct {
Value int8
}
atomizer, _ := atom.Use[Small]()
a := &atom.Atom{Ints: map[string]int64{"Value": 200}}
_, err := atomizer.Deatomize(a)
// err: "value 200 overflows int8 (range -128 to 127)"
Missing Fields
Missing fields are left at their zero value:
atom := &atom.Atom{
Strings: map[string]string{"Name": "Alice"},
// Age not set
}
user, _ := atomizer.Deatomize(atom)
fmt.Println(user.Name) // "Alice"
fmt.Println(user.Age) // 0 (zero value)
Field Introspection
List All Fields
fields := atomizer.Fields()
for _, f := range fields {
fmt.Printf("%s -> %s\n", f.Name, f.Table)
}
// ID -> ints
// Name -> strings
// Email -> strings
Fields by Table
stringFields := atomizer.FieldsIn(atom.TableStrings)
// ["Name", "Email"]
intFields := atomizer.FieldsIn(atom.TableInts)
// ["ID", "Age"]
Get Table for Field
table, ok := atomizer.TableFor("Age")
if ok {
fmt.Println("Age is stored in:", table) // "ints"
}
Working with Pointers
Pointer Fields
Pointer fields use separate tables and can be nil:
type Config struct {
Name string
MaxRetry *int64
Timeout *float64
}
atomizer, _ := atom.Use[Config]()
cfg := &Config{Name: "default", MaxRetry: nil}
atom := atomizer.Atomize(cfg)
// atom.Strings["Name"] = "default"
// atom.IntPtrs["MaxRetry"] = nil
Setting Pointer Values
atom := atomizer.NewAtom()
atom.Strings["Name"] = "custom"
retries := int64(3)
atom.IntPtrs["MaxRetry"] = &retries
cfg, _ := atomizer.Deatomize(atom)
fmt.Println(*cfg.MaxRetry) // 3
Working with Slices
Slice Fields
type User struct {
Name string
Tags []string
Scores []int64
}
atomizer, _ := atom.Use[User]()
user := &User{
Name: "Alice",
Tags: []string{"admin", "verified"},
Scores: []int64{95, 87, 92},
}
atom := atomizer.Atomize(user)
// atom.Strings["Name"] = "Alice"
// atom.StringSlices["Tags"] = ["admin", "verified"]
// atom.IntSlices["Scores"] = [95, 87, 92]
Empty vs Nil Slices
// Nil slice - no entry in atom
user := &User{Tags: nil}
atom := atomizer.Atomize(user)
_, ok := atom.StringSlices["Tags"] // ok = false
// Empty slice - empty entry in atom
user := &User{Tags: []string{}}
atom := atomizer.Atomize(user)
tags := atom.StringSlices["Tags"] // tags = []
Type Metadata
Access Spec
spec := atomizer.Spec()
fmt.Println(spec.TypeName) // "User"
fmt.Println(spec.PackageName) // "github.com/example/app"
Atom Spec
Each atom carries its type spec:
atom := atomizer.Atomize(user)
fmt.Println(atom.Spec.TypeName) // "User"
Best Practices
Do: Register Early
Register types at application startup:
func main() {
// Register all types first
userAtomizer, _ = atom.Use[User]()
orderAtomizer, _ = atom.Use[Order]()
// Then use them
runApp()
}
Do: Reuse Atomizers
Store atomizers rather than calling Use repeatedly:
// Good
var userAtomizer *atom.Atomizer[User]
func init() {
userAtomizer, _ = atom.Use[User]()
}
func ProcessUser(u *User) *atom.Atom {
return userAtomizer.Atomize(u)
}
Don't: Ignore Errors
Always check deatomization errors:
// Bad
user, _ := atomizer.Deatomize(atom)
// Good
user, err := atomizer.Deatomize(atom)
if err != nil {
return fmt.Errorf("deatomize: %w", err)
}
Next Steps
- Custom Types Guide - Named types and byte arrays
- Nested Structs Guide - Working with nested data
- Interfaces Guide - Custom serialization