2020Stub Generation
2121===============
2222
23- TVM-FFI provides ``tvm-ffi-stubgen ``, a tool that generates Python type stubs from C++
24- reflection metadata. It turns registered global functions and classes into proper Python
25- type hints, enabling IDE auto-completion and static type checking.
23+ TVM-FFI provides ``tvm-ffi-stubgen ``, a tool that generates typed language bindings from C++
24+ reflection metadata. It turns registered global functions and classes into native type hints,
25+ enabling IDE auto-completion and static type checking.
26+
27+ The sections below describe the default Python target. The Rust target is covered in
28+ :ref: `sec-stubgen-rust `.
2629
2730.. admonition :: Prerequisite
2831 :class: hint
@@ -46,6 +49,7 @@ This runs stub generation automatically after each build.
4649 [STUB_INIT ON|OFF]
4750 [STUB_PKG <pkg>]
4851 [STUB_PREFIX <prefix>]
52+ [STUB_TARGET python|rust]
4953 )
5054
5155 From the example's
@@ -239,6 +243,9 @@ All three are required together. When omitted, the tool operates in directive-on
239243
240244**Optional arguments: **
241245
246+ ``--target ``
247+ Code generator backend: ``python `` (default) or ``rust ``. See :ref: `sec-stubgen-rust `.
248+
242249``--verbose ``
243250 Print a unified diff of changes to each file.
244251
@@ -253,6 +260,153 @@ All three are required together. When omitted, the tool operates in directive-on
253260
254261For a complete list of options, run ``tvm-ffi-stubgen --help ``.
255262
263+ .. _sec-stubgen-rust :
264+
265+ Rust Code Stubgen (Experimental)
266+ --------------------------------
267+
268+ TVM-FFI provides an efficient, easy-to-use mechanism for exposing C++ classes to Rust.
269+ Objects share the same memory representation, so Rust code can directly access objects
270+ created in C++. However, users have to hand-write the Rust definition of each registered
271+ object to avoid memory layout and alignment mismatches between the two sides. To eliminate
272+ this manual work, ``tvm-ffi-stubgen `` supports generating Rust code directly.
273+
274+ To generate Rust stubs, pass ``STUB_TARGET rust `` to ``tvm_ffi_configure_target ``
275+ (see :ref: `sec-stubgen-cmake `) or ``--target rust `` on the command line
276+ (see :ref: `sec-stubgen-cli `).
277+
278+ Key Features
279+ ~~~~~~~~~~~~
280+
281+ - Generate Rust code for registered C++ classes automatically (via CLI or CMake).
282+ - Mirror the C++ memory layout exactly in Rust.
283+ - Provide Rust-native builder-style constructors for registered classes, with
284+ reflected default values prefilled and overridable through setters.
285+ - Expose methods of registered classes through cross-language calls.
286+
287+ Generation Output
288+ ~~~~~~~~~~~~~~~~~
289+
290+ This section uses `examples/rust_stubgen <https://github.com/apache/tvm-ffi/tree/main/examples/rust_stubgen >`_
291+ as the running example. The library registers a single type, ``rust_stubgen.IntPair ``,
292+ with two required fields ``a `` / ``b ``, a defaulted field ``scale ``, and a method ``sum ``:
293+
294+ .. literalinclude :: ../../examples/rust_stubgen/src/int_pair.cc
295+ :language: cpp
296+ :start-after: [object.begin]
297+ :end-before: [object.end]
298+
299+ For this type the tool generates the following Rust code (bodies abridged):
300+
301+ .. code-block :: rust
302+
303+ #[repr(C)]
304+ #[derive(tvm_ffi::derive::Object)]
305+ #[type_key = "rust_stubgen.IntPair"]
306+ pub struct IntPairObj {
307+ base: Object, // the parent type, embedded as the first field
308+ pub a: i64,
309+ pub b: i64,
310+ pub scale: i64,
311+ }
312+
313+ #[repr(C)]
314+ #[derive(tvm_ffi::derive::ObjectRef, Clone)]
315+ pub struct IntPair {
316+ data: ObjectArc<IntPairObj>,
317+ }
318+
319+ impl IntPair {
320+ pub fn ffi_new() -> IntPairBuilder { /* ... */ }
321+ pub fn sum(&mut self) -> Result<i64> { /* ... */ }
322+ }
323+
324+ pub struct IntPairBuilder { /* base + every field */ }
325+
326+ impl IntPairBuilder {
327+ pub fn a(mut self, a: i64) -> Self { /* ... */ }
328+ pub fn b(mut self, b: i64) -> Self { /* ... */ }
329+ pub fn scale(mut self, scale: i64) -> Self { /* ... */ }
330+ pub fn build(self) -> Result<IntPair> { /* ... */ }
331+ pub fn build_obj(self) -> Result<IntPairObj> { /* ... */ }
332+ }
333+
334+ ``IntPairObj `` mirrors the C++ memory layout exactly, so Rust code can directly
335+ access objects created in C++ and vice versa. ``IntPair `` is the reference type
336+ that owns an allocation of it; fields are read through ``Deref `` (and written
337+ through ``DerefMut ``, since the class declares ``_type_mutable = true ``), and
338+ ``sum `` calls into the C++ implementation through the FFI.
339+
340+ Builder-style Construction
341+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
342+
343+ Construction is fully Rust-native -- no FFI call is involved -- and uniform: a
344+ nullary ``ffi_new() `` opens the builder, every field is set through its
345+ like-named consuming setter, and ``build() `` finishes the chain:
346+
347+ .. code-block :: rust
348+
349+ let pair = IntPair::ffi_new().a(1).b(2).build()?; // scale = 1 (default)
350+ let scaled = IntPair::ffi_new().a(1).b(2).scale(10).build()?; // override the default
351+ let err = IntPair::ffi_new().a(1).build(); // Err: field `b` is not set
352+
353+ ``ffi_new() -> IntPairBuilder ``
354+ Opens the builder. A field with a ``refl::default_value `` (here ``scale ``)
355+ starts prefilled with its default, rendered as a Rust literal at
356+ stub-generation time; every other field starts unset.
357+
358+ ``a(..) `` / ``b(..) `` / ``scale(..) ``
359+ One consuming setter per field. Setting a defaulted field overrides its
360+ default.
361+
362+ ``build() -> Result<IntPair> ``
363+ Validates and allocates: returns an error if a field without a default is
364+ still unset (the ``err `` case above), otherwise wraps the assembled value
365+ in ``ObjectArc `` and returns the reference type. This is the endpoint to
366+ use in ordinary code.
367+
368+ ``build_obj() -> Result<IntPairObj> ``
369+ Performs the same validation and assembly as ``build() `` -- ``build() `` in
370+ fact delegates to it -- but stops at the bare, unallocated struct value.
371+ It exists for inheritance: a C++ class deriving from ``IntPair `` embeds
372+ ``IntPairObj `` as its first field, and the derived type's generated builder
373+ gains a ``base(..) `` setter that takes exactly this value:
374+
375+ .. code-block :: rust
376+
377+ // for a hypothetical `Derived` extending IntPair with a field `c`
378+ let d = Derived::ffi_new()
379+ .base(IntPair::ffi_new().a(1).b(2).build_obj()?)
380+ .c(3)
381+ .build()?;
382+
383+ When ``base `` is left unset, the derived ``build() `` falls back to
384+ default-constructing the parent through its all-default builder. This
385+ succeeds silently when every parent field has a default; for ``IntPair ``
386+ it would fail with an error naming ``base ``, since ``a `` and ``b `` carry
387+ no default.
388+
389+ The builder deliberately bypasses any C++ constructor logic (it never runs
390+ ``IntPairObj ``'s C++ constructor); users who need the faithful C++ semantics
391+ can hand-write a ``new `` constructor (outside the generated markers) on top of
392+ the builder.
393+
394+ Limitations
395+ ~~~~~~~~~~~
396+
397+ A type that mentions an origin the Rust crate cannot represent -- in any
398+ position: field, method argument, return type, or nested inside another
399+ container -- is explicitly unsupported: the whole binding is skipped with a
400+ warning. This covers ``Map `` / ``Dict `` / ``List `` / ``Union `` (no Rust
401+ counterpart) as well as ``Optional `` / ``tuple `` (std ``Option<T> `` and Rust
402+ tuples do not match the C++ ``ffi::Optional `` / ``ffi::Tuple `` memory layout).
403+
404+ A constructor alone cannot be generated when a default comes from a
405+ ``refl::default_factory `` or when a default value has no Rust literal
406+ rendering: such types are emitted without ``ffi_new `` (a warning explains why),
407+ and construction stays on the C++ side -- e.g. through a ``def_static ``
408+ factory.
409+
256410.. _sec-stubgen-advanced :
257411
258412Advanced Topics
@@ -391,7 +545,8 @@ When you run the tool, it:
391545 # tvm-ffi-stubgen(import-object): ffi.Object;False;_ffi_Object
392546
393547 This imports ``ffi.Object `` as ``_ffi_Object `` for use in generated code. The second
394- field (``False ``) indicates the import is not TYPE_CHECKING-only.
548+ field (``False ``) indicates the import is not TYPE_CHECKING-only. The Rust target
549+ records the name as a plain ``use `` and ignores the other two fields.
395550
396551``skip-file `` - Skip File
397552 Prevents the tool from modifying the file. Place anywhere in the file.
0 commit comments