-
Notifications
You must be signed in to change notification settings - Fork 353
Open
Labels
Description
Add a fory::field<> template wrapper for compile-time field metadata to enable performance and space optimization during xlang serialization.
Problem Statement
Current Limitations
The current C++ implementation applies uniform handling to all struct fields:
- Null Checks: Written for all non-primitive fields, even when they can never be null (1 byte per field overhead)
- Reference Tracking: Applied globally regardless of whether fields actually need shared reference support
- Field Name Encoding: Long field names consume extra space via meta string compression instead of compact tag IDs
Expected Benefits
For a 10-field structure with tag IDs and explicit nullable/ref settings:
- Space savings: ~20 bytes per object (eliminates null flags when not needed)
- CPU savings: 10 fewer hash operations per serialization (no meta string lookups for field names)
- Compact encoding: Varint tag IDs (1-2 bytes) replace meta string encoded names (~5-15 bytes)
Proposed Solution
Core Principles
- All fields are non-nullable by default (except
std::optional) - All fields have no reference tracking by default
- Only 2 tags:
fory::nullableandfory::ref - Type-safe:
nullableonly valid for pointer types,refonly valid forshared_ptr
API Design
namespace fory {
struct nullable {}; // Mark shared_ptr/unique_ptr as nullable
struct ref {}; // Enable reference tracking for shared_ptr
template <typename T, int16_t Id, typename... Options>
class field { /* same memory layout as T */ };
}Type Rules
| Type | Default Nullable | Default Ref | Allowed Options |
|---|---|---|---|
Primitives, std::string |
false | false | None (no options allowed) |
std::optional<T> |
true (inherent) | false | None (inherent nullable) |
std::shared_ptr<T> |
false | false | fory::nullable, fory::ref |
std::unique_ptr<T> |
false | false | fory::nullable only |
Usage Example
struct Document {
// Primitives - always non-nullable, NO options allowed
fory::field<std::string, 0> title;
fory::field<int32_t, 1> version;
// std::optional - always nullable (inherent), NO options needed
fory::field<std::optional<std::string>, 2> description;
// shared_ptr - non-nullable by default
fory::field<std::shared_ptr<User>, 3> author; // must exist
fory::field<std::shared_ptr<User>, 4, fory::nullable> reviewer; // can be null
fory::field<std::shared_ptr<Node>, 5, fory::ref> parent; // ref tracking
fory::field<std::shared_ptr<Node>, 6, fory::nullable, fory::ref> p; // nullable + ref
// unique_ptr - non-nullable by default, no ref allowed
fory::field<std::unique_ptr<Data>, 7> data; // must exist
fory::field<std::unique_ptr<Data>, 8, fory::nullable> opt_data; // can be null
};
FORY_STRUCT(Document, title, version, description, author, reviewer, parent, p, data, opt_data);Compile-Time Validation
Invalid configurations are caught at compile time:
// ❌ ERROR: nullable not allowed on primitives
fory::field<int32_t, 0, fory::nullable> age;
// ❌ ERROR: nullable not allowed on string
fory::field<std::string, 1, fory::nullable> name;
// ❌ ERROR: ref not allowed on unique_ptr
fory::field<std::unique_ptr<T>, 2, fory::ref> ptr;
// ❌ ERROR: options not allowed on optional (inherently nullable)
fory::field<std::optional<T>, 3, fory::nullable> opt;
// ✅ CORRECT: use optional for nullable primitives
fory::field<std::optional<int32_t>, 0> age;
fory::field<std::optional<std::string>, 1> name;Runtime Behavior
Serialization (Strict)
| Field Config | Value | Behavior |
|---|---|---|
Non-nullable shared_ptr<T> |
nullptr |
❌ Error: "Cannot serialize null for non-nullable field" |
Non-nullable shared_ptr<T> |
valid ptr | ✅ Serialize directly (no null flag written) |
fory::nullable shared_ptr<T> |
nullptr |
✅ Write null flag |
fory::nullable shared_ptr<T> |
valid ptr | ✅ Write non-null flag + data |
Deserialization (Lenient)
| Field Config | Remote Sends | Result |
|---|---|---|
Non-nullable shared_ptr<T> |
null/missing | ✅ std::make_shared<T>() (default T) |
Non-nullable unique_ptr<T> |
null/missing | ✅ std::make_unique<T>() (default T) |
fory::nullable shared_ptr<T> |
null | ✅ nullptr |
fory::nullable unique_ptr<T> |
null | ✅ nullptr |
Rationale: Be strict in what you send, lenient in what you accept.
Additional Context
This is the C++ equivalent of Java's @ForyField annotation. See Java issue #3000 for the original design discussion.
Protocol spec: https://fory.apache.org/docs/specification/fory_xlang_serialization_spec