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
22 changes: 14 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: mymindstorm/setup-emsdk@v14
- name: Install Rust nightly
- name: Install Rust stable
run: |
rustup toolchain install nightly
rustup default nightly
rustup toolchain install stable
rustup target add wasm32-wasip1
rustup component add rustfmt
- name: Install Clang 19
run: brew install llvm@19
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
- run: CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci
- run: CC=$(brew --prefix llvm@19)/bin/clang CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci

build:
runs-on: ubuntu-latest
Expand All @@ -42,19 +41,26 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: mymindstorm/setup-emsdk@v14
- name: Install Rust nightly
- name: Install Rust stable
run: |
rustup toolchain install nightly
rustup default nightly
rustup toolchain install stable
rustup target add wasm32-wasip1
rustup component add rustfmt
- name: Install osmium
run: sudo apt install libosmium2-dev
- name: Install LLD for clang++
if: matrix.cpp_compiler == 'clang++'
run: sudo apt-get update && sudo apt-get install -y lld
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
- name: Cache WASI SDK
uses: actions/cache@v3
with:
path: examples/tutorial-wasm32/wasi-sdk-25.0-x86_64-linux
key: wasi-sdk-25.0-x86_64-linux-v1
- run: cargo xtask ci
- name: Run CI (clang++)
if: matrix.cpp_compiler == 'clang++'
run: CC=clang cargo xtask ci
- name: Run CI (g++)
if: matrix.cpp_compiler == 'g++'
run: cargo xtask ci
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Name mapping](./call_rust_from_cpp/name_mapping.md)
- [Wellknown traits](./call_rust_from_cpp/wellknown_traits.md)
- [Layout policy](./call_rust_from_cpp/layout_policy.md)
- [Fields](./call_rust_from_cpp/fields.md)
- [Types with special support](./call_rust_from_cpp/special_types.md)
- [Panic and exceptions](./call_rust_from_cpp/panic_and_exceptions.md)
- [Calling C++ from Rust](./call_cpp_from_rust/index.md)
Expand Down
38 changes: 38 additions & 0 deletions book/src/call_rust_from_cpp/fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Fields as underlying types

When you declare fields in a tuple or struct using `field name (offset = X, type = T);`, the generated C++ exposes helper wrapper types:

- `rust::FieldOwned<T, OFFSET>` for fields on owning types
- `rust::FieldRef<T, OFFSET>` for fields on `Ref<Ty>`
- `rust::FieldRefMut<T, OFFSET>` for fields on `RefMut<Ty>`

These wrappers now act as their underlying type `T` in many contexts:

- `Ref<T>` construction from any `Field*<T, OFFSET>`
- Implicit read via `operator T()` for value-like access
- Method calls are forwarded when applicable

Example:

```C++
rust::Tuple<int32_t, rust::std::string::String> t{42, "hi"_rs.to_owned()};

// Read value
int32_t v = t.f0; // operator T() on FieldOwned<int32_t, 0>

// Get a Ref<T> from a field
rust::Ref<int32_t> r = t.f0;

// Access methods through Ref from Field wrappers
rust::Ref<rust::std::string::String> sref = t.f1;
auto len = sref.len();

// From references to container, fields become FieldRef/FieldRefMut
rust::Ref<decltype(t)> rt = t;
auto l1 = rt.f1.len();

rust::RefMut<decltype(t)> mt = t;
mt.f1.push_str("!"_rs);
```

See `examples/regression_test1` for a runnable demonstration.
10 changes: 10 additions & 0 deletions examples/regression_test1/expected_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,13 @@ Test fields and constructor work -- started
[main.cpp:59] v4.f1.field2.len() = 12
Test fields and constructor work -- finished

Test Field* underlying conversions -- started
[main.cpp:70] v0 = 42
[main.cpp:74] v1 = "hi"
[main.cpp:78] sref.len() = 2
[main.cpp:81] int32_t(pref.f0) = 42
[main.cpp:82] pref.f1.len() = 2
[main.cpp:85] int32_t(pmut.f0) = 42
[main.cpp:87] pmut.f1.len() = 3
Test Field* underlying conversions -- finished

29 changes: 29 additions & 0 deletions examples/regression_test1/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,36 @@ void test_fields_and_constructor() {
zngur_dbg(v4.f1.field2.len());
}

void test_field_underlying_conversions() {
auto scope = rust::crate::Scoped::new_("Test Field* underlying conversions"_rs);

rust::Tuple<int32_t, rust::std::string::String> pair{42, "hi"_rs.to_owned()};

// FieldOwned conversion to Ref and value
rust::Ref<int32_t> r0 = pair.f0;
int32_t v0 = pair.f0;
zngur_dbg(v0);
// Types which are not `Copy` cannot support implicit conversion to T.
// We must use `.clone()` or similar methods to get a copy.
rust::std::string::String v1 = pair.f1.clone();
zngur_dbg(v1);

// FieldOwned<String> to Ref<String> and call a method
rust::Ref<rust::std::string::String> sref = pair.f1;
zngur_dbg(sref.len());

rust::Ref<rust::Tuple<int32_t, rust::std::string::String>> pref = pair;
zngur_dbg(int32_t(pref.f0));
zngur_dbg(pref.f1.len());

rust::RefMut<rust::Tuple<int32_t, rust::std::string::String>> pmut = pair;
zngur_dbg(int32_t(pmut.f0));
pmut.f1.push_str("!"_rs);
zngur_dbg(pmut.f1.len());
}

int main() {
test_dbg_works_for_ref_and_refmut();
test_fields_and_constructor();
test_field_underlying_conversions();
}
11 changes: 10 additions & 1 deletion examples/regression_test1/main.zng
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ::std::string::String {
#layout(size = 24, align = 8);
wellknown_traits(Debug);

fn clone(&self) -> ::std::string::String;
fn push_str(&mut self, &str);
fn len(&self) -> usize;
}
Expand All @@ -32,8 +33,16 @@ type (::std::string::String, crate::Foo) {
field 1 (offset = 24, type = crate::Foo);
}

type (i32, ::std::string::String) {
#layout(size = 32, align = 8);
wellknown_traits(Debug);

field 0 (offset = 0, type = i32);
field 1 (offset = 8, type = ::std::string::String);
}

type crate::Scoped {
#layout(size = 16, align = 8);

fn new(&str) -> crate::Scoped;
}
}
37 changes: 34 additions & 3 deletions zngur-generator/src/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1658,13 +1658,19 @@ namespace rust {
struct RefMut;

template<typename T, size_t OFFSET>
struct FieldOwned;
struct FieldOwned {
inline operator T() const noexcept { return *::rust::Ref<T>(*this); }
};

template<typename T, size_t OFFSET>
struct FieldRef;
struct FieldRef {
inline operator T() const noexcept { return *::rust::Ref<T>(*this); }
};

template<typename T, size_t OFFSET>
struct FieldRefMut;
struct FieldRefMut {
inline operator T() const noexcept { return *::rust::Ref<T>(*this); }
};

template<typename... T>
struct Tuple;
Expand Down Expand Up @@ -1759,6 +1765,21 @@ namespace rust {
data = reinterpret_cast<size_t>(__zngur_internal_data_ptr(t));
}}

template<size_t OFFSET>
Ref(const FieldOwned< {ty}, OFFSET >& f) {{
data = reinterpret_cast<size_t>(&f) + OFFSET;
}}

template<size_t OFFSET>
Ref(const FieldRef< {ty}, OFFSET >& f) {{
data = *reinterpret_cast<const size_t*>(&f) + OFFSET;
}}

template<size_t OFFSET>
Ref(const FieldRefMut< {ty}, OFFSET >& f) {{
data = *reinterpret_cast<const size_t*>(&f) + OFFSET;
}}

{ty}& operator*() {{
return *reinterpret_cast< {ty}*>(data);
}}
Expand All @@ -1777,6 +1798,16 @@ namespace rust {
data = reinterpret_cast<size_t>(__zngur_internal_data_ptr(t));
}}

template<size_t OFFSET>
RefMut(const FieldOwned< {ty}, OFFSET >& f) {{
data = reinterpret_cast<size_t>(&f) + OFFSET;
}}

template<size_t OFFSET>
RefMut(const FieldRefMut< {ty}, OFFSET >& f) {{
data = *reinterpret_cast<const size_t*>(&f) + OFFSET;
}}

{ty}& operator*() {{
return *reinterpret_cast< {ty}*>(data);
}}
Expand Down