This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch add_cpp_introduction_blog
in repository https://gitbox.apache.org/repos/asf/fory-site.git
The following commit(s) were added to refs/heads/add_cpp_introduction_blog by
this push:
new fb882353f update blog
fb882353f is described below
commit fb882353f53f94c2ec96876f0fe7b807d0e2e4f1
Author: chaokunyang <[email protected]>
AuthorDate: Tue Jan 20 00:35:51 2026 +0800
update blog
---
blog/2026-01-19-fory_cpp_first_release.md | 229 +++++++++++++++++++++---------
1 file changed, 159 insertions(+), 70 deletions(-)
diff --git a/blog/2026-01-19-fory_cpp_first_release.md
b/blog/2026-01-19-fory_cpp_first_release.md
index ce0e41d21..18723fbbb 100644
--- a/blog/2026-01-19-fory_cpp_first_release.md
+++ b/blog/2026-01-19-fory_cpp_first_release.md
@@ -26,19 +26,21 @@ Every backend team eventually needs to move complex objects
across process or la
Apache Fory C++ eliminates this false choice. It is designed to be fast,
cross-language, and production friendly without sacrificing safety or developer
ergonomics.
-## Core features you should know
+## Core features of Apache Fory C++
- **Automatic object serialization**: Serialize numeric types, strings,
collections, maps, enums, and structs automatically. No IDL required, so domain
objects can be serialized and sent directly.
-- **Cross-language support**: Fory C++ speaks the same binary protocol as
Java, Python, Rust and Go. IDL is optional, and structs with consistent or
compatible schemas can be deserialized by other languages automatically.
+- **Cross-language support**: Fory C++ speaks the same binary protocol as
Java, Python, Rust and Go. IDL is optional, and structs with consistent or
compatible schemas can be deserialized across languages without a mandatory IDL.
- **Automatic schema evolution**: Enable Compatible mode and Fory matches
fields by field ID when provided, otherwise by name (not position), fills
defaults for new fields, and safely skips unknown fields. This is built into
the protocol and works across languages.
- **Shared and circular references**: With `track_ref(true)`, Fory preserves
object identity and handles cycles. This is essential for graphs, ORM models,
or tree structures with parent pointers.
- **Polymorphic and union support**: C++ supports `std::shared_ptr<Base>`
polymorphism (named registration) and `std::variant` union types. This covers
inheritance, plugin architectures, and heterogeneous collections.
- **Compact binary protocol and meta packing**: The protocol uses compact
headers, varints, and meta string encoding. Type metadata is packed with size
and hash headers, written once per stream, and referenced by index on
subsequent occurrences.
- **Compile-time performance**: Serialization is generated through templates
and macros. No runtime reflection. No dynamic field discovery. The fast path is
direct memory writes with minimal branching.
-## Cross-language example (C++ -> Java and Rust)
+## Cross-language Example (C++ -> Java/Python)
-The example below serializes in C++ and deserializes in Java and Rust using
the same type ID and field layout.
+The example below serializes in C++ and deserializes in Java and Python using
the same type ID and field layout.
+
+C++ producer:
```cpp
#include "fory/serialization/fory.h"
@@ -66,6 +68,8 @@ int main() {
}
```
+Java consumer:
+
```java
import org.apache.fory.Fory;
import org.apache.fory.config.Language;
@@ -86,71 +90,34 @@ fory.register(Message.class, 100);
Message msg = (Message) fory.deserialize(bytes);
```
-```rust
-use fory::{Fory, ForyObject};
-use std::collections::HashMap;
-
-#[derive(ForyObject)]
-struct Message {
- topic: String,
- timestamp: i64,
- headers: HashMap<String, String>,
- payload: Vec<u8>,
-}
-
-let mut fory = Fory::default();
-fory.register::<Message>(100);
-let msg: Message = fory.deserialize(&bytes)?;
-```
-
-Tip: in xlang mode, fields are matched by field ID when provided; otherwise by
snake_case field name. Keep names or IDs aligned across languages and use the
same type IDs.
+Python consumer:
-## Struct serialization protocol: meta packing, sharing, and schema evolution
+```python
+import pyfory
-This is the core of Fory C++ and the reason it can evolve schemas safely
without IDLs.
+class Message:
+ topic: str
+ timestamp: int
+ headers: dict[str, str]
+ payload: bytes
-### Protocol layout (high level)
+fory = pyfory.Fory(xlang=True)
+fory.register(Message, type_id=100)
+msg = fory.deserialize(bytes)
```
-| header | type info | reference meta | value data |
-```
-
-- **Header**: compact flags + language byte
-- **Type info**: type IDs and packed TypeMeta when compatible mode is enabled
-- **Reference meta**: flags and reference IDs for shared or circular objects
-- **Value data**: the serialized payload
-
-### Meta packing and sharing
-
-Fory C++ packs type metadata into a compact binary TypeMeta format that
includes:
-
-- A size header and a hash of the metadata
-- Encoded namespace and type name (meta string encoding)
-- Field definitions (name + field type + options)
-When a type appears multiple times in a stream, the first occurrence writes
the TypeMeta inline and assigns it an index. Later occurrences only write the
index. This removes repeated schema payloads while keeping compatibility intact.
+Tip: in xlang mode, fields are matched by field ID when provided; otherwise by
snake_case field name. Keep names or IDs aligned across languages and use the
same types.
-### Meta string encoding and further compression
+## What Makes Apache Fory C++ Different?
-Type names and namespaces are encoded with specialized meta string encodings
(for example, lower-case and symbol-aware encodings). This reduces metadata
size significantly. The design also enables additional meta compression in
higher-level pipelines because the metadata payload is compact, structured, and
hash-addressable.
-
-### Why this enables schema evolution
-
-Compatible mode builds a field map from TypeMeta and matches by field ID when
present, otherwise by name. When schemas diverge:
-
-- Added fields are filled with default values
-- Removed fields are skipped
-- Reordered fields are resolved by ID or name
-- Nullability changes are supported
-- Field IDs, when provided, take precedence over names for matching
-
-This works across languages because the same TypeMeta format is used in each
implementation.
-
-## Shared and circular references
+### Shared and circular references
Reference tracking is built into the protocol using reference flags and IDs.
In C++ you enable it with `track_ref(true)` so shared pointers preserve
identity and circular graphs can be round-tripped safely.
-### Shared references (aliasing preserved)
+#### Shared references (aliasing preserved)
+
+Example:
```cpp
struct Product {
@@ -181,10 +148,12 @@ assert(decoded.first->sku == "A-100");
assert(decoded.first->price == 19.95);
```
-### Circular references (parent/child graph)
+#### Circular references (parent/child graph)
Use `SharedWeak<T>` for non-owning back references so you can form cycles
without memory leaks. Fory resolves these links during deserialization while
keeping reference identity intact.
+Example:
+
```cpp
struct Node {
std::string name;
@@ -212,12 +181,14 @@ auto decoded_parent = decoded_child->parent.upgrade();
assert(decoded_parent == decoded);
```
-## Polymorphism and union support
+### Polymorphism and union support
-### Polymorphism
+#### Polymorphism
Register derived types by name and serialize base-class pointers. The type
info is written once and reused in the stream, so polymorphic dispatch works on
deserialize.
+Example:
+
```cpp
struct Animal {
virtual ~Animal() = default;
@@ -263,10 +234,12 @@ assert(dog->speak() == "woof");
assert(cat->speak() == "meow");
```
-### Union types
+#### Union types
Use `std::variant` for union-like fields. Fory records the active alternative
and preserves it across languages that support union types.
+Example:
+
```cpp
using Value = std::variant<int32_t, std::string, double>;
@@ -274,32 +247,132 @@ auto bytes = fory.serialize(Value{42}).value();
auto decoded = fory.deserialize<Value>(bytes).value();
```
-## Automatic schema evolution in practice
+### Automatic schema evolution in practice
+
+Docs: https://fory.apache.org/docs/guide/cpp/schema-evolution/
+
+Microservices evolve independently. Compatible mode lets schema changes roll
out without tight coordination.
+
+Example:
```cpp
+#include "fory/serialization/fory.h"
+#include <map>
+#include <optional>
+#include <string>
+
struct UserV1 {
std::string name;
int32_t age;
+ std::string address;
};
-FORY_STRUCT(UserV1, name, age);
+FORY_STRUCT(UserV1, name, age, address);
struct UserV2 {
std::string name;
int32_t age;
- std::string email; // New field
+ std::optional<std::string> phone; // New field
+ std::map<std::string, std::string> metadata; // Another new field
+};
+FORY_STRUCT(UserV2, name, age, phone, metadata);
+
+auto fory_v1 = Fory::builder().compatible(true).xlang(true).build();
+auto fory_v2 = Fory::builder().compatible(true).xlang(true).build();
+
+constexpr uint32_t kUserTypeId = 100;
+fory_v1.register_struct<UserV1>(kUserTypeId);
+fory_v2.register_struct<UserV2>(kUserTypeId);
+
+UserV1 v1{"Alice", 30, "NYC"};
+auto bytes = fory_v1.serialize(v1).value();
+
+auto v2 = fory_v2.deserialize<UserV2>(bytes).value();
+// Missing fields get default values automatically.
+assert(!v2.phone.has_value());
+assert(v2.metadata.empty());
+```
+
+Compatibility rules:
+
+- Add new fields (default values applied)
+- Remove fields (skipped during deserialization)
+- Reorder fields (matched by ID or name)
+- Change nullability (`T` <-> `std::optional<T>`)
+- Type changes are not supported (except nullable variants)
+
+If you assign field IDs (via `fory::field<>`, `FORY_FIELD_TAGS`, or
`FORY_FIELD_CONFIG`), those IDs become the primary match key.
+
+### Schema evolution with field IDs
+
+Docs: https://fory.apache.org/docs/guide/cpp/schema-evolution/
+
+Field IDs give you an explicit, stable schema key that remains consistent even
when field names or ordering change.
+
+Example:
+
+```cpp
+struct AccountV1 {
+ fory::field<std::string, 1> id;
+ fory::field<int64_t, 2> balance;
+};
+FORY_STRUCT(AccountV1, id, balance);
+
+struct AccountV2 {
+ fory::field<int64_t, 2> balance; // Reordered
+ fory::field<std::string, 1> id; // Reordered
+ fory::field<std::string, 3> currency; // New field
};
-FORY_STRUCT(UserV2, name, age, email);
+FORY_STRUCT(AccountV2, balance, id, currency);
auto fory_v1 = Fory::builder().compatible(true).xlang(true).build();
auto fory_v2 = Fory::builder().compatible(true).xlang(true).build();
-fory_v1.register_struct<UserV1>(100);
-fory_v2.register_struct<UserV2>(100);
+fory_v1.register_struct<AccountV1>(200);
+fory_v2.register_struct<AccountV2>(200);
+```
+
+## The Technical Rationals
+
+This is the core of Fory C++ and the reason it can evolve schemas safely
without IDLs.
+
+### Protocol layout (high level)
+
+Protocol layout sketch:
+
+```
+| header | type info | reference meta | value data |
```
-Compatible mode supports adding, removing, or reordering fields and changing
nullability. If you assign field IDs (via `fory::field<>`, `FORY_FIELD_TAGS`,
or `FORY_FIELD_CONFIG`), those IDs become the primary match key. Type changes
are not allowed.
+- **Header**: compact flags + language byte
+- **Type info**: type IDs and packed TypeMeta when compatible mode is enabled
+- **Reference meta**: flags and reference IDs for shared or circular objects
+- **Value data**: the serialized payload
-## Compile-time performance and runtime efficiency
+### Struct Meta packing and sharing
+
+Fory C++ packs type metadata into a compact binary TypeMeta format that
includes:
+
+- A size header and a hash of the metadata
+- Encoded namespace and type name (meta string encoding)
+- Field definitions (name + field type + options)
+
+When a type appears multiple times in a stream, the first occurrence writes
the TypeMeta inline and assigns it an index. Later occurrences only write the
index. This removes repeated schema payloads while keeping compatibility intact.
+
+For minimized type meta space cost, the type names and namespaces are encoded
with specialized meta string encodings (for example, lower-case and
symbol-aware encodings). This reduces metadata size significantly. The design
also enables additional meta compression in higher-level pipelines because the
metadata payload is compact, structured, and hash-addressable.
+
+### Why this enables schema evolution
+
+Compatible mode builds a field map from TypeMeta and matches by field ID when
present, otherwise by name. When schemas diverge:
+
+- Added fields are filled with default values
+- Removed fields are skipped
+- Reordered fields are resolved by ID or name
+- Nullability changes are supported
+- Field IDs, when provided, take precedence over names for matching
+
+This works across languages because the same TypeMeta format is used in each
implementation.
+
+### Compile-time performance and runtime efficiency
Fory C++ is designed for throughput and low latency:
@@ -346,6 +419,8 @@ cd benchmarks/cpp_benchmark
### Install by CMake
+CMake example:
+
```cmake
cmake_minimum_required(VERSION 3.16)
project(my_project LANGUAGES CXX)
@@ -373,6 +448,8 @@ For Bazel installation, see
https://fory.apache.org/docs/guide/cpp/#using-bazel.
### Basic object serialization
+Example:
+
```cpp
#include "fory/serialization/fory.h"
@@ -417,6 +494,8 @@ Why explicit registration matters:
- Concrete type mapping across languages: custom types need a stable, shared
identity (ID or name) so C++, Java, and Rust agree on the same schema.
- Automatic polymorphic deserialization: registering concrete types enables
downcasting when deserializing `shared_ptr<Base>` or other polymorphic
containers.
+Example:
+
```cpp
enum class Color { Red, Green, Blue };
FORY_ENUM(Color, Red, Green, Blue);
@@ -436,6 +515,8 @@ if (!bytes_result.ok()) {
All serialization APIs return `Result<T, Error>`. Check `ok()` before using
the value and use `error().to_string()` for diagnostic output.
+Example:
+
```cpp
auto result = fory.deserialize<Person>(bytes);
if (!result.ok()) {
@@ -461,6 +542,8 @@ auto write_result = fory.serialize_to(buffer, person);
### Thread-safe variant
+Example:
+
```cpp
auto fory = Fory::builder().xlang(true).build_thread_safe();
// Register types before spawning threads.
@@ -472,6 +555,8 @@ auto fory = Fory::builder().xlang(true).build_thread_safe();
Use `fory::field<>` for inline metadata or `FORY_FIELD_TAGS` and
`FORY_FIELD_CONFIG` for non-invasive configuration.
+Example:
+
```cpp
struct Document {
std::string title;
@@ -491,6 +576,8 @@ If field IDs are defined, compatible mode uses them as the
primary key for match
When you cannot use `FORY_STRUCT`, specialize the `Serializer<T>` template and
register the type as an extension.
+Example:
+
```cpp
struct ExternalType { int32_t value; };
@@ -513,6 +600,8 @@ template <> struct Serializer<ExternalType> {
} // namespace fory
```
+Registration example:
+
```cpp
auto fory = Fory::builder().build();
fory.register_extension_type<ExternalType>(200);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]