Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler/extension/fory_options.proto
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,11 @@ message ForyFieldOptions {
optional bool weak_ref = 4;

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

Expand Down
10 changes: 6 additions & 4 deletions compiler/fory_compiler/generators/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
ArrayType,
MapType,
Schema,
thread_safe_pointer_enabled,
)
from fory_compiler.ir.types import PrimitiveKind

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

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

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

FORY_TEMPORAL_MAP = {
Expand Down Expand Up @@ -1012,12 +1014,12 @@ def generate_type(
element_optional: bool = False,
element_ref: bool = False,
parent_stack: Optional[List[Message]] = None,
pointer_type: str = "::std::rc::Rc",
pointer_type: str = "::std::sync::Arc",
) -> str:
"""Generate Rust type string."""
if isinstance(field_type, PrimitiveType):
if field_type.kind == PrimitiveKind.ANY:
return "::std::boxed::Box<dyn ::std::any::Any>"
return self.RUST_ANY_TYPE
base_type = self.primitive_type_name(field_type.kind)
if nullable:
return f"::std::option::Option<{base_type}>"
Expand Down Expand Up @@ -1152,7 +1154,7 @@ def get_field_pointer_type(self, field: Field) -> str:

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

Expand Down
7 changes: 7 additions & 0 deletions compiler/fory_compiler/ir/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@

from fory_compiler.ir.types import PrimitiveKind

THREAD_SAFE_POINTER_DEFAULT = True


def thread_safe_pointer_enabled(ref_options: dict) -> bool:
"""Return the effective Rust pointer-carrier default for ref options."""
return ref_options.get("thread_safe_pointer", THREAD_SAFE_POINTER_DEFAULT) is True


@dataclass(frozen=True)
class SourceLocation:
Expand Down
34 changes: 18 additions & 16 deletions compiler/fory_compiler/tests/test_generated_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ def test_rust_nested_container_ref_uses_correct_pointer_type():
}

message Request {
list<list<ref(thread_safe=true) Node>> groups = 1;
map<string, map<string, ref(thread_safe=true) Node>> nodes = 2;
list<list<ref Node>> groups = 1;
map<string, map<string, ref Node>> nodes = 2;
}
"""
)
Expand Down Expand Up @@ -497,7 +497,7 @@ def test_generated_code_map_types_equivalent():
assert "SharedWeak<MapValue>" in cpp_output


def test_rust_generated_ref_pointer_default_and_thread_safe_option():
def test_rust_generated_ref_pointer_default_and_opt_out():
schema = parse_fdl(
dedent(
"""
Expand All @@ -509,25 +509,24 @@ def test_rust_generated_ref_pointer_default_and_thread_safe_option():

message Holder {
ref Node default_ref = 1;
ref(thread_safe=true) Node thread_safe_ref = 2;
ref(thread_safe=false) Node rc_ref = 2;
ref(weak=true) Node default_weak_ref = 3;
ref(weak=true, thread_safe=true) Node thread_safe_weak_ref = 4;
ref(weak=true, thread_safe=false) Node rc_weak_ref = 4;
list<ref Node> default_ref_list = 5;
list<ref(thread_safe=true) Node> thread_safe_ref_list = 6;
list<ref(thread_safe=false) Node> rc_ref_list = 6;
}
"""
)
)
rust_output = render_files(generate_files(schema, RustGenerator))
assert "pub default_ref: ::std::rc::Rc<Node>," in rust_output
assert "pub thread_safe_ref: ::std::sync::Arc<Node>," in rust_output
assert "pub default_weak_ref: ::fory::RcWeak<Node>," in rust_output
assert "pub thread_safe_weak_ref: ::fory::ArcWeak<Node>," in rust_output
assert "pub default_ref_list: ::std::vec::Vec<::std::rc::Rc<Node>>," in rust_output
assert "pub default_ref: ::std::sync::Arc<Node>," in rust_output
assert "pub rc_ref: ::std::rc::Rc<Node>," in rust_output
assert "pub default_weak_ref: ::fory::ArcWeak<Node>," in rust_output
assert "pub rc_weak_ref: ::fory::RcWeak<Node>," in rust_output
assert (
"pub thread_safe_ref_list: ::std::vec::Vec<::std::sync::Arc<Node>>,"
in rust_output
"pub default_ref_list: ::std::vec::Vec<::std::sync::Arc<Node>>," in rust_output
)
assert "pub rc_ref_list: ::std::vec::Vec<::std::rc::Rc<Node>>," in rust_output


def test_generated_code_nested_messages_equivalent():
Expand Down Expand Up @@ -779,7 +778,7 @@ def test_generated_code_tree_ref_options_equivalent():
assert_all_languages_equal(schemas)

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

cpp_output = render_files(generate_files(schemas["fdl"], CppGenerator))
Expand Down Expand Up @@ -1153,8 +1152,11 @@ def test_rust_generated_code_uses_absolute_paths():
"pub labels: ::std::collections::HashMap<::std::string::String, ::std::string::String>,"
in rust_output
)
assert "pub payload: ::std::boxed::Box<dyn ::std::any::Any>," in rust_output
assert "pub parent: ::fory::RcWeak<String>," in rust_output
assert (
"pub payload: ::std::sync::Arc<dyn ::std::any::Any + Send + Sync>,"
in rust_output
)
assert "pub parent: ::fory::ArcWeak<String>," in rust_output
assert "pub fn register_types(fory: &mut ::fory::Fory)" in rust_output
assert "static FORY: ::std::sync::OnceLock<::fory::Fory>" in rust_output

Expand Down
10 changes: 5 additions & 5 deletions compiler/fory_compiler/tests/test_weak_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def test_weak_ref_requires_repeated_ref():
)


def test_list_and_map_ref_options_with_thread_safe():
def test_list_and_map_ref_options_preserve_explicit_opt_out():
source = """
message Foo {
int32 id = 1;
Expand All @@ -134,8 +134,8 @@ def test_list_and_map_ref_options_with_thread_safe():

message Holder {
list<ref Foo> foos = 1;
list<ref(weak=true, thread_safe=true) Bar> bars = 2;
map<Foo, ref(weak=true, thread_safe=true) Bar> bar_map = 3;
list<ref(weak=true, thread_safe=false) Bar> bars = 2;
map<Foo, ref(weak=true, thread_safe=false) Bar> bar_map = 3;
}
"""
schema = parse_schema(source)
Expand All @@ -152,8 +152,8 @@ def test_list_and_map_ref_options_with_thread_safe():

assert bars.element_ref is True
assert bars.element_ref_options.get("weak_ref") is True
assert bars.element_ref_options.get("thread_safe_pointer") is True
assert bars.element_ref_options.get("thread_safe_pointer") is False

assert bar_map.field_type.value_ref is True
assert bar_map.field_type.value_ref_options.get("weak_ref") is True
assert bar_map.field_type.value_ref_options.get("thread_safe_pointer") is True
assert bar_map.field_type.value_ref_options.get("thread_safe_pointer") is False
20 changes: 10 additions & 10 deletions docs/compiler/flatbuffers-idl.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,20 @@ FlatBuffers metadata attributes use `key:value`. For Fory-specific options, use

### Supported Field Attributes

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

Semantics:

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

Example:
Expand All @@ -178,7 +178,7 @@ Example:
table Node {
parent: Node (fory_weak_ref: true);
children: [Node] (fory_ref: true);
cached: Node (fory_ref: true, fory_thread_safe_pointer: true);
local: Node (fory_ref: true, fory_thread_safe_pointer: false);
}
```

Expand Down
25 changes: 13 additions & 12 deletions docs/compiler/protobuf-idl.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,34 +232,35 @@ message TreeNode {

### Field-Level Options

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

Reference option behavior:

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

### Option Examples by Shape

```protobuf
message Graph {
Node root = 1 [(fory).ref = true, (fory).thread_safe_pointer = true];
Node root = 1 [(fory).ref = true];
repeated Node nodes = 2 [(fory).ref = true];
map<string, Node> cache = 3 [(fory).ref = true];
Node parent = 4 [(fory).weak_ref = true];
Node local = 5 [(fory).ref = true, (fory).thread_safe_pointer = false];
}
```

Expand Down
Loading
Loading