跳到主要内容

🦀 Fory v0.13.1 发布:Rust 类型演进新范式 - 枚举变体与元组的灵活进化

· 阅读需 8 分钟
杨朝坤

Apache Fory 团队很高兴地宣布 0.13.1 版本发布。此版本包含来自 11 位不同贡献者的 28 个 PR。请参阅安装页面了解如何获取适用于您平台的库。

✨ 亮点

  • 🔧 支持 Rust 枚举变体以及元组/结构体风格枚举的 schema 演进
  • 🎯 支持 Rust 元组序列化和 schema 演进
  • ⏭️ 支持 Rust skip 宏属性

🎨 枚举 Schema 演进

Fory v0.13.1 在兼容模式下增加了全面的枚举 schema 演进支持,支持所有三种变体类型(Unit、Unnamed、Named):

  • 添加/删除变体:未知变体回退到 #[fory(default)]
  • 📝 添加/删除字段:命名变体支持字段演进,自动使用默认值
  • 🔄 修改元素:未命名变体处理元素数量变化(多余的被跳过,缺失的使用默认值)
  • 🔀 变体类型更改:在 Unit/Unnamed/Named 之间转换,自动使用默认值
// 版本 1
#[derive(ForyObject)]
enum Command {
#[fory(default)]
Noop,
Execute { name: String, args: i32 },
}

// 版本 2 - 添加了字段和新变体
// 同时支持 `fory(default)` 和标准 `default`
#[derive(Default, ForyObject)]
enum Command {
#[default]
Noop,
Execute { name: String, args: i32, env: String }, // 添加了 'env'
Cancel { reason: String }, // 新变体
}

// V1→V2: 缺失的 'env' 获得默认值 ""; Cancel→Noop 回退到 V1
// V2→V1: 额外的 'env' 被跳过; Cancel→Noop 回退

📦 元组 Schema 演进

元组(1-22 个元素)现在在兼容模式下支持长度演进:

  • 📏 长度变化:增大或缩小元组大小(缺失的元素获得默认值,多余的被丢弃)
  • 📚 集合:完全支持 VecHashMapHashSet 元素
  • 🪆 嵌套元组:多级嵌套,每层独立演进
  • 🔗 智能指针OptionArcRc 包装的元素正确处理演进
  • 🏗️ 结构体字段:结构体中的元组字段独立演进
let fory = Fory::default().compatible(true);

// 序列化 2 元素元组
let short = (42i32, "hello".to_string());
let bin = fory.serialize(&short).unwrap();

// 反序列化为 4 元素元组 - 额外的元素获得默认值
let long: (i32, String, f64, bool) = fory.deserialize(&bin).unwrap();
assert_eq!(long, (42, "hello".to_string(), 0.0, false));

// 反向:4→2 元素,多余的被丢弃
let long = (100i32, "world".to_string(), 3.14, true);
let bin = fory.serialize(&long).unwrap();
let short: (i32, String) = fory.deserialize(&bin).unwrap();
assert_eq!(short, (100, "world".to_string()));

📦 快速开始

默认情况下会使用各语言的原生模式以获得最佳性能;只有在需要跨语言互通时,才将序列化模式切换为 xlang=true(或等效选项)。

Rust

[dependencies]
fory = "0.13"
use fory::{Error, Fory};
use fory::ForyObject;

#[derive(ForyObject, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
}

fn main() -> Result<(), Error> {
let mut fory = Fory::default();
fory.register::<Person>(1)?;
let bytes = fory.serialize(&Person { name: "Alice".into(), age: 30 })?;
let decoded: Person = fory.deserialize(&bytes)?;
assert_eq!(decoded.age, 30);
Ok(())
}

Python

python -m pip install --upgrade pip
pip install pyfory==0.13.1
from dataclasses import dataclass
import pyfory

@dataclass
class Person:
name: str
age: pyfory.int32

fory = pyfory.Fory()
fory.register_type(Person)
payload = fory.serialize(Person(name="Alice", age=30))
decoded = fory.deserialize(payload)
print(decoded.name, decoded.age)

Java

<dependency>
<groupId>org.apache.fory</groupId>
<artifactId>fory-core</artifactId>
<version>0.13.1</version>
</dependency>
import org.apache.fory.*;
import org.apache.fory.config.*;

public class QuickStart {
public static class Person {
String name;
int age;

Person(String name, int age) {
this.name = name;
this.age = age;
}
}

public static void main(String[] args) {
Fory fory = Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(true)
.build();
fory.register(Person.class);
byte[] bytes = fory.serialize(new Person("Alice", 30));
Person result = (Person) fory.deserialize(bytes);
System.out.println(result.name + " " + result.age);
}
}

Scala

// SBT Scala 2.13
libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "0.13.1"
// SBT Scala 3
libraryDependencies += "org.apache.fory" % "fory-scala_3" % "0.13.1"
import org.apache.fory.Fory
import org.apache.fory.config.Language
import org.apache.fory.serializer.scala.ScalaSerializers

case class Person(name: String, age: Int)

object Example {
def main(args: Array[String]): Unit = {
val fory = Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(true)
.build()
ScalaSerializers.registerSerializers(fory)
fory.register(classOf[Person])
val bytes = fory.serialize(Person("Alice", 30))
val result = fory.deserialize(bytes).asInstanceOf[Person]
println(s"${result.name} ${result.age}")
}
}

Kotlin

<dependency>
<groupId>org.apache.fory</groupId>
<artifactId>fory-kotlin</artifactId>
<version>0.13.1</version>
</dependency>
import org.apache.fory.Fory
import org.apache.fory.config.Language
import org.apache.fory.serializer.kotlin.KotlinSerializers

data class Person(val name: String, val age: Int)

fun main() {
val fory = Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(true)
.build()
KotlinSerializers.registerSerializers(fory)
fory.register(Person::class.java)
val bytes = fory.serialize(Person("Alice", 30))
val result = fory.deserialize(bytes) as Person
println("${result.name} ${result.age}")
}

Golang

package main

import (
"fmt"

forygo "github.com/apache/fory/go/fory"
)

type Person struct {
Name string
Age int32
}

func main() {
fory := forygo.NewFory(true)
fory.Register(Person{}, 1)
payload, _ := fory.Marshal(Person{Name: "Alice", Age: 30})
var decoded Person
fory.Unmarshal(payload, &decoded)
fmt.Println(decoded.Name, decoded.Age)
}

JavaScript

import Fory, { Type } from "@apache-fory/fory";
import hps from "@apache-fory/hps";

const description = Type.object("example.Person", {
name: Type.string(),
age: Type.int32(),
});

const fory = new Fory({ hps });
const { serialize, deserialize } = fory.registerSerializer(description);
const payload = serialize({ name: "Alice", age: 30 });
const decoded = deserialize(payload);
console.log(decoded.name, decoded.age);

🎁 新特性

🐛 Bug 修复

🔨 其他改进

👥 新贡献者

完整变更日志: https://github.com/apache/fory/compare/v0.13.0...v0.13.1