Skip to main content
Version: dev

Reference Tracking

This page explains how Fory handles reference tracking for shared and circular references in cross-language serialization.

Overview

Reference tracking enables:

  • Shared references: Same object referenced multiple times is serialized once
  • Circular references: Objects that reference themselves or form cycles
  • Memory efficiency: No duplicate data for repeated objects

Enabling Reference Tracking

Java

Fory fory = Fory.builder()
.withLanguage(Language.XLANG)
.withRefTracking(true)
.build();

Python

fory = pyfory.Fory(xlang=True, ref_tracking=True)

Go

fory := forygo.NewFory(true)  // true enables ref tracking

C++

auto fory = fory::Fory::create(fory::Config{
.ref_tracking = true
});

Rust

let fory = Fory::builder()
.with_ref_tracking(true)
.build();

Wire Format

When reference tracking is enabled, nullable fields write a ref flag byte before the value:

[ref_flag] [value data if not null/ref]

Where ref_flag is:

ValueMeaning
-1 (NULL_FLAG)Value is null
-2 (NOT_NULL_VALUE_FLAG)Value is present, first occurrence
≥0Reference ID pointing to previously serialized object

Reference Tracking vs Nullability

These are independent concepts:

ConceptPurposeControlled By
NullabilityWhether a field can hold null valuesField type (Optional<T>) or annotation
Reference TrackingWhether duplicate objects are deduplicatedGlobal refTracking option

Key behavior:

  • Ref flag bytes are only written for nullable fields
  • Non-nullable fields skip ref flags entirely, even with refTracking=true
  • Reference deduplication only applies to objects that appear multiple times
// Reference tracking enabled, but non-nullable fields still skip ref flags
Fory fory = Fory.builder()
.withLanguage(Language.XLANG)
.withRefTracking(true)
.build();

Per-Field Reference Tracking

By default, most fields do not track references even when global refTracking=true. Only specific pointer/smart pointer types track references by default.

Default Behavior by Language

LanguageDefault Ref TrackingTypes That Track Refs by Default
JavaNoNone (use annotation to enable)
PythonNoNone (use annotation to enable)
GoNoNone (use fory:"ref" to enable)
C++Nostd::shared_ptr<T>
RustNoRc<T>, Arc<T>, Weak<T>

Customizing Per-Field Ref Tracking

Java: @ForyField Annotation

public class Document {
// Default: no ref tracking
String title;

// Enable ref tracking for this field
@ForyField(trackingRef = true)
Author author;

// Shared across documents, track refs to avoid duplicates
@ForyField(trackingRef = true)
List<Tag> tags;
}

C++: fory::field Wrapper

struct Document {
std::string title;

// shared_ptr tracks refs by default
std::shared_ptr<Author> author;

// Explicitly enable ref tracking
fory::field<std::vector<Tag>, 1, fory::track_ref<true>> tags;

// Explicitly disable ref tracking
fory::field<std::shared_ptr<Data>, 2, fory::track_ref<false>> data;
};
FORY_STRUCT(Document, title, author, tags, data);

Rust: Field Attributes

#[derive(Fory)]
#[tag("example.Document")]
struct Document {
title: String,

// Rc/Arc track refs by default
author: Rc<Author>,

// Explicitly enable ref tracking
#[track_ref]
tags: Vec<Tag>,
}

Go: Struct Tags

type Document struct {
Title string

// Enable ref tracking for pointer to struct
Author *Author `fory:"ref"`

// Enable ref tracking for slice
Tags []Tag `fory:"ref"`
}

When to Enable Per-Field Ref Tracking

Enable ref tracking for fields that:

  • May contain the same object instance multiple times
  • Are part of circular reference chains
  • Hold large objects that might be shared

Disable (or leave default) for fields that:

  • Always contain unique values
  • Are primitives or simple value types
  • Don't participate in object sharing

Example: Shared References

public class Container {
List<String> data;
List<String> sameData; // Points to same list
}

Container obj = new Container();
obj.data = Arrays.asList("a", "b", "c");
obj.sameData = obj.data; // Shared reference

// With refTracking=true: data serialized once, sameData stores reference ID
// With refTracking=false: data serialized twice (duplicate)

Example: Circular References

public class Node {
String value;
Node next;
}

Node a = new Node("A");
Node b = new Node("B");
a.next = b;
b.next = a; // Circular reference

// With refTracking=true: works correctly
// With refTracking=false: infinite recursion error

Language Support

LanguageShared RefsCircular Refs
JavaYesYes
PythonYesYes
GoYesYes
C++YesYes
JavaScriptYesYes
RustYesNo (ownership rules)

Performance Considerations

  • Overhead: Reference tracking adds a hash map lookup per object
  • When to enable: Use when data has shared/circular references
  • When to disable: Use for simple data structures without sharing

See Also