Skip to content

Conversation

@userzhy
Copy link
Contributor

@userzhy userzhy commented Dec 19, 2025

Why?

Go Fory lacks support for Union types, which prevents cross-language serialization compatibility with other languages like C++, Java, Python, and Rust that already support union/variant types. This limitation was reported in issue #3031.

Without Union type support, Go users cannot:

  • Serialize union types to communicate with other languages
  • Use tagged union patterns common in serialization scenarios
  • Leverage the full xlang serialization capabilities

What does this PR do?

This PR implements complete Union type support for Go Fory by:

  1. Added TypeId constants to go/fory/types.go:

    • UNION = 38 - for tagged union types
    • NONE = 39 - for empty/unit values
  2. Implemented Union struct and unionSerializer in go/fory/union.go:

    • Union struct that holds an interface{} value
    • Serializes Union types by writing variant index + value
    • Supports both internal Go mode and xlang mode (with type info)
    • Properly dispatches to alternative type serializers
  3. Added RegisterUnionType API to Fory:

    • Allows users to register alternative types for a union
    • Example: f.RegisterUnionType(reflect.TypeOf(int32(0)), reflect.TypeOf(""))
  4. Added comprehensive tests in go/fory/union_test.go:

    • Basic types (int32, string, float64)
    • Multiple alternative types
    • Null/nil values
    • Pointer types
    • Bytes type
    • Reference tracking mode
    • Error cases (invalid alternatives, empty registration)

The implementation follows the same binary protocol as C++/Python/Rust variant serialization:

  1. Write variant index (varuint32)
  2. In xlang mode, write type info for the active alternative
  3. Write the value using the alternative's serializer

Related issues

Fixes #3031
Related to #3027 (tracking issue for xlang union type system)

Does this PR introduce any user-facing change?

  • Does this PR introduce any public API change?

    • Yes: Adds Union struct and RegisterUnionType method. Users can now use Union types in their code and they will be automatically serialized/deserialized.
    • This is backward compatible - existing code continues to work.
  • Does this PR introduce any binary protocol compatibility change?

    • No: The implementation uses the existing TypeId.UNION (38) and TypeId.NONE (39) defined in the specification.
    • The binary protocol follows the same format as C++/Python/Rust implementations.
    • Existing serialized data is not affected.

Benchmark

This PR does not have a performance impact on existing functionality:

  • Union type handling only activates when Union types are actually used
  • The implementation uses efficient type matching via reflection
  • Variant index is encoded using varuint32 (compact encoding)
  • No changes to existing serializers or hot paths

For Union types specifically, the overhead is minimal:

  • One varuint32 write/read for the variant index
  • One type dispatch (same as normal polymorphic serialization)
  • No additional allocations beyond the Union struct itself

@userzhy userzhy requested a review from chaokunyang as a code owner December 19, 2025 14:50
go/fory/union.go Outdated
// union := fory.Union{Value: int32(42)}
// // or
// union := fory.Union{Value: "hello"}
type Union struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are use go 1.23+, we could use go generics now. We can define union as:

package union

import "fmt"

// --- Union2 ---

type Union2[T1 any, T2 any] struct {
    v1 *T1
    v2 *T2
    index int 
}

func NewUnion2A[T1 any, T2 any](t T1) Union2[T1, T2] {
    return Union2[T1, T2]{v1: &t, index: 1}
}

func NewUnion2B[T1 any, T2 any](t T2) Union2[T1, T2] {
    return Union2[T1, T2]{v2: &t, index: 2}
}

func (u Union2[T1, T2]) Match(
    case1 func(T1), 
    case2 func(T2),
) {
    switch u.index {
    case 1:
        case1(*u.v1)
    case 2:
        case2(*u.v2)
    default:
        panic("Union2 is uninitialized")
    }
}

// --- Union3 ---

type Union3[T1 any, T2 any, T3 any] struct {
    v1 *T1
    v2 *T2
    v3 *T3
    index int
}

func NewUnion3A[T1 any, T2, T3 any](t T1) Union3[T1, T2, T3] {
    return Union3[T1, T2, T3]{v1: &t, index: 1}
}

func NewUnion3B[T1, T3 any, T2 any](t T2) Union3[T1, T2, T3] {
    return Union3[T1, T2, T3]{v2: &t, index: 2}
}

func NewUnion3C[T1, T2 any, T3 any](t T3) Union3[T1, T2, T3] {
    return Union3[T1, T2, T3]{v3: &t, index: 3}
}

func (u Union3[T1, T2, T3]) Match(f1 func(T1), f2 func(T2), f3 func(T3)) {
    switch u.index {
    case 1: f1(*u.v1)
    case 2: f2(*u.v2)
    case 3: f3(*u.v3)
    }
}

Add support for tagged union types in Go Fory, enabling cross-language
serialization compatibility with other languages like C++, Java, Python,
and Rust that already support union/variant types.

Changes:
- Add UNION (38) and NONE (39) type constants to types.go
- Implement Union struct and unionSerializer in union.go
- Add RegisterUnionType API to Fory for registering union alternatives
- Add comprehensive tests in union_test.go

The implementation follows the same binary protocol as other languages:
1. Write variant index (varuint32)
2. In xlang mode, write type info for the active alternative
3. Write the value data using the alternative's serializer

Fixes apache#3031
Address review feedback from chaokunyang. Since Go 1.23+ is used,
replace interface{}-based Union with generic Union2, Union3, Union4 types.

New API:
- Union2[T1, T2], Union3[T1, T2, T3], Union4[T1, T2, T3, T4] generic types
- NewUnion2A/B, NewUnion3A/B/C, NewUnion4A/B/C/D constructors
- Match() methods for pattern matching (type-safe callbacks)
- Index(), IsFirst(), IsSecond() accessor methods
- First(), Second() value extraction methods (panics on wrong access)
- RegisterUnion2Type, RegisterUnion3Type, RegisterUnion4Type functions

Benefits:
- Compile-time type safety
- Explicit variant tracking
- Pattern matching support
- No runtime type assertions needed
@userzhy
Copy link
Contributor Author

userzhy commented Dec 24, 2025

@chaokunyang Fixed, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement union type in go

2 participants