Skip to content

Commit e637cf7

Browse files
authored
feat(rust): support thread safe Arc<dyn Any + Send + Sync> type (#3736)
## Why? ## What does this PR do? ## Related issues Closes #3732 ## AI Contribution Checklist - [ ] Substantial AI assistance was used in this PR: `yes` / `no` - [ ] If `yes`, I included a completed [AI Contribution Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs) in this PR description and the required `AI Usage Disclosure`. - [ ] If `yes`, my PR description includes the required `ai_review` summary and screenshot evidence of the final clean AI review results from both fresh reviewers on the current PR diff or current HEAD after the latest code changes. ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark
1 parent a95a126 commit e637cf7

32 files changed

Lines changed: 1082 additions & 294 deletions

compiler/extension/fory_options.proto

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,11 @@ message ForyFieldOptions {
179179
optional bool weak_ref = 4;
180180

181181
// Generate thread-safe Rust pointer carriers for ref fields.
182-
// When true, Rust codegen uses Arc/ArcWeak instead of Rc/RcWeak.
182+
// Rust codegen uses Arc/ArcWeak by default. Set this to false to generate
183+
// Rc/RcWeak for ref fields that must stay single-threaded.
183184
// This does not change the wire format and does not make the referenced
184185
// value itself thread-safe.
185-
// Default: false
186+
// Default: true
186187
optional bool thread_safe_pointer = 5;
187188
}
188189

compiler/fory_compiler/generators/rust.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
ArrayType,
3535
MapType,
3636
Schema,
37+
thread_safe_pointer_enabled,
3738
)
3839
from fory_compiler.ir.types import PrimitiveKind
3940

@@ -43,6 +44,7 @@ class RustGenerator(BaseGenerator):
4344

4445
language_name = "rust"
4546
file_extension = ".rs"
47+
RUST_ANY_TYPE = "::std::sync::Arc<dyn ::std::any::Any + Send + Sync>"
4648

4749
# Mapping from FDL primitive types to Rust types
4850
PRIMITIVE_MAP = {
@@ -62,7 +64,7 @@ class RustGenerator(BaseGenerator):
6264
PrimitiveKind.STRING: "::std::string::String",
6365
PrimitiveKind.BYTES: "::std::vec::Vec<u8>",
6466
PrimitiveKind.DECIMAL: "::fory::Decimal",
65-
PrimitiveKind.ANY: "::std::boxed::Box<dyn ::std::any::Any>",
67+
PrimitiveKind.ANY: RUST_ANY_TYPE,
6668
}
6769

6870
FORY_TEMPORAL_MAP = {
@@ -1012,12 +1014,12 @@ def generate_type(
10121014
element_optional: bool = False,
10131015
element_ref: bool = False,
10141016
parent_stack: Optional[List[Message]] = None,
1015-
pointer_type: str = "::std::rc::Rc",
1017+
pointer_type: str = "::std::sync::Arc",
10161018
) -> str:
10171019
"""Generate Rust type string."""
10181020
if isinstance(field_type, PrimitiveType):
10191021
if field_type.kind == PrimitiveKind.ANY:
1020-
return "::std::boxed::Box<dyn ::std::any::Any>"
1022+
return self.RUST_ANY_TYPE
10211023
base_type = self.primitive_type_name(field_type.kind)
10221024
if nullable:
10231025
return f"::std::option::Option<{base_type}>"
@@ -1152,7 +1154,7 @@ def get_field_pointer_type(self, field: Field) -> str:
11521154

11531155
def get_pointer_type(self, ref_options: dict, weak_ref: bool = False) -> str:
11541156
"""Determine pointer type for ref tracking based on field options."""
1155-
if ref_options.get("thread_safe_pointer") is True:
1157+
if thread_safe_pointer_enabled(ref_options):
11561158
return "::fory::ArcWeak" if weak_ref else "::std::sync::Arc"
11571159
return "::fory::RcWeak" if weak_ref else "::std::rc::Rc"
11581160

compiler/fory_compiler/ir/ast.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222

2323
from fory_compiler.ir.types import PrimitiveKind
2424

25+
THREAD_SAFE_POINTER_DEFAULT = True
26+
27+
28+
def thread_safe_pointer_enabled(ref_options: dict) -> bool:
29+
"""Return the effective Rust pointer-carrier default for ref options."""
30+
return ref_options.get("thread_safe_pointer", THREAD_SAFE_POINTER_DEFAULT) is True
31+
2532

2633
@dataclass(frozen=True)
2734
class SourceLocation:

compiler/fory_compiler/tests/test_generated_code.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ def test_rust_nested_container_ref_uses_correct_pointer_type():
216216
}
217217
218218
message Request {
219-
list<list<ref(thread_safe=true) Node>> groups = 1;
220-
map<string, map<string, ref(thread_safe=true) Node>> nodes = 2;
219+
list<list<ref Node>> groups = 1;
220+
map<string, map<string, ref Node>> nodes = 2;
221221
}
222222
"""
223223
)
@@ -497,7 +497,7 @@ def test_generated_code_map_types_equivalent():
497497
assert "SharedWeak<MapValue>" in cpp_output
498498

499499

500-
def test_rust_generated_ref_pointer_default_and_thread_safe_option():
500+
def test_rust_generated_ref_pointer_default_and_opt_out():
501501
schema = parse_fdl(
502502
dedent(
503503
"""
@@ -509,25 +509,24 @@ def test_rust_generated_ref_pointer_default_and_thread_safe_option():
509509
510510
message Holder {
511511
ref Node default_ref = 1;
512-
ref(thread_safe=true) Node thread_safe_ref = 2;
512+
ref(thread_safe=false) Node rc_ref = 2;
513513
ref(weak=true) Node default_weak_ref = 3;
514-
ref(weak=true, thread_safe=true) Node thread_safe_weak_ref = 4;
514+
ref(weak=true, thread_safe=false) Node rc_weak_ref = 4;
515515
list<ref Node> default_ref_list = 5;
516-
list<ref(thread_safe=true) Node> thread_safe_ref_list = 6;
516+
list<ref(thread_safe=false) Node> rc_ref_list = 6;
517517
}
518518
"""
519519
)
520520
)
521521
rust_output = render_files(generate_files(schema, RustGenerator))
522-
assert "pub default_ref: ::std::rc::Rc<Node>," in rust_output
523-
assert "pub thread_safe_ref: ::std::sync::Arc<Node>," in rust_output
524-
assert "pub default_weak_ref: ::fory::RcWeak<Node>," in rust_output
525-
assert "pub thread_safe_weak_ref: ::fory::ArcWeak<Node>," in rust_output
526-
assert "pub default_ref_list: ::std::vec::Vec<::std::rc::Rc<Node>>," in rust_output
522+
assert "pub default_ref: ::std::sync::Arc<Node>," in rust_output
523+
assert "pub rc_ref: ::std::rc::Rc<Node>," in rust_output
524+
assert "pub default_weak_ref: ::fory::ArcWeak<Node>," in rust_output
525+
assert "pub rc_weak_ref: ::fory::RcWeak<Node>," in rust_output
527526
assert (
528-
"pub thread_safe_ref_list: ::std::vec::Vec<::std::sync::Arc<Node>>,"
529-
in rust_output
527+
"pub default_ref_list: ::std::vec::Vec<::std::sync::Arc<Node>>," in rust_output
530528
)
529+
assert "pub rc_ref_list: ::std::vec::Vec<::std::rc::Rc<Node>>," in rust_output
531530

532531

533532
def test_generated_code_nested_messages_equivalent():
@@ -779,7 +778,7 @@ def test_generated_code_tree_ref_options_equivalent():
779778
assert_all_languages_equal(schemas)
780779

781780
rust_output = render_files(generate_files(schemas["fdl"], RustGenerator))
782-
assert "RcWeak<TreeNode>" in rust_output
781+
assert "ArcWeak<TreeNode>" in rust_output
783782
assert "#[derive(::fory::ForyStruct, Clone, PartialEq, Eq, Default)]" in rust_output
784783

785784
cpp_output = render_files(generate_files(schemas["fdl"], CppGenerator))
@@ -1153,8 +1152,11 @@ def test_rust_generated_code_uses_absolute_paths():
11531152
"pub labels: ::std::collections::HashMap<::std::string::String, ::std::string::String>,"
11541153
in rust_output
11551154
)
1156-
assert "pub payload: ::std::boxed::Box<dyn ::std::any::Any>," in rust_output
1157-
assert "pub parent: ::fory::RcWeak<String>," in rust_output
1155+
assert (
1156+
"pub payload: ::std::sync::Arc<dyn ::std::any::Any + Send + Sync>,"
1157+
in rust_output
1158+
)
1159+
assert "pub parent: ::fory::ArcWeak<String>," in rust_output
11581160
assert "pub fn register_types(fory: &mut ::fory::Fory)" in rust_output
11591161
assert "static FORY: ::std::sync::OnceLock<::fory::Fory>" in rust_output
11601162

compiler/fory_compiler/tests/test_weak_ref.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test_weak_ref_requires_repeated_ref():
122122
)
123123

124124

125-
def test_list_and_map_ref_options_with_thread_safe():
125+
def test_list_and_map_ref_options_preserve_explicit_opt_out():
126126
source = """
127127
message Foo {
128128
int32 id = 1;
@@ -134,8 +134,8 @@ def test_list_and_map_ref_options_with_thread_safe():
134134
135135
message Holder {
136136
list<ref Foo> foos = 1;
137-
list<ref(weak=true, thread_safe=true) Bar> bars = 2;
138-
map<Foo, ref(weak=true, thread_safe=true) Bar> bar_map = 3;
137+
list<ref(weak=true, thread_safe=false) Bar> bars = 2;
138+
map<Foo, ref(weak=true, thread_safe=false) Bar> bar_map = 3;
139139
}
140140
"""
141141
schema = parse_schema(source)
@@ -152,8 +152,8 @@ def test_list_and_map_ref_options_with_thread_safe():
152152

153153
assert bars.element_ref is True
154154
assert bars.element_ref_options.get("weak_ref") is True
155-
assert bars.element_ref_options.get("thread_safe_pointer") is True
155+
assert bars.element_ref_options.get("thread_safe_pointer") is False
156156

157157
assert bar_map.field_type.value_ref is True
158158
assert bar_map.field_type.value_ref_options.get("weak_ref") is True
159-
assert bar_map.field_type.value_ref_options.get("thread_safe_pointer") is True
159+
assert bar_map.field_type.value_ref_options.get("thread_safe_pointer") is False

docs/compiler/flatbuffers-idl.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,20 @@ FlatBuffers metadata attributes use `key:value`. For Fory-specific options, use
156156

157157
### Supported Field Attributes
158158

159-
| FlatBuffers Attribute | Effect in Fory |
160-
| ------------------------------- | -------------------------------------------------------------------------------- |
161-
| `fory_ref:true` | Enable reference tracking for the field |
162-
| `fory_nullable:true` | Mark field optional/nullable |
163-
| `fory_weak_ref:true` | Enable weak reference semantics and implies `ref` |
164-
| `fory_thread_safe_pointer:true` | For ref fields, select Rust `Arc`/`ArcWeak` instead of the default `Rc`/`RcWeak` |
159+
| FlatBuffers Attribute | Effect in Fory |
160+
| -------------------------------- | -------------------------------------------------------------------------------- |
161+
| `fory_ref:true` | Enable reference tracking for the field |
162+
| `fory_nullable:true` | Mark field optional/nullable |
163+
| `fory_weak_ref:true` | Enable weak reference semantics and implies `ref` |
164+
| `fory_thread_safe_pointer:false` | For ref fields, select Rust `Rc`/`RcWeak` instead of the default `Arc`/`ArcWeak` |
165165

166166
Semantics:
167167

168168
- `fory_weak_ref:true` implies `ref`.
169-
- `fory_thread_safe_pointer` defaults to `false`, only takes effect when the field
169+
- `fory_thread_safe_pointer` defaults to `true`, only takes effect when the field
170170
is ref-tracked, and does not change the wire format.
171-
- In Rust codegen, `fory_weak_ref:true` uses `RcWeak` by default and switches to
172-
`ArcWeak` only when `fory_thread_safe_pointer:true` is also set.
171+
- In Rust codegen, `fory_weak_ref:true` uses `ArcWeak` by default and switches to
172+
`RcWeak` only when `fory_thread_safe_pointer:false` is set.
173173
- For list fields, `fory_ref:true` applies to list elements.
174174

175175
Example:
@@ -178,7 +178,7 @@ Example:
178178
table Node {
179179
parent: Node (fory_weak_ref: true);
180180
children: [Node] (fory_ref: true);
181-
cached: Node (fory_ref: true, fory_thread_safe_pointer: true);
181+
local: Node (fory_ref: true, fory_thread_safe_pointer: false);
182182
}
183183
```
184184

docs/compiler/protobuf-idl.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,34 +232,35 @@ message TreeNode {
232232

233233
### Field-Level Options
234234

235-
| Option | Type | Description |
236-
| ---------------------------- | ------ | --------------------------------------------------------------------------- |
237-
| `(fory).ref` | bool | Enable reference tracking for this field |
238-
| `(fory).nullable` | bool | Treat field as nullable (`optional`) |
239-
| `(fory).weak_ref` | bool | Generate weak pointer semantics (C++/Rust codegen) |
240-
| `(fory).thread_safe_pointer` | bool | Use Rust `Arc`/`ArcWeak` for ref fields; default `false` uses `Rc`/`RcWeak` |
241-
| `(fory).deprecated` | bool | Mark field as deprecated |
242-
| `(fory).type` | string | Primitive override for tagged 64-bit integer encoding |
235+
| Option | Type | Description |
236+
| ---------------------------- | ------ | ---------------------------------------------------------------------------------------------------- |
237+
| `(fory).ref` | bool | Enable reference tracking for this field |
238+
| `(fory).nullable` | bool | Treat field as nullable (`optional`) |
239+
| `(fory).weak_ref` | bool | Generate weak pointer semantics (C++/Rust codegen) |
240+
| `(fory).thread_safe_pointer` | bool | Rust ref carrier selection; default `true` uses `Arc`/`ArcWeak`, explicit `false` uses `Rc`/`RcWeak` |
241+
| `(fory).deprecated` | bool | Mark field as deprecated |
242+
| `(fory).type` | string | Primitive override for tagged 64-bit integer encoding |
243243

244244
Reference option behavior:
245245

246246
- `weak_ref = true` implies ref tracking.
247247
- For `repeated` fields, `(fory).ref = true` applies to list elements.
248248
- For `map<K, V>` fields, `(fory).ref = true` applies to map values.
249249
- `weak_ref` and `thread_safe_pointer` are codegen hints for C++/Rust.
250-
- `thread_safe_pointer` defaults to `false`; it changes only the generated Rust
250+
- `thread_safe_pointer` defaults to `true`; it changes only the generated Rust
251251
pointer carrier and does not change the wire format.
252-
- In Rust codegen, `(fory).weak_ref = true` uses `RcWeak` by default and switches
253-
to `ArcWeak` only when `(fory).thread_safe_pointer = true`.
252+
- In Rust codegen, `(fory).weak_ref = true` uses `ArcWeak` by default and
253+
switches to `RcWeak` only when `(fory).thread_safe_pointer = false`.
254254

255255
### Option Examples by Shape
256256

257257
```protobuf
258258
message Graph {
259-
Node root = 1 [(fory).ref = true, (fory).thread_safe_pointer = true];
259+
Node root = 1 [(fory).ref = true];
260260
repeated Node nodes = 2 [(fory).ref = true];
261261
map<string, Node> cache = 3 [(fory).ref = true];
262262
Node parent = 4 [(fory).weak_ref = true];
263+
Node local = 5 [(fory).ref = true, (fory).thread_safe_pointer = false];
263264
}
264265
```
265266

0 commit comments

Comments
 (0)