From a6786efe1f2e21d95b333bc48e79cb043f8b7568 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Thu, 14 Aug 2025 08:55:48 -0400 Subject: [PATCH 01/11] Add support for using Field* as their underlying type --- zngur-generator/src/cpp.rs | 43 +++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/zngur-generator/src/cpp.rs b/zngur-generator/src/cpp.rs index 3d47a05a..6501812e 100644 --- a/zngur-generator/src/cpp.rs +++ b/zngur-generator/src/cpp.rs @@ -1658,13 +1658,25 @@ namespace rust { struct RefMut; template - struct FieldOwned; + struct FieldOwned { + inline ::rust::Ref as_ref() const noexcept { return ::rust::Ref(*this); } + inline T read() const noexcept { auto r = as_ref(); return *r; } + inline operator T() const noexcept { return read(); } + }; template - struct FieldRef; + struct FieldRef { + inline ::rust::Ref as_ref() const noexcept { return ::rust::Ref(*this); } + inline T read() const noexcept { auto r = as_ref(); return *r; } + inline operator T() const noexcept { return read(); } + }; template - struct FieldRefMut; + struct FieldRefMut { + inline ::rust::Ref as_ref() const noexcept { return ::rust::Ref(*this); } + inline T read() const noexcept { auto r = as_ref(); return *r; } + inline operator T() const noexcept { return read(); } + }; template struct Tuple; @@ -1759,6 +1771,21 @@ namespace rust { data = reinterpret_cast(__zngur_internal_data_ptr(t)); }} + template + Ref(const FieldOwned< {ty}, OFFSET >& f) {{ + data = reinterpret_cast(&f) + OFFSET; + }} + + template + Ref(const FieldRef< {ty}, OFFSET >& f) {{ + data = *reinterpret_cast(&f) + OFFSET; + }} + + template + Ref(const FieldRefMut< {ty}, OFFSET >& f) {{ + data = *reinterpret_cast(&f) + OFFSET; + }} + {ty}& operator*() {{ return *reinterpret_cast< {ty}*>(data); }} @@ -1777,6 +1804,16 @@ namespace rust { data = reinterpret_cast(__zngur_internal_data_ptr(t)); }} + template + RefMut(const FieldOwned< {ty}, OFFSET >& f) {{ + data = reinterpret_cast(&f) + OFFSET; + }} + + template + RefMut(const FieldRefMut< {ty}, OFFSET >& f) {{ + data = *reinterpret_cast(&f) + OFFSET; + }} + {ty}& operator*() {{ return *reinterpret_cast< {ty}*>(data); }} From 0807f30f0df3ced1ea017cd2e47400e67022f622 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Thu, 14 Aug 2025 09:17:55 -0400 Subject: [PATCH 02/11] Documentation for new field support --- book/src/call_rust_from_cpp/special_types.md | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/book/src/call_rust_from_cpp/special_types.md b/book/src/call_rust_from_cpp/special_types.md index 64b77f80..a7ff30fd 100644 --- a/book/src/call_rust_from_cpp/special_types.md +++ b/book/src/call_rust_from_cpp/special_types.md @@ -17,3 +17,42 @@ tries to support them using C++ feature called User-defined Literals. | `'a'_rs_b` | `b'a'` | `uint8_t` | Not Implemented | unconditionally | | `"hello"_rs_b` | `b"hello"` | `rust::Ref>` | Not Implemented | `[u8]` | | `"hello"_rs_c` | `c"hello"` | `rust::Ref` | Not Implemented | `::rust::ffi::CStr` | + +## 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` for fields on owning types +- `rust::FieldRef` for fields on `Ref` +- `rust::FieldRefMut` for fields on `RefMut` + +These wrappers now act as their underlying type `T` in many contexts: + +- `Ref` construction from any `Field*` +- Implicit read via `operator T()` and `.read()` for value-like access +- Method calls are forwarded when applicable + +Example: + +```C++ +rust::Tuple t{42, "hi"_rs.to_owned()}; + +// Read value +int32_t v = t.f0; // operator T() on FieldOwned + +// Get a Ref from a field +rust::Ref r = t.f0; + +// Access methods through Ref from Field wrappers +rust::Ref sref = t.f1; +auto len = sref.len(); + +// From references to container, fields become FieldRef/FieldRefMut +rust::Ref rt = t; +auto l1 = rt.f1.len(); + +rust::RefMut mt = t; +mt.f1.push_str("!"_rs); +``` + +See `examples/regression_test1` for a runnable demonstration. From f8fcbf63435cc0c18c2106cfc91d4c33ef63a654 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Thu, 14 Aug 2025 09:18:43 -0400 Subject: [PATCH 03/11] Tests for field support with underlying read --- examples/regression_test1/expected_output.txt | 8 ++++++ examples/regression_test1/main.cpp | 25 +++++++++++++++++++ examples/regression_test1/main.zng | 8 ++++++ 3 files changed, 41 insertions(+) diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index 47eff4f3..8008fd56 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -49,3 +49,11 @@ 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] sref.len() = 2 +[main.cpp:77] pref.f0.read() = 42 +[main.cpp:78] pref.f1.len() = 2 +[main.cpp:81] pmut.f0.read() = 42 +[main.cpp:83] pmut.f1.len() = 3 +Test Field* underlying conversions -- finished diff --git a/examples/regression_test1/main.cpp b/examples/regression_test1/main.cpp index 7465cf0e..0923c570 100644 --- a/examples/regression_test1/main.cpp +++ b/examples/regression_test1/main.cpp @@ -59,7 +59,32 @@ 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 pair{42, "hi"_rs.to_owned()}; + + // FieldOwned conversion to Ref and value + rust::Ref r0 = pair.f0; // Ref from FieldOwned + int32_t v0 = pair.f0; // value read via operator T() + zngur_dbg(v0); + + // FieldOwned to Ref and call a method + rust::Ref sref = pair.f1; + zngur_dbg(sref.len()); + + rust::Ref> pref = pair; + zngur_dbg(pref.f0.read()); + zngur_dbg(pref.f1.len()); + + rust::RefMut> pmut = pair; + zngur_dbg(pmut.f0.read()); + 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(); } diff --git a/examples/regression_test1/main.zng b/examples/regression_test1/main.zng index 66872cae..df2f96d5 100644 --- a/examples/regression_test1/main.zng +++ b/examples/regression_test1/main.zng @@ -32,6 +32,14 @@ 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); From d4d558ac9845da847d66e49f1c450924df8d307b Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Thu, 14 Aug 2025 09:25:23 -0400 Subject: [PATCH 04/11] Update expected_output.txt to account for extra newline in test output --- examples/regression_test1/expected_output.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index 8008fd56..4bfc2275 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -57,3 +57,4 @@ Test Field* underlying conversions -- started [main.cpp:81] pmut.f0.read() = 42 [main.cpp:83] pmut.f1.len() = 3 Test Field* underlying conversions -- finished + From dab808842ba9ff2f077c4e7fb0f26b2b65a884f6 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Fri, 15 Aug 2025 11:04:50 -0400 Subject: [PATCH 05/11] Move field documentation to new file --- book/src/SUMMARY.md | 1 + book/src/call_rust_from_cpp/fields.md | 38 +++++++++++++++++++ book/src/call_rust_from_cpp/special_types.md | 39 -------------------- 3 files changed, 39 insertions(+), 39 deletions(-) create mode 100644 book/src/call_rust_from_cpp/fields.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 95708932..04a326f3 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -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) diff --git a/book/src/call_rust_from_cpp/fields.md b/book/src/call_rust_from_cpp/fields.md new file mode 100644 index 00000000..096c37b9 --- /dev/null +++ b/book/src/call_rust_from_cpp/fields.md @@ -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` for fields on owning types +- `rust::FieldRef` for fields on `Ref` +- `rust::FieldRefMut` for fields on `RefMut` + +These wrappers now act as their underlying type `T` in many contexts: + +- `Ref` construction from any `Field*` +- Implicit read via `operator T()` and `.read()` for value-like access +- Method calls are forwarded when applicable + +Example: + +```C++ +rust::Tuple t{42, "hi"_rs.to_owned()}; + +// Read value +int32_t v = t.f0; // operator T() on FieldOwned + +// Get a Ref from a field +rust::Ref r = t.f0; + +// Access methods through Ref from Field wrappers +rust::Ref sref = t.f1; +auto len = sref.len(); + +// From references to container, fields become FieldRef/FieldRefMut +rust::Ref rt = t; +auto l1 = rt.f1.len(); + +rust::RefMut mt = t; +mt.f1.push_str("!"_rs); +``` + +See `examples/regression_test1` for a runnable demonstration. diff --git a/book/src/call_rust_from_cpp/special_types.md b/book/src/call_rust_from_cpp/special_types.md index a7ff30fd..64b77f80 100644 --- a/book/src/call_rust_from_cpp/special_types.md +++ b/book/src/call_rust_from_cpp/special_types.md @@ -17,42 +17,3 @@ tries to support them using C++ feature called User-defined Literals. | `'a'_rs_b` | `b'a'` | `uint8_t` | Not Implemented | unconditionally | | `"hello"_rs_b` | `b"hello"` | `rust::Ref>` | Not Implemented | `[u8]` | | `"hello"_rs_c` | `c"hello"` | `rust::Ref` | Not Implemented | `::rust::ffi::CStr` | - -## 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` for fields on owning types -- `rust::FieldRef` for fields on `Ref` -- `rust::FieldRefMut` for fields on `RefMut` - -These wrappers now act as their underlying type `T` in many contexts: - -- `Ref` construction from any `Field*` -- Implicit read via `operator T()` and `.read()` for value-like access -- Method calls are forwarded when applicable - -Example: - -```C++ -rust::Tuple t{42, "hi"_rs.to_owned()}; - -// Read value -int32_t v = t.f0; // operator T() on FieldOwned - -// Get a Ref from a field -rust::Ref r = t.f0; - -// Access methods through Ref from Field wrappers -rust::Ref sref = t.f1; -auto len = sref.len(); - -// From references to container, fields become FieldRef/FieldRefMut -rust::Ref rt = t; -auto l1 = rt.f1.len(); - -rust::RefMut mt = t; -mt.f1.push_str("!"_rs); -``` - -See `examples/regression_test1` for a runnable demonstration. From 36cfa7f62fb506b87af232f6f6e07f525d586c46 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Fri, 15 Aug 2025 11:12:00 -0400 Subject: [PATCH 06/11] Remove read and as_ref methods on fields --- examples/regression_test1/expected_output.txt | 10 +++++----- examples/regression_test1/main.cpp | 10 ++++++---- examples/regression_test1/main.zng | 2 +- zngur-generator/src/cpp.rs | 12 +++--------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index 4bfc2275..20212313 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -51,10 +51,10 @@ Test fields and constructor work -- finished Test Field* underlying conversions -- started [main.cpp:70] v0 = 42 -[main.cpp:74] sref.len() = 2 -[main.cpp:77] pref.f0.read() = 42 -[main.cpp:78] pref.f1.len() = 2 -[main.cpp:81] pmut.f0.read() = 42 -[main.cpp:83] pmut.f1.len() = 3 +[main.cpp:76] sref.len() = 2 +[main.cpp:79] int32_t(pref.f0) = 42 +[main.cpp:80] pref.f1.len() = 2 +[main.cpp:83] int32_t(pmut.f0) = 42 +[main.cpp:85] pmut.f1.len() = 3 Test Field* underlying conversions -- finished diff --git a/examples/regression_test1/main.cpp b/examples/regression_test1/main.cpp index 0923c570..10570627 100644 --- a/examples/regression_test1/main.cpp +++ b/examples/regression_test1/main.cpp @@ -65,20 +65,22 @@ void test_field_underlying_conversions() { rust::Tuple pair{42, "hi"_rs.to_owned()}; // FieldOwned conversion to Ref and value - rust::Ref r0 = pair.f0; // Ref from FieldOwned - int32_t v0 = pair.f0; // value read via operator T() + rust::Ref r0 = pair.f0; + int32_t v0 = pair.f0; zngur_dbg(v0); + // TODO: Add support for conversion to T for all fields. + // rust::std::string::String v1 = pair.f1; // FieldOwned to Ref and call a method rust::Ref sref = pair.f1; zngur_dbg(sref.len()); rust::Ref> pref = pair; - zngur_dbg(pref.f0.read()); + zngur_dbg(int32_t(pref.f0)); zngur_dbg(pref.f1.len()); rust::RefMut> pmut = pair; - zngur_dbg(pmut.f0.read()); + zngur_dbg(int32_t(pmut.f0)); pmut.f1.push_str("!"_rs); zngur_dbg(pmut.f1.len()); } diff --git a/examples/regression_test1/main.zng b/examples/regression_test1/main.zng index df2f96d5..2acc378e 100644 --- a/examples/regression_test1/main.zng +++ b/examples/regression_test1/main.zng @@ -44,4 +44,4 @@ type crate::Scoped { #layout(size = 16, align = 8); fn new(&str) -> crate::Scoped; -} \ No newline at end of file +} diff --git a/zngur-generator/src/cpp.rs b/zngur-generator/src/cpp.rs index 6501812e..c7d93a7b 100644 --- a/zngur-generator/src/cpp.rs +++ b/zngur-generator/src/cpp.rs @@ -1659,23 +1659,17 @@ namespace rust { template struct FieldOwned { - inline ::rust::Ref as_ref() const noexcept { return ::rust::Ref(*this); } - inline T read() const noexcept { auto r = as_ref(); return *r; } - inline operator T() const noexcept { return read(); } + inline operator T() const noexcept { return *::rust::Ref(*this); } }; template struct FieldRef { - inline ::rust::Ref as_ref() const noexcept { return ::rust::Ref(*this); } - inline T read() const noexcept { auto r = as_ref(); return *r; } - inline operator T() const noexcept { return read(); } + inline operator T() const noexcept { return *::rust::Ref(*this); } }; template struct FieldRefMut { - inline ::rust::Ref as_ref() const noexcept { return ::rust::Ref(*this); } - inline T read() const noexcept { auto r = as_ref(); return *r; } - inline operator T() const noexcept { return read(); } + inline operator T() const noexcept { return *::rust::Ref(*this); } }; template From c5b992d9dd2ef71375a85c082f6509ca535879e4 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Fri, 15 Aug 2025 11:13:30 -0400 Subject: [PATCH 07/11] Switch to rust stable to work around rust-lang/rust#115746 --- .github/workflows/ci.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4bbbfb5..b80d3a10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,9 @@ 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 @@ -42,10 +41,9 @@ 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 From 94d0ec91cfddbf48155c03b1cad68c1c6170f2e4 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 18 Aug 2025 18:03:11 -0400 Subject: [PATCH 08/11] Use clone to copy std::string --- examples/regression_test1/main.cpp | 6 ++++-- examples/regression_test1/main.zng | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/regression_test1/main.cpp b/examples/regression_test1/main.cpp index 10570627..fa5b22f2 100644 --- a/examples/regression_test1/main.cpp +++ b/examples/regression_test1/main.cpp @@ -68,8 +68,10 @@ void test_field_underlying_conversions() { rust::Ref r0 = pair.f0; int32_t v0 = pair.f0; zngur_dbg(v0); - // TODO: Add support for conversion to T for all fields. - // rust::std::string::String v1 = pair.f1; + // 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 to Ref and call a method rust::Ref sref = pair.f1; diff --git a/examples/regression_test1/main.zng b/examples/regression_test1/main.zng index 2acc378e..35819b7c 100644 --- a/examples/regression_test1/main.zng +++ b/examples/regression_test1/main.zng @@ -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; } From ea38b4c8bc62716e3a2478ee14e774858bb9ca4f Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 18 Aug 2025 18:08:25 -0400 Subject: [PATCH 09/11] Update test expectation --- examples/regression_test1/expected_output.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/regression_test1/expected_output.txt b/examples/regression_test1/expected_output.txt index 20212313..7c14583a 100644 --- a/examples/regression_test1/expected_output.txt +++ b/examples/regression_test1/expected_output.txt @@ -51,10 +51,11 @@ Test fields and constructor work -- finished Test Field* underlying conversions -- started [main.cpp:70] v0 = 42 -[main.cpp:76] sref.len() = 2 -[main.cpp:79] int32_t(pref.f0) = 42 -[main.cpp:80] pref.f1.len() = 2 -[main.cpp:83] int32_t(pmut.f0) = 42 -[main.cpp:85] pmut.f1.len() = 3 +[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 From 2128929213e8b1e69cd714648f0b513008c5c990 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 18 Aug 2025 18:51:07 -0400 Subject: [PATCH 10/11] Install lld and explicitly set CC for clang++ builds --- .github/workflows/ci.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b80d3a10..31aa8c5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: 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 @@ -48,6 +48,9 @@ jobs: 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 @@ -55,4 +58,9 @@ jobs: 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 From 51bdb9dfa4f2e630fa3a3aa6e025b068e8c1a632 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Tue, 19 Aug 2025 10:35:31 -0400 Subject: [PATCH 11/11] Remove errant read() from docs --- book/src/call_rust_from_cpp/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/call_rust_from_cpp/fields.md b/book/src/call_rust_from_cpp/fields.md index 096c37b9..7d037992 100644 --- a/book/src/call_rust_from_cpp/fields.md +++ b/book/src/call_rust_from_cpp/fields.md @@ -9,7 +9,7 @@ When you declare fields in a tuple or struct using `field name (offset = X, type These wrappers now act as their underlying type `T` in many contexts: - `Ref` construction from any `Field*` -- Implicit read via `operator T()` and `.read()` for value-like access +- Implicit read via `operator T()` for value-like access - Method calls are forwarded when applicable Example: