title: Type System sidebar_position: 4 id: type_system license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

This document describes the FDL type system and how types map to each target language.

Overview

FDL provides a rich type system designed for cross-language compatibility:

  • Primitive Types: Basic scalar types (integers, floats, strings, etc.)
  • Enum Types: Named integer constants
  • Message Types: Structured compound types
  • Collection Types: Lists and maps
  • Nullable Types: Optional/nullable variants

Primitive Types

Boolean

bool is_active = 1;
LanguageTypeNotes
Javaboolean / BooleanPrimitive or boxed
Pythonbool
Gobool
Rustbool
C++bool

Integer Types

FDL provides fixed-width signed integers (varint encoding for 32/64-bit):

FDL TypeSizeRange
int88-bit-128 to 127
int1616-bit-32,768 to 32,767
int3232-bit-2^31 to 2^31 - 1
int6464-bit-2^63 to 2^63 - 1

Language Mapping:

FDLJavaPythonGoRustC++
int8bytepyfory.int8int8i8int8_t
int16shortpyfory.int16int16i16int16_t
int32intpyfory.int32int32i32int32_t
int64longpyfory.int64int64i64int64_t

FDL provides fixed-width unsigned integers (varint encoding for 32/64-bit):

FDLSizeRange
uint88-bit0 to 255
uint1616-bit0 to 65,535
uint3232-bit0 to 2^32 - 1
uint6464-bit0 to 2^64 - 1

Language Mapping (Unsigned):

FDLJavaPythonGoRustC++
uint8shortpyfory.uint8uint8u8uint8_t
uint16intpyfory.uint16uint16u16uint16_t
uint32longpyfory.uint32uint32u32uint32_t
uint64longpyfory.uint64uint64u64uint64_t

Examples:

message Counters {
    int8 tiny = 1;
    int16 small = 2;
    int32 medium = 3;
    int64 large = 4;
}

Python Type Hints:

Python's native int is arbitrary precision, so FDL uses type wrappers for fixed-width integers:

from pyfory import int8, int16, int32

@dataclass
class Counters:
    tiny: int8
    small: int16
    medium: int32
    large: int  # int64 maps to native int

Integer Encoding Variants

For 32/64-bit integers, FDL uses varint encoding by default. Use explicit types when you need fixed-width or tagged encoding:

FDL TypeEncodingNotes
fixed_int32fixedSigned 32-bit
fixed_int64fixedSigned 64-bit
fixed_uint32fixedUnsigned 32-bit
fixed_uint64fixedUnsigned 64-bit
tagged_int64taggedSigned 64-bit (hybrid)
tagged_uint64taggedUnsigned 64-bit (hybrid)

Floating-Point Types

FDL TypeSizePrecision
float3232-bit~7 digits
float6464-bit~15-16 digits

Language Mapping:

FDLJavaPythonGoRustC++
float32floatpyfory.float32float32f32float
float64doublepyfory.float64float64f64double

Example:

message Coordinates {
    float64 latitude = 1;
    float64 longitude = 2;
    float32 altitude = 3;
}

String Type

UTF-8 encoded text:

string name = 1;
LanguageTypeNotes
JavaStringImmutable
Pythonstr
GostringImmutable
RustStringOwned, heap-allocated
C++std::string

Bytes Type

Raw binary data:

bytes data = 1;
LanguageTypeNotes
Javabyte[]
PythonbytesImmutable
Go[]byte
RustVec<u8>
C++std::vector<uint8_t>

Temporal Types

Date

Calendar date without time:

date birth_date = 1;
LanguageTypeNotes
Javajava.time.LocalDate
Pythondatetime.date
Gotime.TimeTime portion ignored
Rustchrono::NaiveDateRequires chrono crate
C++fory::serialization::Date

Timestamp

Date and time with nanosecond precision:

timestamp created_at = 1;
LanguageTypeNotes
Javajava.time.InstantUTC-based
Pythondatetime.datetime
Gotime.Time
Rustchrono::NaiveDateTimeRequires chrono crate
C++fory::serialization::Timestamp

Any

Dynamic value with runtime type information:

any payload = 1;
LanguageTypeNotes
JavaObjectRuntime type written
PythonAnyRuntime type written
GoanyRuntime type written
RustBox<dyn Any>Runtime type written
C++std::anyRuntime type written

Notes:

  • any always writes a null flag (same as nullable) because values may be empty; codegen treats any as nullable even without optional.
  • Allowed runtime values are limited to bool, string, enum, message, and union. Other primitives (numeric, bytes, date/time) and list/map are not supported; wrap them in a message or use explicit fields instead.
  • ref is not allowed on any fields (including repeated/map values). Wrap any in a message if you need reference tracking.
  • The runtime type must be registered in the target language schema/IDL registration; unknown types fail to deserialize.

Enum Types

Enums define named integer constants:

enum Priority [id=100] {
    LOW = 0;
    MEDIUM = 1;
    HIGH = 2;
    CRITICAL = 3;
}

Language Mapping:

LanguageImplementation
Javaenum Priority { LOW, MEDIUM, ... }
Pythonclass Priority(IntEnum): LOW = 0, ...
Gotype Priority int32 with constants
Rust#[repr(i32)] enum Priority { ... }
C++enum class Priority : int32_t { ... }

Java:

public enum Priority {
    LOW,
    MEDIUM,
    HIGH,
    CRITICAL;
}

Python:

class Priority(IntEnum):
    LOW = 0
    MEDIUM = 1
    HIGH = 2
    CRITICAL = 3

Go:

type Priority int32

const (
    PriorityLow      Priority = 0
    PriorityMedium   Priority = 1
    PriorityHigh     Priority = 2
    PriorityCritical Priority = 3
)

Rust:

#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
#[repr(i32)]
pub enum Priority {
    #[default]
    Low = 0,
    Medium = 1,
    High = 2,
    Critical = 3,
}

C++:

enum class Priority : int32_t {
    LOW = 0,
    MEDIUM = 1,
    HIGH = 2,
    CRITICAL = 3,
};
FORY_ENUM(Priority, LOW, MEDIUM, HIGH, CRITICAL);

Message Types

Messages are structured types composed of fields:

message User [id=101] {
    string id = 1;
    string name = 2;
    int32 age = 3;
}

Language Mapping:

LanguageImplementation
JavaPOJO class with getters/setters
Python@dataclass class
GoStruct with exported fields
RustStruct with #[derive(ForyObject)]
C++Struct with FORY_STRUCT macro

Collection Types

List (repeated)

The repeated modifier creates a list:

repeated string tags = 1;
repeated User users = 2;

Language Mapping:

FDLJavaPythonGoRustC++
repeated stringList<String>List[str][]stringVec<String>std::vector<std::string>
repeated int32List<Integer>List[int][]int32Vec<i32>std::vector<int32_t>
repeated UserList<User>List[User][]UserVec<User>std::vector<User>

List modifiers:

FDLJavaPythonGoRustC++
optional repeated stringList<String> + @ForyField(nullable = true)Optional[List[str]][]string + nullableOption<Vec<String>>std::optional<std::vector<std::string>>
repeated optional stringList<String> (nullable elements)List[Optional[str]][]*stringVec<Option<String>>std::vector<std::optional<std::string>>
ref repeated UserList<User> + @ForyField(ref = true)List[User] + pyfory.field(ref=True)[]User + refArc<Vec<User>>*std::shared_ptr<std::vector<User>>
repeated ref UserList<User>List[User][]*User + ref=falseVec<Arc<User>>*std::vector<std::shared_ptr<User>>

*Use [(fory).thread_safe_pointer = false] to generate Rc instead of Arc in Rust.

Map

Maps with typed keys and values:

map<string, int32> counts = 1;
map<string, User> users = 2;

Language Mapping:

FDLJavaPythonGoRustC++
map<string, int32>Map<String, Integer>Dict[str, int]map[string]int32HashMap<String, i32>std::map<std::string, int32_t>
map<string, User>Map<String, User>Dict[str, User]map[string]UserHashMap<String, User>std::map<std::string, User>

Key Type Restrictions:

Map keys should be hashable types:

  • string (most common)
  • Integer types (int8, int16, int32, int64)
  • bool

Avoid using messages or complex types as keys.

Nullable Types

The optional modifier makes a field nullable:

message Profile {
    string name = 1;              // Required
    optional string bio = 2;      // Nullable
    optional int32 age = 3;       // Nullable integer
}

Language Mapping:

FDLJavaPythonGoRustC++
optional stringString*Optional[str]*stringOption<String>std::optional<std::string>
optional int32IntegerOptional[int]*int32Option<i32>std::optional<int32_t>

*Java uses boxed types with @ForyField(nullable = true) annotation.

Default Values:

TypeDefault Value
Non-optional typesLanguage default
Optional typesnull/None/nil

Reference Types

The ref modifier enables reference tracking:

message TreeNode {
    string value = 1;
    ref TreeNode parent = 2;
    repeated ref TreeNode children = 3;
}

Use Cases:

  1. Shared References: Same object referenced from multiple places
  2. Circular References: Object graphs with cycles
  3. Large Objects: Avoid duplicate serialization

Language Mapping:

FDLJavaPythonGoRustC++
ref UserUser*User*User + fory:"ref"Arc<User>std::shared_ptr<User>

*Java uses @ForyField(ref = true) annotation.

Rust uses Arc by default; set ref(thread_safe = false) in FDL (or [(fory).thread_safe_pointer = false] in protobuf) to use Rc. Use ref(weak = true) in FDL (or [(fory).weak_ref = true] in protobuf) with ref to generate weak pointer types: ArcWeak/RcWeak in Rust and fory::serialization::SharedWeak<T> in C++. Java/Python/Go ignore weak_ref.

Type Compatibility Matrix

This matrix shows which type conversions are safe across languages:

From → Toboolint8int16int32int64float32float64string
bool---
int8--
int16---
int32-----
int64-------
float32------
float64-------
string-------

✓ = Safe conversion, - = Not recommended

Best Practices

Choosing Integer Types

  • Use int32 as the default for most integers
  • Use int64 for large values (timestamps, IDs)
  • Use int8/int16 only when storage size matters

String vs Bytes

  • Use string for text data (UTF-8)
  • Use bytes for binary data (images, files, encrypted data)

Optional vs Required

  • Use optional when the field may legitimately be absent
  • Default to required fields for better type safety
  • Document why a field is optional

Reference Tracking

  • Use ref only when needed (shared/circular references)
  • Reference tracking adds overhead
  • Test with realistic data to ensure correctness

Collections

  • Prefer repeated for ordered sequences
  • Use map for key-value lookups
  • Consider message types for complex map values