Custom Types
Working with named types, enums, and byte arrays.
Named Types
Named types (type aliases with semantic meaning) are fully supported.
String-based Types
type UserID string
type Email string
type Currency string
type User struct {
ID UserID
Email Email
Currency Currency
}
atomizer, _ := atom.Use[User]()
user := &User{
ID: "usr_123",
Email: "alice@example.com",
Currency: "USD",
}
atom := atomizer.Atomize(user)
// All stored in Strings table
// atom.Strings["ID"] = "usr_123"
// atom.Strings["Email"] = "alice@example.com"
// atom.Strings["Currency"] = "USD"
Integer-based Types (Enums)
type Status int
type Priority int
const (
StatusPending Status = iota
StatusActive
StatusComplete
)
const (
PriorityLow Priority = iota
PriorityMedium
PriorityHigh
)
type Task struct {
Name string
Status Status
Priority Priority
}
atomizer, _ := atom.Use[Task]()
task := &Task{
Name: "Review PR",
Status: StatusActive,
Priority: PriorityHigh,
}
atom := atomizer.Atomize(task)
// atom.Strings["Name"] = "Review PR"
// atom.Ints["Status"] = 1
// atom.Ints["Priority"] = 2
Float-based Types
type Score float64
type Percentage float64
type Result struct {
Score Score
Confidence Percentage
}
atomizer, _ := atom.Use[Result]()
result := &Result{Score: 95.5, Confidence: 0.87}
atom := atomizer.Atomize(result)
// atom.Floats["Score"] = 95.5
// atom.Floats["Confidence"] = 0.87
Named Slices
Slice of Named Types
type Tag string
type UserID string
type User struct {
ID UserID
Tags []Tag
}
atomizer, _ := atom.Use[User]()
user := &User{
ID: "usr_123",
Tags: []Tag{"admin", "verified", "premium"},
}
atom := atomizer.Atomize(user)
// atom.Strings["ID"] = "usr_123"
// atom.StringSlices["Tags"] = ["admin", "verified", "premium"]
Round-trip Preservation
Named types are preserved through atomization:
restored, _ := atomizer.Deatomize(atom)
fmt.Printf("%T\n", restored.ID) // main.UserID
fmt.Printf("%T\n", restored.Tags) // []main.Tag
Byte Slice Types
Named byte Types
Common examples: net.IP, json.RawMessage, custom binary types.
type IP []byte
type Hash []byte
type RawJSON []byte
type Record struct {
ClientIP IP
Checksum Hash
Payload RawJSON
}
atomizer, _ := atom.Use[Record]()
record := &Record{
ClientIP: IP{192, 168, 1, 1},
Checksum: Hash{0xde, 0xad, 0xbe, 0xef},
Payload: RawJSON(`{"key": "value"}`),
}
atom := atomizer.Atomize(record)
// All stored in Bytes table
// atom.Bytes["ClientIP"] = [192, 168, 1, 1]
// atom.Bytes["Checksum"] = [0xde, 0xad, 0xbe, 0xef]
// atom.Bytes["Payload"] = [123, 34, 107, ...]
net.IP Example
import "net"
type Connection struct {
LocalAddr net.IP
RemoteAddr net.IP
Port int64
}
atomizer, _ := atom.Use[Connection]()
conn := &Connection{
LocalAddr: net.ParseIP("127.0.0.1"),
RemoteAddr: net.ParseIP("10.0.0.1"),
Port: 8080,
}
atom := atomizer.Atomize(conn)
// atom.Bytes["LocalAddr"] = [127, 0, 0, 1]
// atom.Bytes["RemoteAddr"] = [10, 0, 0, 1]
// atom.Ints["Port"] = 8080
Fixed-Size Byte Arrays
Arrays of bytes [N]byte are supported and stored in the Bytes table.
Basic Usage
type HashID [32]byte
type UUID [16]byte
type Document struct {
ID UUID
Hash HashID
}
atomizer, _ := atom.Use[Document]()
var id UUID
copy(id[:], "0123456789abcdef")
var hash HashID
copy(hash[:], "abcdefghijklmnopqrstuvwxyz012345")
doc := &Document{ID: id, Hash: hash}
atom := atomizer.Atomize(doc)
// atom.Bytes["ID"] = [48, 49, 50, 51, ...] (16 bytes)
// atom.Bytes["Hash"] = [97, 98, 99, 100, ...] (32 bytes)
Size Validation
During deatomization, byte slice length must match array size:
type Fixed struct {
Data [4]byte
}
atomizer, _ := atom.Use[Fixed]()
// Correct size
atom := &atom.Atom{Bytes: map[string][]byte{"Data": {1, 2, 3, 4}}}
fixed, err := atomizer.Deatomize(atom) // OK
// Wrong size
atom = &atom.Atom{Bytes: map[string][]byte{"Data": {1, 2}}}
_, err = atomizer.Deatomize(atom)
// err: "field Data: expected 4 bytes, got 2"
Pointer to Named Types
type UserID string
type Score float64
type OptionalUser struct {
ID *UserID
Score *Score
}
atomizer, _ := atom.Use[OptionalUser]()
id := UserID("usr_123")
score := Score(95.5)
user := &OptionalUser{ID: &id, Score: &score}
atom := atomizer.Atomize(user)
// atom.StringPtrs["ID"] = &"usr_123"
// atom.FloatPtrs["Score"] = &95.5
// With nil values
user = &OptionalUser{ID: nil, Score: nil}
atom = atomizer.Atomize(user)
// atom.StringPtrs["ID"] = nil
// atom.FloatPtrs["Score"] = nil
Type Mapping Reference
| Named Type Base | Storage Table |
|---|---|
type X string | strings |
type X int* | ints |
type X uint* | uints |
type X float* | floats |
type X bool | bools |
type X []byte | bytes |
type X [N]byte | bytes |
type X []string (etc) | corresponding slice table |
type X *string (etc) | corresponding pointer table |
Best Practices
Use Named Types for Domain Concepts
// Good - clear semantics
type UserID string
type Email string
type Money int64 // cents
type User struct {
ID UserID
Email Email
Balance Money
}
// Bad - primitive soup
type User struct {
ID string
Email string
Balance int64
}
Use Enums Instead of Strings
// Good - type-safe
type Status int
const (
StatusActive Status = iota
StatusInactive
)
// Bad - error-prone
type User struct {
Status string // "active", "inactive", typos possible
}
Next Steps
- Nested Structs Guide - Working with nested data
- Interfaces Guide - Custom serialization