Skip to content

Commit 5d7a87c

Browse files
authored
Get underling values of Fields (#49)
* Add support for using Field* as their underlying type * Documentation for new field support * Tests for field support with underlying read * Update expected_output.txt to account for extra newline in test output * Move field documentation to new file * Remove read and as_ref methods on fields * Switch to rust stable to work around rust-lang/rust#115746 * Use clone to copy std::string * Update test expectation * Install lld and explicitly set CC for clang++ builds * Remove errant read() from docs
1 parent d694bee commit 5d7a87c

File tree

7 files changed

+136
-12
lines changed

7 files changed

+136
-12
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@ jobs:
1717
steps:
1818
- uses: actions/checkout@v3
1919
- uses: mymindstorm/setup-emsdk@v14
20-
- name: Install Rust nightly
20+
- name: Install Rust stable
2121
run: |
22-
rustup toolchain install nightly
23-
rustup default nightly
22+
rustup toolchain install stable
2423
rustup target add wasm32-wasip1
2524
rustup component add rustfmt
2625
- name: Install Clang 19
2726
run: brew install llvm@19
2827
- name: Install wasmtime
2928
uses: bytecodealliance/actions/wasmtime/setup@v1
30-
- run: CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci
29+
- run: CC=$(brew --prefix llvm@19)/bin/clang CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci
3130

3231
build:
3332
runs-on: ubuntu-latest
@@ -42,19 +41,26 @@ jobs:
4241
steps:
4342
- uses: actions/checkout@v3
4443
- uses: mymindstorm/setup-emsdk@v14
45-
- name: Install Rust nightly
44+
- name: Install Rust stable
4645
run: |
47-
rustup toolchain install nightly
48-
rustup default nightly
46+
rustup toolchain install stable
4947
rustup target add wasm32-wasip1
5048
rustup component add rustfmt
5149
- name: Install osmium
5250
run: sudo apt install libosmium2-dev
51+
- name: Install LLD for clang++
52+
if: matrix.cpp_compiler == 'clang++'
53+
run: sudo apt-get update && sudo apt-get install -y lld
5354
- name: Install wasmtime
5455
uses: bytecodealliance/actions/wasmtime/setup@v1
5556
- name: Cache WASI SDK
5657
uses: actions/cache@v3
5758
with:
5859
path: examples/tutorial-wasm32/wasi-sdk-25.0-x86_64-linux
5960
key: wasi-sdk-25.0-x86_64-linux-v1
60-
- run: cargo xtask ci
61+
- name: Run CI (clang++)
62+
if: matrix.cpp_compiler == 'clang++'
63+
run: CC=clang cargo xtask ci
64+
- name: Run CI (g++)
65+
if: matrix.cpp_compiler == 'g++'
66+
run: cargo xtask ci

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [Name mapping](./call_rust_from_cpp/name_mapping.md)
88
- [Wellknown traits](./call_rust_from_cpp/wellknown_traits.md)
99
- [Layout policy](./call_rust_from_cpp/layout_policy.md)
10+
- [Fields](./call_rust_from_cpp/fields.md)
1011
- [Types with special support](./call_rust_from_cpp/special_types.md)
1112
- [Panic and exceptions](./call_rust_from_cpp/panic_and_exceptions.md)
1213
- [Calling C++ from Rust](./call_cpp_from_rust/index.md)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Fields as underlying types
2+
3+
When you declare fields in a tuple or struct using `field name (offset = X, type = T);`, the generated C++ exposes helper wrapper types:
4+
5+
- `rust::FieldOwned<T, OFFSET>` for fields on owning types
6+
- `rust::FieldRef<T, OFFSET>` for fields on `Ref<Ty>`
7+
- `rust::FieldRefMut<T, OFFSET>` for fields on `RefMut<Ty>`
8+
9+
These wrappers now act as their underlying type `T` in many contexts:
10+
11+
- `Ref<T>` construction from any `Field*<T, OFFSET>`
12+
- Implicit read via `operator T()` for value-like access
13+
- Method calls are forwarded when applicable
14+
15+
Example:
16+
17+
```C++
18+
rust::Tuple<int32_t, rust::std::string::String> t{42, "hi"_rs.to_owned()};
19+
20+
// Read value
21+
int32_t v = t.f0; // operator T() on FieldOwned<int32_t, 0>
22+
23+
// Get a Ref<T> from a field
24+
rust::Ref<int32_t> r = t.f0;
25+
26+
// Access methods through Ref from Field wrappers
27+
rust::Ref<rust::std::string::String> sref = t.f1;
28+
auto len = sref.len();
29+
30+
// From references to container, fields become FieldRef/FieldRefMut
31+
rust::Ref<decltype(t)> rt = t;
32+
auto l1 = rt.f1.len();
33+
34+
rust::RefMut<decltype(t)> mt = t;
35+
mt.f1.push_str("!"_rs);
36+
```
37+
38+
See `examples/regression_test1` for a runnable demonstration.

examples/regression_test1/expected_output.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,13 @@ Test fields and constructor work -- started
4949
[main.cpp:59] v4.f1.field2.len() = 12
5050
Test fields and constructor work -- finished
5151

52+
Test Field* underlying conversions -- started
53+
[main.cpp:70] v0 = 42
54+
[main.cpp:74] v1 = "hi"
55+
[main.cpp:78] sref.len() = 2
56+
[main.cpp:81] int32_t(pref.f0) = 42
57+
[main.cpp:82] pref.f1.len() = 2
58+
[main.cpp:85] int32_t(pmut.f0) = 42
59+
[main.cpp:87] pmut.f1.len() = 3
60+
Test Field* underlying conversions -- finished
61+

examples/regression_test1/main.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,36 @@ void test_fields_and_constructor() {
5959
zngur_dbg(v4.f1.field2.len());
6060
}
6161

62+
void test_field_underlying_conversions() {
63+
auto scope = rust::crate::Scoped::new_("Test Field* underlying conversions"_rs);
64+
65+
rust::Tuple<int32_t, rust::std::string::String> pair{42, "hi"_rs.to_owned()};
66+
67+
// FieldOwned conversion to Ref and value
68+
rust::Ref<int32_t> r0 = pair.f0;
69+
int32_t v0 = pair.f0;
70+
zngur_dbg(v0);
71+
// Types which are not `Copy` cannot support implicit conversion to T.
72+
// We must use `.clone()` or similar methods to get a copy.
73+
rust::std::string::String v1 = pair.f1.clone();
74+
zngur_dbg(v1);
75+
76+
// FieldOwned<String> to Ref<String> and call a method
77+
rust::Ref<rust::std::string::String> sref = pair.f1;
78+
zngur_dbg(sref.len());
79+
80+
rust::Ref<rust::Tuple<int32_t, rust::std::string::String>> pref = pair;
81+
zngur_dbg(int32_t(pref.f0));
82+
zngur_dbg(pref.f1.len());
83+
84+
rust::RefMut<rust::Tuple<int32_t, rust::std::string::String>> pmut = pair;
85+
zngur_dbg(int32_t(pmut.f0));
86+
pmut.f1.push_str("!"_rs);
87+
zngur_dbg(pmut.f1.len());
88+
}
89+
6290
int main() {
6391
test_dbg_works_for_ref_and_refmut();
6492
test_fields_and_constructor();
93+
test_field_underlying_conversions();
6594
}

examples/regression_test1/main.zng

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type ::std::string::String {
1212
#layout(size = 24, align = 8);
1313
wellknown_traits(Debug);
1414

15+
fn clone(&self) -> ::std::string::String;
1516
fn push_str(&mut self, &str);
1617
fn len(&self) -> usize;
1718
}
@@ -32,8 +33,16 @@ type (::std::string::String, crate::Foo) {
3233
field 1 (offset = 24, type = crate::Foo);
3334
}
3435

36+
type (i32, ::std::string::String) {
37+
#layout(size = 32, align = 8);
38+
wellknown_traits(Debug);
39+
40+
field 0 (offset = 0, type = i32);
41+
field 1 (offset = 8, type = ::std::string::String);
42+
}
43+
3544
type crate::Scoped {
3645
#layout(size = 16, align = 8);
3746

3847
fn new(&str) -> crate::Scoped;
39-
}
48+
}

zngur-generator/src/cpp.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,13 +1658,19 @@ namespace rust {
16581658
struct RefMut;
16591659
16601660
template<typename T, size_t OFFSET>
1661-
struct FieldOwned;
1661+
struct FieldOwned {
1662+
inline operator T() const noexcept { return *::rust::Ref<T>(*this); }
1663+
};
16621664
16631665
template<typename T, size_t OFFSET>
1664-
struct FieldRef;
1666+
struct FieldRef {
1667+
inline operator T() const noexcept { return *::rust::Ref<T>(*this); }
1668+
};
16651669
16661670
template<typename T, size_t OFFSET>
1667-
struct FieldRefMut;
1671+
struct FieldRefMut {
1672+
inline operator T() const noexcept { return *::rust::Ref<T>(*this); }
1673+
};
16681674
16691675
template<typename... T>
16701676
struct Tuple;
@@ -1759,6 +1765,21 @@ namespace rust {
17591765
data = reinterpret_cast<size_t>(__zngur_internal_data_ptr(t));
17601766
}}
17611767
1768+
template<size_t OFFSET>
1769+
Ref(const FieldOwned< {ty}, OFFSET >& f) {{
1770+
data = reinterpret_cast<size_t>(&f) + OFFSET;
1771+
}}
1772+
1773+
template<size_t OFFSET>
1774+
Ref(const FieldRef< {ty}, OFFSET >& f) {{
1775+
data = *reinterpret_cast<const size_t*>(&f) + OFFSET;
1776+
}}
1777+
1778+
template<size_t OFFSET>
1779+
Ref(const FieldRefMut< {ty}, OFFSET >& f) {{
1780+
data = *reinterpret_cast<const size_t*>(&f) + OFFSET;
1781+
}}
1782+
17621783
{ty}& operator*() {{
17631784
return *reinterpret_cast< {ty}*>(data);
17641785
}}
@@ -1777,6 +1798,16 @@ namespace rust {
17771798
data = reinterpret_cast<size_t>(__zngur_internal_data_ptr(t));
17781799
}}
17791800
1801+
template<size_t OFFSET>
1802+
RefMut(const FieldOwned< {ty}, OFFSET >& f) {{
1803+
data = reinterpret_cast<size_t>(&f) + OFFSET;
1804+
}}
1805+
1806+
template<size_t OFFSET>
1807+
RefMut(const FieldRefMut< {ty}, OFFSET >& f) {{
1808+
data = *reinterpret_cast<const size_t*>(&f) + OFFSET;
1809+
}}
1810+
17801811
{ty}& operator*() {{
17811812
return *reinterpret_cast< {ty}*>(data);
17821813
}}

0 commit comments

Comments
 (0)