Code Generation
Code generation is an experimental feature in Fory Go. The API and behavior may change in future releases. The reflection-based path remains the stable, recommended approach for most use cases.
Fory Go provides optional ahead-of-time (AOT) code generation for performance-critical paths. This eliminates reflection overhead and provides compile-time type safety.
Why Code Generation?
| Aspect | Reflection-Based | Code Generation |
|---|---|---|
| Setup | Zero configuration | Requires go generate |
| Performance | Good | Better (no reflection) |
| Type Safety | Runtime | Compile-time |
| Maintenance | Automatic | Requires regeneration |
Use code generation when:
- Maximum performance is required
- Compile-time type safety is important
- Hot paths are performance-critical
Use reflection when:
- Simple setup is preferred
- Types change frequently
- Dynamic typing is needed
- Code generation complexity is undesirable
Installation
Install the fory generator binary:
go install github.com/apache/fory/go/fory/cmd/fory@latest
GO111MODULE=on go get -u github.com/apache/fory/go/fory/cmd/fory
Ensure $GOBIN or $GOPATH/bin is in your PATH.
Basic Usage
Step 1: Annotate Structs
Add the //fory:generate comment above structs:
package models
//fory:generate
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
}
//fory:generate
type Order struct {
ID int64
Customer string
Total float64
}
Step 2: Add Go Generate Directive
Add a go:generate directive (once per file or package):
//go:generate fory -file models.go
Or for the entire package:
//go:generate fory -pkg .
Step 3: Run Code Generation
go generate ./...
This creates models_fory_gen.go with generated serializers.
Generated Code Structure
The generator creates:
Type Snapshot
A compile-time check to detect struct changes:
// Snapshot of User's underlying type at generation time
type _User_expected struct {
ID int64
Name string
}
// Compile-time check: fails if User no longer matches
var _ = func(x User) { _ = _User_expected(x) }
Serializer Implementation
Strongly-typed serialization methods:
type User_ForyGenSerializer struct{}
func (User_ForyGenSerializer) WriteTyped(f *fory.Fory, buf *fory.ByteBuffer, v *User) error {
buf.WriteInt64(v.ID)
fory.WriteString(buf, v.Name)
return nil
}
func (User_ForyGenSerializer) ReadTyped(f *fory.Fory, buf *fory.ByteBuffer, v *User) error {
v.ID = buf.ReadInt64()
v.Name = fory.ReadString(buf)
return nil
}
Auto-Registration
Serializers are registered in init():
func init() {
fory.RegisterGenSerializer(User{}, User_ForyGenSerializer{})
}
Command-Line Options
File-Based Generation
Generate for a specific file:
fory -file models.go
Package-Based Generation
Generate for a package:
fory -pkg ./models
Explicit Types (Legacy)
Specify types explicitly:
fory -pkg ./models -type "User,Order"
Force Regeneration
Force regeneration even if up-to-date:
fory --force -file models.go
When to Regenerate
Regenerate when any of these change:
- Field additions, removals, or renames
- Field type changes
- Struct tag changes
- New structs with
//fory:generate
Automatic Detection
Fory includes a compile-time guard:
// If struct changed, this fails to compile
var _ = func(x User) { _ = _User_expected(x) }
If you forget to regenerate, the build fails with a clear message.
Auto-Retry
When invoked via go generate, the generator detects stale code and retries:
- Detects compile error from guard
- Removes stale generated file
- Regenerates fresh code
Supported Types
Code generation supports:
- All primitive types (
bool,int*,uint*,float*,string) - Slices of primitives and structs
- Maps with supported key/value types
- Nested structs (must also be generated)
- Pointers to structs
Nested Structs
All nested structs must also have //fory:generate:
//fory:generate
type Address struct {
City string
Country string
}
//fory:generate
type Person struct {
Name string
Address Address // Address must also be generated
}
CI/CD Integration
Check In Generated Code
Recommended for libraries:
go generate ./...
git add *_fory_gen.go
git commit -m "Regenerate Fory serializers"
Pros: Consumers can build without generator; reproducible builds Cons: Larger diffs; must remember to regenerate
Generate in Pipeline
Recommended for applications:
steps:
- run: go install github.com/apache/fory/go/fory/cmd/fory@latest
- run: go generate ./...
- run: go build ./...
Usage with Generated Code
Generated code integrates transparently:
f := fory.New()
// Fory automatically uses generated serializer if available
user := &User{ID: 1, Name: "Alice"}
data, _ := f.Serialize(user)
var result User
f.Deserialize(data, &result)
No code changes needed - registration happens in init().
Mixing Generated and Non-Generated
You can mix approaches:
//fory:generate
type HotPathStruct struct {
// Performance-critical, use codegen
}
type ColdPathStruct struct {
// Not annotated, uses reflection
}
Limitations
Experimental Status
- API may change
- Not all edge cases tested
- May have undiscovered bugs
Not Supported
- Interface fields (dynamic types)
- Recursive types without pointers
- Private (unexported) fields
- Custom serializers
Reflection Fallback
If codegen fails, Fory falls back to reflection:
// If User_ForyGenSerializer not found, uses reflection
f.Serialize(&User{})
Troubleshooting
"fory: command not found"
Ensure the binary is in PATH:
export PATH=$PATH:$(go env GOPATH)/bin
Compile Error After Struct Change
Regenerate:
go generate ./...
Or force:
fory --force -file yourfile.go
Generated Code Out of Sync
The compile-time guard catches this:
cannot use x (variable of type User) as type _User_expected in argument
Run go generate to fix.
Example Project Structure
myproject/
├── models/
│ ├── models.go # Struct definitions
│ ├── models_fory_gen.go # Generated code
│ └── generate.go # go:generate directive
├── main.go
└── go.mod
models/generate.go:
package models
//go:generate fory -pkg .
models/models.go:
package models
//fory:generate
type User struct {
ID int64
Name string
}
FAQ
Is codegen required?
No. Reflection-based serialization works without code generation.
Does generated code work across Go versions?
Yes. Generated code is plain Go with no version-specific features.
Can I mix generated and non-generated types?
Yes. Fory automatically uses generated serializers when available.
How do I update generated code?
Run go generate ./... after struct changes.
Should I commit generated files?
For libraries: yes. For applications: either works.