This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new e9fdc58f [FEAT][Rust] Replace rs_class placeholders with an UnsafeInit 
creator (#886)
e9fdc58f is described below

commit e9fdc58fa4bf2e60ed701dacac48a01e41a3dc1f
Author: Linzhang Li <[email protected]>
AuthorDate: Wed Oct 28 22:23:44 2026 -0400

    [FEAT][Rust] Replace rs_class placeholders with an UnsafeInit creator (#886)
    
    ## Summary
    
    apache/tvm-ffi#884 made a creator of an `rs_class` type start the object
    from `tvm_ffi::Placeholder`
    values: a Rust reference is never null, so a Rust object cannot start
    empty as a C++ one does. That
    needs a placeholder for the parent's object and for every field type,
    builds a whole placeholder
    object for each reference field, and still leaves each downstream
    project to register `__ffi_new__`
    and call `ffi._RegisterFFIInit` itself.
    
    This PR removes `Placeholder` and the `placeholder` flag, and implements
    C++ `UnsafeInit` in Rust
    instead, without calling into C++ for it. Every `#[rs_class]` type now
    registers a creator, as
    `refl::ObjectDef` registers `ObjectCreatorUnsafeInit<T>`. The creator
    allocates the object zeroed
    and leaves its fields undefined for reflection to set. From it the type
    registers `__ffi_new__` and,
    unless the type defines its own, the `__ffi_init__` that
    `ffi._RegisterFFIInit` generates. A type
    needs no per-type code:
    
    ```rust
    use tvm_ffi::object::ObjectRef;
    use tvm_ffi::{def_object_ref, rs_class, Object};
    
    #[rs_class("my.Point")]
    pub struct PointObj {
        base: Object,
        pub x: i64,
        pub label: tvm_ffi::String,
    }
    
    def_object_ref!(pub Point, ObjectRef, PointObj);
    ```
    
    ```python
    @tvm_ffi.register_object("my.Point")
    class Point(tvm_ffi.Object):
        pass
    
    point = Point(1, "p")  # the generated `__ffi_init__`
    json = tvm_ffi.serialization.to_json_graph_str(point)
    back = tvm_ffi.serialization.from_json_graph_str(json)  # the creator
    ```
---
 docs/guides/rust_lang_guide.md                |  29 ++--
 python/tvm_ffi/stub/rust_generator/codegen.py |  45 +++++-
 rust/tvm-ffi-macros/src/lib.rs                |  20 +--
 rust/tvm-ffi-macros/src/object_macros.rs      |  35 +++--
 rust/tvm-ffi-macros/src/rs_class.rs           |  39 +++--
 rust/tvm-ffi/src/big_int/mod.rs               |   6 +
 rust/tvm-ffi/src/collections/array.rs         |   5 +
 rust/tvm-ffi/src/collections/list.rs          |   5 +
 rust/tvm-ffi/src/collections/map.rs           |   5 +
 rust/tvm-ffi/src/device.rs                    |   6 +
 rust/tvm-ffi/src/lib.rs                       |   4 +-
 rust/tvm-ffi/src/object.rs                    |  12 ++
 rust/tvm-ffi/src/reflection.rs                |   6 +-
 rust/tvm-ffi/src/reflection/placeholder.rs    |  90 -----------
 rust/tvm-ffi/src/reflection/registry.rs       |  85 ++++++++--
 rust/tvm-ffi/src/reflection/unsafe_init.rs    | 213 ++++++++++++++++++++++++++
 rust/tvm-ffi/src/string.rs                    |  12 ++
 rust/tvm-ffi/src/type_traits.rs               |  36 +++++
 rust/tvm-ffi/tests/test_rs_class.rs           | 189 ++++++++++++++++++-----
 tests/python/test_stubgen_rust.py             |  53 +++++++
 20 files changed, 700 insertions(+), 195 deletions(-)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index 3dadfe5e..b6d5431e 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -323,16 +323,25 @@ let point: Point = point3.into();
 `Point3Obj::new`, which takes the parent's object, then the fields: a scalar
 (`bool`, an integer or a float) as its own type, so that a literal infers as 
it,
 and any other field from anything that converts into it, as the `new` of a type
-bound by `tvm-ffi-stubgen` does. The parent's object must implement
-`ShallowCopy`, as `rs_class` types and non-final C++ types bound by
-`tvm-ffi-stubgen` do. With `placeholder` after the type key, `#[rs_class]` also
-implements `Placeholder` for the object, from the placeholders of the parent's
-object and the fields: a valid object for a creator to make before reflection
-sets its fields, as the creator of a C++ type makes an empty one. The reference
-dereferences to the object, and the object converts into it. The reference also
-converts into `ParentType`, the reference to its parent (`ObjectRef` for a type
-under `ffi.Object`); `impl_object_upcast!` converts it into the references to
-further ancestors.
+bound by `tvm-ffi-stubgen` does. The reference dereferences to the object, and
+the object converts into it. The reference also converts into `ParentType`, the
+reference to its parent (`ObjectRef` for a type under `ffi.Object`);
+`impl_object_upcast!` converts it into the references to further ancestors.
+
+The type also registers a creator, as C++ `refl::ObjectDef` registers one from
+the `UnsafeInit` constructor, and from it `__ffi_new__` and `__ffi_init__`
+(unless `reflection = path` defines `__ffi_init__`), so that a JSON graph reads
+the type back and Python constructs it from its fields. The creator makes the
+object with every field undefined, as C++ `UnsafeInit` leaves a reference null,
+and reflection then sets the fields. A Rust reference is never null, so an
+undefined field holds all-zero bytes, which are no value of its type
+(`AnyCompatible::is_undefined`): the field setter overwrites them without
+dropping, the field getter reads them as None and the object's deleter skips
+them. An `init_off` field stays all-zero after `__ffi_init__`, so its type must
+take all-zero bytes as a value, as a scalar or `Option<T>` does; a reference or
+a string does not, and fails the type's registration. The parent's object must
+implement `ShallowCopy` and `UnsafeInit`, as `rs_class` types and non-final C++
+types bound by `tvm-ffi-stubgen` do.
 
 ### Converting Borrowed Values into `Any`
 
diff --git a/python/tvm_ffi/stub/rust_generator/codegen.py 
b/python/tvm_ffi/stub/rust_generator/codegen.py
index ec72d739..0d2629df 100644
--- a/python/tvm_ffi/stub/rust_generator/codegen.py
+++ b/python/tvm_ffi/stub/rust_generator/codegen.py
@@ -27,9 +27,9 @@ the verdict of :mod:`tvm_ffi.stub.layout`:
   ``impl Into``. ``custom-new`` leaves the wrapper's ``new`` to hand-written
   code and names the generated one ``from_fields``. A type that is not final
   can be the parent of a type defined in Rust (``tvm_ffi::rs_class``): its
-  ``<Leaf>Obj::new`` is public, and its object implements ``ShallowCopy`` when
-  every ancestor's is generated too (a ``ty-map``'d one is hand-written and
-  may not). ``<Leaf>Obj::new`` of a final type is crate-private.
+  ``<Leaf>Obj::new`` is public, and its object implements ``ShallowCopy`` and
+  ``UnsafeInit`` when every ancestor's is generated too (a ``ty-map``'d one is
+  hand-written and may not). ``<Leaf>Obj::new`` of a final type is 
crate-private.
 - *opaque*: the struct embeds only its parent, and each field is read through
   the C ABI getter. Nothing allocates it.
 
@@ -601,6 +601,44 @@ class _ObjectRenderer:
             "}",
         ]
 
+    def _unsafe_init_lines(self) -> list[str]:
+        """``UnsafeInit``, which a subtype defined in Rust needs of its 
parent's object.
+
+        It drops the defined fields of an object made with every field 
undefined
+        (all-zero), the parent's first, as the creator of the subtype makes 
it: a
+        ``TVMFFIAny`` cell (``Any``, an ``Optional`` that is not a pointer) 
drops as
+        it is, since all-zero bytes are None; an enum newtype has nothing to 
drop;
+        anything else goes through ``drop_if_defined``, which skips an 
undefined field.
+        Emitted wherever ``ShallowCopy`` is.
+        """
+        unsafe_init = self.imports.record("tvm_ffi::UnsafeInit")
+        addr = self.imports.record("std::ptr::addr_of_mut")
+        directives = self.imports.directives
+        drops = [f"{unsafe_init}::drop_defined_fields({addr}!((*this).base));"]
+        for field in sorted(self.info.fields, key=lambda f: f.offset or 0):
+            target = f"{self.type_key}.{field.name}"
+            if target in directives.enums:
+                continue
+            mirror = self._field_mirror(self.type_key, field, self.imports)
+            assert mirror is not None  # the verdict already ran the 
renderability check
+            any_cell = target not in directives.field_types and (
+                field.origin == "Any"
+                or (field.origin == "Optional" and not 
mirror.startswith("Option<"))
+            )
+            drop = self.imports.record(
+                "std::ptr::drop_in_place" if any_cell else 
"tvm_ffi::reflection::drop_if_defined"
+            )
+            drops.append(f"{drop}({addr}!((*this).{rust_ident(field.name)}));")
+        return [
+            f"unsafe impl {unsafe_init} for {self.obj_struct} {{",
+            "    unsafe fn drop_defined_fields(this: *mut Self) {",
+            "        unsafe {",
+            *[f"            {line}" for line in drops],
+            "        }",
+            "    }",
+            "}",
+        ]
+
     def body(self) -> list[str]:
         """Build the Rust source lines for the object."""
         self.imports.reserved.update((self.leaf, self.obj_struct))
@@ -654,6 +692,7 @@ class _ObjectRenderer:
             shallow_copy = self._shallow_copy_lines()
             if shallow_copy:
                 sections.append(shallow_copy)
+                sections.append(self._unsafe_init_lines())
         upcasts = self._upcast_lines()
         if upcasts:
             sections.append(upcasts)
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 998670a6..7e43b8db 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -145,9 +145,9 @@ pub fn derive_object_ref(input: TokenStream) -> TokenStream 
{
 ///
 /// The first field, `base`, is the parent's object, as the base class of a C++
 /// object is: `base: Object` for a type under `ffi.Object`. The parent's 
object
-/// must implement `ShallowCopy`, as `rs_class` types and non-final C++ types
-/// bound by `tvm-ffi-stubgen` do. Every other field is reflected;
-/// `#[reflect(writable, seq_hash_ignore, ...)]` gives a field the
+/// must implement `ShallowCopy` and [`UnsafeInit`], as `rs_class` types and
+/// non-final C++ types bound by `tvm-ffi-stubgen` do. Every other field is
+/// reflected; `#[reflect(writable, seq_hash_ignore, ...)]` gives a field the
 /// [`FieldFlags`] of those names, and `name` and `doc` as for
 /// `derive(Object)`. The macro adds to the struct:
 ///
@@ -169,20 +169,22 @@ pub fn derive_object_ref(input: TokenStream) -> 
TokenStream {
 ///   `derive(Object)`'s `#[type_final]`, `#[type_s_eq_hash_kind]`,
 ///   `#[type_child_slots]`, `#[type_child_slots_can_overflow]` and
 ///   `#[type_reflection]`.
-/// - `placeholder` also implements [`Placeholder`] for the object, from the
-///   placeholders of the parent's object and the fields, which must implement
-///   it too: the object a creator makes before reflection sets its fields.
 /// - `crate = path` is where the generated code finds tvm-ffi
 ///   (`crate = ::my_lib::tvm_ffi`), for a crate that depends on it only 
through
 ///   a re-export; by default `tvm_ffi`.
 ///
 /// The type is a `derive(Object)` type with `#[type_register]`: it registers 
on
 /// its first `type_index()`, with the fields, the methods,
-/// `__ffi_shallow_copy__` and `__ffi_type_final__`, and without
-/// `__ffi_init__`. A field's type must implement `AnyCompatible` and `Clone`.
+/// `__ffi_shallow_copy__` and `__ffi_type_final__`, and a creator that makes
+/// the object with every field undefined for reflection to set, as C++ makes 
one
+/// with `UnsafeInit`, from which it registers `__ffi_new__` and, unless the
+/// methods define it, `__ffi_init__`; the macro implements [`UnsafeInit`] for
+/// the object. A field's type must implement `AnyCompatible` and `Clone`; an
+/// `init_off` field's type must take all-zero bytes as a value, as
+/// `__ffi_init__` leaves the field so.
 ///
 /// [`FieldFlags`]: ../tvm_ffi/reflection/struct.FieldFlags.html
-/// [`Placeholder`]: ../tvm_ffi/reflection/trait.Placeholder.html
+/// [`UnsafeInit`]: ../tvm_ffi/reflection/trait.UnsafeInit.html
 #[proc_macro_error]
 #[proc_macro_attribute]
 pub fn rs_class(args: TokenStream, item: TokenStream) -> TokenStream {
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs 
b/rust/tvm-ffi-macros/src/object_macros.rs
index 49f9584d..408b1dcc 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -33,12 +33,13 @@ pub fn derive_object(input: proc_macro::TokenStream) -> 
TokenStream {
 /// The `ObjectCore` impl of an object struct, and its `ShallowCopy` when it
 /// registers reflection, naming the tvm-ffi crate `tvm_ffi_crate`.
 ///
-/// `reflect_always` registers the reflection of a type without `#[reflect]`
-/// fields or `#[type_s_eq_hash_kind]` too, as `#[rs_class]` does.
+/// `rs_class` registers the reflection of a type without `#[reflect]` fields
+/// or `#[type_s_eq_hash_kind]` too, with the creator of its `UnsafeInit`, as
+/// `#[rs_class]` does.
 pub(crate) fn expand_object(
     derive_input: &DeriveInput,
     tvm_ffi_crate: &TokenStream2,
-    reflect_always: bool,
+    rs_class: bool,
 ) -> TokenStream2 {
     let struct_name = derive_input.ident.clone();
 
@@ -102,7 +103,7 @@ pub(crate) fn expand_object(
     }
     .expect("First field must be `<base_name>: <ObjectCoreType>`");
 
-    let reflection = Reflection::parse(derive_input, reflect_always);
+    let reflection = Reflection::parse(derive_input, rs_class);
     assert!(
         reflection.is_none() || (type_register && get_attr(derive_input, 
"type_index").is_none()),
         "#[reflect], #[type_s_eq_hash_kind] and #[type_reflection] need 
#[type_register] and no \
@@ -264,6 +265,9 @@ struct Reflection {
     field_ids: Vec<syn::Ident>,
     /// The reflected fields.
     reflected: Vec<ReflectedField>,
+    /// Whether the type registers the creator of its `UnsafeInit`, as an
+    /// `#[rs_class]` type does.
+    unsafe_init: bool,
 }
 
 /// A `#[reflect]` field: its identifier, type, flag names, and the name and
@@ -349,9 +353,9 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String {
 }
 
 impl Reflection {
-    /// The type's reflection, or `None` when it declares none and `always`
-    /// is false.
-    fn parse(derive_input: &DeriveInput, always: bool) -> Option<Self> {
+    /// The type's reflection, or `None` when it declares none and is not an
+    /// `rs_class`.
+    fn parse(derive_input: &DeriveInput, rs_class: bool) -> Option<Self> {
         let kind = get_attr(derive_input, "type_s_eq_hash_kind").map(|attr| {
             let kind = attr_to_str(attr);
             syn::Ident::new(&kind.value(), kind.span())
@@ -388,7 +392,7 @@ impl Reflection {
                 reflected.push(field);
             }
         }
-        if kind.is_none() && methods.is_none() && reflected.is_empty() && 
!always {
+        if kind.is_none() && methods.is_none() && reflected.is_empty() && 
!rs_class {
             return None;
         }
         Some(Self {
@@ -400,6 +404,7 @@ impl Reflection {
                 .expect("First field must be `<base_name>: <ObjectCoreType>`"),
             field_ids,
             reflected,
+            unsafe_init: rs_class,
         })
     }
 
@@ -453,9 +458,16 @@ impl Reflection {
                 #path(&mut def);
             }
         });
+        let creator = if self.unsafe_init {
+            quote!(Some(#reflection::unsafe_init_creator::<#struct_name>))
+        } else {
+            quote!(None)
+        };
         quote! {
             let registered = (|| -> #tvm_ffi_crate::Result<()> {
-                let def = 
#reflection::ObjectDef::<#struct_name>::registering(tindex, #kind)?
+                let def = #reflection::ObjectDef::<#struct_name>::registering(
+                    tindex, #kind, #creator,
+                )?
                     #(#fields)*
                     .shallow_copy()?;
                 #methods
@@ -558,6 +570,11 @@ pub(crate) fn expand_object_ref(
                 <ContainerType as 
#tvm_ffi_crate::object::ObjectCore>::type_index()
             }
 
+            #[inline]
+            unsafe fn is_undefined(field: *const Self) -> bool {
+                
#tvm_ffi_crate::object::ObjectArc::is_null(::core::ptr::addr_of!((*field).#data_id))
+            }
+
             fn type_str() -> std::string::String {
                 type ContainerType = <#struct_name as 
#tvm_ffi_crate::object::ObjectRefCore>
                     ::ContainerType;
diff --git a/rust/tvm-ffi-macros/src/rs_class.rs 
b/rust/tvm-ffi-macros/src/rs_class.rs
index e10de7c7..b19a6ab9 100644
--- a/rust/tvm-ffi-macros/src/rs_class.rs
+++ b/rust/tvm-ffi-macros/src/rs_class.rs
@@ -38,7 +38,6 @@ pub(crate) fn rs_class(args: TokenStream, item: TokenStream) 
-> TokenStream {
 struct RsClassArgs {
     type_key: LitStr,
     type_final: bool,
-    placeholder: bool,
     s_eq_hash_kind: Option<LitStr>,
     child_slots: Option<LitInt>,
     child_slots_can_overflow: Option<LitBool>,
@@ -57,7 +56,6 @@ impl Parse for RsClassArgs {
         let mut args = RsClassArgs {
             type_key,
             type_final: false,
-            placeholder: false,
             s_eq_hash_kind: None,
             child_slots: None,
             child_slots_can_overflow: None,
@@ -76,12 +74,11 @@ impl Parse for RsClassArgs {
             if seen.contains(&key) {
                 return Err(syn::Error::new(name.span(), format!("duplicate 
`{key}`")));
             }
-            if key != "final" && key != "placeholder" {
+            if key != "final" {
                 input.parse::<Token![=]>()?;
             }
             match key.as_str() {
                 "final" => args.type_final = true,
-                "placeholder" => args.placeholder = true,
                 "s_eq_hash_kind" => args.s_eq_hash_kind = Some(input.parse()?),
                 "child_slots" => args.child_slots = Some(input.parse()?),
                 "child_slots_can_overflow" => args.child_slots_can_overflow = 
Some(input.parse()?),
@@ -90,9 +87,9 @@ impl Parse for RsClassArgs {
                 _ => {
                     return Err(syn::Error::new(
                         name.span(),
-                        "expected `final`, `placeholder`, `s_eq_hash_kind = 
\"<kind>\"`, \
-                         `child_slots = <n>`, `child_slots_can_overflow = 
<bool>`, \
-                         `reflection = <path>` or `crate = <path>`",
+                        "expected `final`, `s_eq_hash_kind = \"<kind>\"`, 
`child_slots = <n>`, \
+                         `child_slots_can_overflow = <bool>`, `reflection = 
<path>` or \
+                         `crate = <path>`",
                     ));
                 }
             }
@@ -212,21 +209,23 @@ fn expand(args: &RsClassArgs, item: &DeriveInput) -> 
syn::Result<TokenStream2> {
         }
     })?;
     let object_impl = expand_object(&object_input, &tvm_ffi, true);
-    // The object from the placeholders of the parent's object and the fields, 
which
-    // `new` takes as it takes any value of their types.
-    let placeholder_impl = args.placeholder.then(|| {
-        let field_tys = fields.iter().map(|field| &field.ty);
-        quote! {
-            impl #tvm_ffi::Placeholder for #obj {
-                fn placeholder() -> Self {
-                    Self::new(
-                        <#base_ty as #tvm_ffi::Placeholder>::placeholder(),
-                        #(<#field_tys as 
#tvm_ffi::Placeholder>::placeholder(),)*
-                    )
+    // The parent's fields first, then the others in order, as the drop glue 
drops
+    // them; only through raw pointers, since some may be undefined.
+    let field_ids = fields.iter().map(|field| &field.ident);
+    let unsafe_init_impl = quote! {
+        unsafe impl #tvm_ffi::UnsafeInit for #obj {
+            unsafe fn drop_defined_fields(this: *mut Self) {
+                unsafe {
+                    <#base_ty as #tvm_ffi::UnsafeInit>::drop_defined_fields(
+                        ::core::ptr::addr_of_mut!((*this).#base_id),
+                    );
+                    #(#tvm_ffi::reflection::drop_if_defined(
+                        ::core::ptr::addr_of_mut!((*this).#field_ids),
+                    );)*
                 }
             }
         }
-    });
+    };
     let plain_fields = fields.iter().map(|field| {
         let mut field = (*field).clone();
         field.attrs.retain(|a| !a.path.is_ident("reflect"));
@@ -243,7 +242,7 @@ fn expand(args: &RsClassArgs, item: &DeriveInput) -> 
syn::Result<TokenStream2> {
 
         #object_impl
 
-        #placeholder_impl
+        #unsafe_init_impl
 
         impl ::core::ops::Deref for #obj {
             type Target = #base_ty;
diff --git a/rust/tvm-ffi/src/big_int/mod.rs b/rust/tvm-ffi/src/big_int/mod.rs
index 652dbd41..3fe7acec 100644
--- a/rust/tvm-ffi/src/big_int/mod.rs
+++ b/rust/tvm-ffi/src/big_int/mod.rs
@@ -825,6 +825,12 @@ unsafe impl AnyCompatible for BigInt {
         "BigInt".to_string()
     }
 
+    // A big integer is an inline integer or a big integer object, never None.
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        (*std::ptr::addr_of!((*field).data.type_index)) == 
TypeIndex::kTVMFFINone as i32
+    }
+
     fn type_schema() -> std::string::String {
         crate::type_traits::type_schema(<BigIntObj as 
crate::ObjectCore>::TYPE_KEY, &[])
     }
diff --git a/rust/tvm-ffi/src/collections/array.rs 
b/rust/tvm-ffi/src/collections/array.rs
index 5d6bb115..59f025ef 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -239,6 +239,11 @@ unsafe impl<T> AnyCompatible for Array<T>
 where
     T: ContainerElement + Clone,
 {
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        ObjectArc::is_null(std::ptr::addr_of!((*field).data))
+    }
+
     fn type_str() -> String {
         format!("Array<{}>", T::container_type_str())
     }
diff --git a/rust/tvm-ffi/src/collections/list.rs 
b/rust/tvm-ffi/src/collections/list.rs
index 2aabfacd..63aad17d 100644
--- a/rust/tvm-ffi/src/collections/list.rs
+++ b/rust/tvm-ffi/src/collections/list.rs
@@ -561,6 +561,11 @@ impl<'a, T: ContainerElement> IntoIterator for &'a List<T> 
{
 // --- Any Type System Conversions ---
 
 unsafe impl<T: ContainerElement> AnyCompatible for List<T> {
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        ObjectArc::is_null(std::ptr::addr_of!((*field).data))
+    }
+
     fn type_str() -> String {
         format!("List<{}>", T::container_type_str())
     }
diff --git a/rust/tvm-ffi/src/collections/map.rs 
b/rust/tvm-ffi/src/collections/map.rs
index c7da95eb..0f320a47 100644
--- a/rust/tvm-ffi/src/collections/map.rs
+++ b/rust/tvm-ffi/src/collections/map.rs
@@ -459,6 +459,11 @@ where
     K: ContainerElement,
     V: ContainerElement,
 {
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        ObjectArc::is_null(std::ptr::addr_of!((*field).data))
+    }
+
     fn type_str() -> String {
         format!(
             "Map<{}, {}>",
diff --git a/rust/tvm-ffi/src/device.rs b/rust/tvm-ffi/src/device.rs
index c69129ee..0885edb5 100644
--- a/rust/tvm-ffi/src/device.rs
+++ b/rust/tvm-ffi/src/device.rs
@@ -94,6 +94,12 @@ impl Drop for RestoreStream {
 
 /// AnyCompatible for DLDevice
 unsafe impl AnyCompatible for DLDevice {
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        // No device type is zero; read as an integer, since zero is no 
`DLDeviceType`.
+        *std::ptr::addr_of!((*field).device_type).cast::<u32>() == 0
+    }
+
     fn type_str() -> String {
         // make it consistent with c++ representation
         "Device".to_string()
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index b1d5d285..7d3a810a 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -74,8 +74,8 @@ pub use crate::object::{
 };
 pub use crate::optional::{Optional, OptionalCompatible};
 pub use crate::reflection::{
-    get_type_attr, FieldFlags, FieldGetter, ObjectDef, Placeholder, 
SEqHashKind, ShallowCopy,
-    TypeAttrColumn,
+    get_type_attr, FieldFlags, FieldGetter, ObjectDef, SEqHashKind, 
ShallowCopy, TypeAttrColumn,
+    UnsafeInit,
 };
 pub use crate::rvalue_ref::RValueRef;
 pub use crate::static_init::run_static_init_blocks;
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 93306bdc..df5aae51 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -557,6 +557,18 @@ impl<T: ObjectCore> ObjectArc<T> {
         }
     }
 
+    /// Whether `this` holds a null pointer, as all-zero bytes do: no
+    /// `ObjectArc`, but the field of an object that a creator left undefined.
+    ///
+    /// # Safety
+    ///
+    /// `this` points to an `ObjectArc<T>` or to all-zero bytes.
+    #[doc(hidden)]
+    #[inline]
+    pub unsafe fn is_null(this: *const Self) -> bool {
+        (*std::ptr::addr_of!((*this).ptr).cast::<*const T>()).is_null()
+    }
+
     /// Move a previously allocated object into the ObjectArc
     ///
     /// # Arguments
diff --git a/rust/tvm-ffi/src/reflection.rs b/rust/tvm-ffi/src/reflection.rs
index 92abc4d1..b95aaf04 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection.rs
@@ -30,10 +30,12 @@ use crate::tvm_ffi_sys::{
 };
 use crate::{Any, AnyView, Error, ObjectCore, Result, TYPE_ERROR};
 
-mod placeholder;
 mod registry;
-pub use placeholder::Placeholder;
+mod unsafe_init;
 pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy};
+#[doc(hidden)]
+pub use unsafe_init::{drop_if_defined, unsafe_init_creator};
+pub use unsafe_init::{unsafe_init, UnsafeInit};
 
 /// A registry-owned type-attribute column indexed by runtime type.
 ///
diff --git a/rust/tvm-ffi/src/reflection/placeholder.rs 
b/rust/tvm-ffi/src/reflection/placeholder.rs
deleted file mode 100644
index 9f9d7462..00000000
--- a/rust/tvm-ffi/src/reflection/placeholder.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use crate::collections::list::List;
-use crate::object::Object;
-use crate::optional::{Optional, OptionalCompatible};
-use crate::type_traits::ContainerElement;
-use crate::{Any, Array, Bytes, Map, String};
-
-/// A valid value of an object or field type, which an object made before
-/// reflection sets its fields holds until then.
-///
-/// A C++ object can start empty, its references null, and the reflection then
-/// fills it (the creator of `refl::ObjectDef`); a Rust object cannot, so a
-/// creator of a Rust type (an `__ffi_new__` that a JSON graph or a generated
-/// `__ffi_init__` fills) makes the object from placeholders. `#[rs_class(...,
-/// placeholder)]` implements it for the object, from the placeholders of the
-/// parent's object and of the fields. tvm-ffi implements it for `Object`, the
-/// scalars, `String`, `Bytes`, `Any` and the containers; a field or parent 
type
-/// of another crate implements it there.
-pub trait Placeholder {
-    /// The value.
-    fn placeholder() -> Self;
-}
-
-impl Placeholder for Object {
-    fn placeholder() -> Self {
-        Object::new()
-    }
-}
-
-macro_rules! default_placeholder {
-    ($($ty:ty),* $(,)?) => {
-        $(impl Placeholder for $ty {
-            fn placeholder() -> Self {
-                Self::default()
-            }
-        })*
-    };
-}
-
-default_placeholder!(
-    bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, 
String, Bytes, Any,
-);
-
-impl<T: ContainerElement + Clone> Placeholder for Array<T> {
-    fn placeholder() -> Self {
-        Self::default()
-    }
-}
-
-impl<T: ContainerElement> Placeholder for List<T> {
-    fn placeholder() -> Self {
-        Self::default()
-    }
-}
-
-impl<K: ContainerElement, V: ContainerElement> Placeholder for Map<K, V> {
-    fn placeholder() -> Self {
-        Self::default()
-    }
-}
-
-impl<T: OptionalCompatible> Placeholder for Optional<T> {
-    fn placeholder() -> Self {
-        Self::default()
-    }
-}
-
-impl<T> Placeholder for Option<T> {
-    fn placeholder() -> Self {
-        None
-    }
-}
diff --git a/rust/tvm-ffi/src/reflection/registry.rs 
b/rust/tvm-ffi/src/reflection/registry.rs
index b24807da..8c70ef0d 100644
--- a/rust/tvm-ffi/src/reflection/registry.rs
+++ b/rust/tvm-ffi/src/reflection/registry.rs
@@ -22,10 +22,11 @@
 //! [`ObjectDef`] fills the same type table entries as C++ `refl::ObjectDef<T>`
 //! and Python `_register_py_class`: the type metadata, one record per field
 //! with C ABI getter and setter, one record per method, and the
-//! `__ffi_shallow_copy__` and `__ffi_type_final__` type attributes. Through
-//! them the C++ structural equality, hashing and mutation, `repr`, deep copy
-//! and Python's generated classes reach the fields and methods of a Rust
-//! type.
+//! `__ffi_shallow_copy__` and `__ffi_type_final__` type attributes; for an
+//! `#[rs_class]` type also the creator, and from it the `__ffi_new__` and
+//! `__ffi_init__` type attributes. Through them the C++ structural equality,
+//! hashing and mutation, `repr`, deep copy, JSON graphs and Python's generated
+//! classes reach the fields and methods of a Rust type, and construct it.
 //!
 //! Only `#[derive(Object)]` creates an [`ObjectDef`], for a `#[type_register]`
 //! type with `#[reflect]` fields or a `#[type_reflection]` function; the
@@ -43,9 +44,9 @@ use crate::function_internal::AsPackedCallable;
 use crate::object::{Object, ObjectArc, ObjectCore, ObjectRef, ObjectRefCore};
 use crate::tvm_ffi_sys::{
     TVMFFIAny, TVMFFIByteArray, TVMFFIFieldFlagBitMask, TVMFFIFieldInfo, 
TVMFFIFieldSetter,
-    TVMFFIGetTypeInfo, TVMFFIMethodInfo, TVMFFIObject, TVMFFISEqHashKind, 
TVMFFITypeIndex,
-    TVMFFITypeMetadata, TVMFFITypeRegisterAttr, TVMFFITypeRegisterField,
-    TVMFFITypeRegisterMetadata, TVMFFITypeRegisterMethod,
+    TVMFFIGetTypeInfo, TVMFFIMethodInfo, TVMFFIObject, TVMFFIObjectCreator, 
TVMFFIObjectHandle,
+    TVMFFISEqHashKind, TVMFFITypeIndex, TVMFFITypeMetadata, 
TVMFFITypeRegisterAttr,
+    TVMFFITypeRegisterField, TVMFFITypeRegisterMetadata, 
TVMFFITypeRegisterMethod,
 };
 use crate::type_traits::{type_schema_metadata, AnyCompatible};
 use crate::{Any, Error, Function, Result, INTERNAL_ERROR, TYPE_ERROR, 
VALUE_ERROR};
@@ -171,6 +172,9 @@ impl BitOr for FieldFlags {
 pub struct ObjectDef<N: ObjectCore> {
     type_index: i32,
     fresh: bool,
+    /// The creator registered in the metadata, from which `finish` registers
+    /// `__ffi_new__` and `__ffi_init__`.
+    creator: Option<TVMFFIObjectCreator>,
     /// The first failed method registration, which `finish` reports.
     error: Option<Error>,
     method_names: Vec<String>,
@@ -182,14 +186,21 @@ impl<N: ObjectCore> ObjectDef<N> {
     /// derive registers `N`'s type index, before `N::type_index()` returns.
     ///
     /// `kind` of `None` takes the nearest ancestor's kind, as a C++ subclass
-    /// inherits `_type_s_eq_hash_kind`.
+    /// inherits `_type_s_eq_hash_kind`. `creator`, as C++ `ObjectDef` takes
+    /// the default or `UnsafeInit` constructor, makes an object of `N` for
+    /// reflection to fill (`unsafe_init_creator::<N>` for an `#[rs_class]`).
     ///
     /// # Safety
     ///
     /// The caller holds `TYPE_REGISTRATION` for the builder's lifetime, and
-    /// `type_index` is `N`'s.
+    /// `type_index` is `N`'s. `creator` returns an owned handle of an object 
of
+    /// `N` that the field setters can fill.
     #[doc(hidden)]
-    pub unsafe fn registering(type_index: i32, kind: Option<SEqHashKind>) -> 
Result<Self> {
+    pub unsafe fn registering(
+        type_index: i32,
+        kind: Option<SEqHashKind>,
+        creator: Option<TVMFFIObjectCreator>,
+    ) -> Result<Self> {
         let info = TVMFFIGetTypeInfo(type_index);
         crate::ensure!(
             !info.is_null(),
@@ -205,7 +216,7 @@ impl<N: ObjectCore> ObjectDef<N> {
             };
             let metadata = TVMFFITypeMetadata {
                 doc: TVMFFIByteArray::from_str(""),
-                creator: None,
+                creator,
                 total_size: size_of::<N>() as i32,
                 structural_eq_hash_kind: kind,
             };
@@ -214,6 +225,7 @@ impl<N: ObjectCore> ObjectDef<N> {
         Ok(Self {
             type_index,
             fresh,
+            creator,
             error: None,
             method_names: Vec::new(),
             _object: PhantomData,
@@ -239,6 +251,18 @@ impl<N: ObjectCore> ObjectDef<N> {
         if !self.fresh {
             return Ok(self);
         }
+        // `__ffi_init__` leaves an `init_off` field as the creator made it, 
all-zero.
+        if self.creator.is_some() && flags.bits() & 
FieldFlags::INIT_OFF.bits() != 0 {
+            let zero = std::mem::MaybeUninit::<F>::zeroed();
+            crate::ensure!(
+                !unsafe { F::is_undefined(zero.as_ptr()) },
+                VALUE_ERROR,
+                "`init_off` field `{name}` of `{}` stays all-zero after 
`__ffi_init__`, which \
+                 is no value of `{}`",
+                N::TYPE_KEY,
+                F::type_str()
+            );
+        }
         let metadata = type_schema_metadata(&F::type_schema());
         let info = TVMFFIFieldInfo {
             name: unsafe { TVMFFIByteArray::from_str(name) },
@@ -346,8 +370,10 @@ impl<N: ObjectCore> ObjectDef<N> {
         self
     }
 
-    /// Registers `__ffi_type_final__`, which no other registered metadata
-    /// exposes, and reports the first failed method registration.
+    /// Registers, as C++ `ObjectDef` does, `__ffi_new__` from the creator and
+    /// `__ffi_init__` from the fields unless the type defines its own, then
+    /// `__ffi_type_final__`, which no other registered metadata exposes; and
+    /// reports the first failed method registration.
     #[doc(hidden)]
     pub fn finish(self) -> Result<()> {
         if let Some(error) = self.error {
@@ -356,6 +382,23 @@ impl<N: ObjectCore> ObjectDef<N> {
         if !self.fresh {
             return Ok(());
         }
+        if let Some(creator) = self.creator {
+            let new = Function::from_typed(move || -> Result<ObjectRef> {
+                let mut handle: TVMFFIObjectHandle = std::ptr::null_mut();
+                crate::check_safe_call!(unsafe { creator(&mut handle) })?;
+                // SAFETY: the creator returns an owned handle, which 
`ObjectRef` views.
+                Ok(unsafe { 
ObjectRef::from_data(ObjectArc::from_raw(handle.cast::<Object>())) })
+            });
+            register_type_attr(self.type_index, "__ffi_new__", 
Any::from(new))?;
+            // C++ `ObjectDef` looks `ffi._RegisterFFIInit` up the same way, 
in case
+            // it is not registered yet.
+            let explicit_init = self.method_names.iter().any(|name| name == 
"__ffi_init__");
+            if !explicit_init {
+                if let Ok(register_init) = 
Function::get_global("ffi._RegisterFFIInit") {
+                    register_init.call_tuple((self.type_index,))?;
+                }
+            }
+        }
         register_type_attr(
             self.type_index,
             "__ffi_type_final__",
@@ -419,7 +462,13 @@ unsafe extern "C" fn field_getter<F: AnyCompatible>(
     result: *mut TVMFFIAny,
 ) -> i32 {
     let read = catch_unwind(AssertUnwindSafe(|| {
-        let value: Any = (*field.cast::<F>()).to_any();
+        let field = field.cast::<F>();
+        // An undefined field reads as None, as a null `ObjectRef` does in C++.
+        let value = if F::is_undefined(field) {
+            Any::new()
+        } else {
+            (*field).to_any()
+        };
         std::ptr::write(result, Any::into_raw_ffi_any(value));
     }));
     match read {
@@ -441,7 +490,13 @@ unsafe extern "C" fn field_setter<F: AnyCompatible>(
     let written = catch_unwind(AssertUnwindSafe(|| {
         match F::try_cast_from_any_view(&*value) {
             Ok(converted) => {
-                *field.cast::<F>() = converted;
+                let field = field.cast::<F>();
+                // An undefined field holds no value to drop.
+                if F::is_undefined(field) {
+                    field.write(converted);
+                } else {
+                    *field = converted;
+                }
                 None
             }
             Err(()) => Some(Error::new(
diff --git a/rust/tvm-ffi/src/reflection/unsafe_init.rs 
b/rust/tvm-ffi/src/reflection/unsafe_init.rs
new file mode 100644
index 00000000..4147db0a
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/unsafe_init.rs
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Objects made with every field undefined, for reflection to set the fields,
+//! as C++ makes an object with `UnsafeInit`.
+//!
+//! A C++ object made with `UnsafeInit` holds undefined (null) references, 
which
+//! the field setters overwrite and the destructor skips. A Rust reference is
+//! never null, so a Rust object cannot hold one; instead [`unsafe_init`]
+//! allocates the object zeroed, and a field holding all-zero bytes that are no
+//! value of its type ([`AnyCompatible::is_undefined`]) is undefined. The field
+//! setter writes an undefined field without dropping it, the field getter 
reads
+//! it as None, and the object's deleter drops only the defined fields, all
+//! through raw pointers, so no undefined field is ever viewed as its type.
+
+use std::alloc::Layout;
+use std::ffi::c_void;
+use std::panic::{catch_unwind, AssertUnwindSafe};
+use std::sync::atomic::AtomicU64;
+
+use crate::object::{Object, ObjectArc, ObjectCore, ObjectRef, ObjectRefCore};
+use crate::tvm_ffi_sys::TVMFFIObjectDeleterFlagBitMask::{
+    kTVMFFIObjectDeleterFlagBitMaskStrong, kTVMFFIObjectDeleterFlagBitMaskWeak,
+};
+use crate::tvm_ffi_sys::{TVMFFIObject, TVMFFIObjectHandle, 
COMBINED_REF_COUNT_BOTH_ONE};
+use crate::type_traits::AnyCompatible;
+use crate::{Error, INTERNAL_ERROR};
+
+/// An object type whose object can start with every field undefined, for
+/// reflection to set the fields: C++ `UnsafeInit`.
+///
+/// The creator that `#[rs_class]` registers for its type makes the object with
+/// [`unsafe_init`], which a JSON graph read back, `__ffi_init__` and
+/// `__ffi_new__` then fill. `#[rs_class]` implements it for its object, so the
+/// parent's object must implement it too: [`Object`] does, and so does the
+/// object of a non-final C++ type bound by `tvm-ffi-stubgen`.
+///
+/// `__ffi_init__` leaves an `init_off` field as the creator made it, so such a
+/// field's type must take all-zero bytes as a value, as a scalar, `Option<T>`
+/// or `Any` does; the type's registration fails otherwise.
+///
+/// # Safety
+///
+/// `drop_defined_fields` drops each defined field of the object and no
+/// undefined one (as [`drop_if_defined`] does), the parent's object's 
included.
+pub unsafe trait UnsafeInit: ObjectCore {
+    /// Drops the defined fields of the object at `this`, the parent's object's
+    /// included, and leaves the undefined ones.
+    ///
+    /// # Safety
+    ///
+    /// `this` points to an object that [`unsafe_init`] made, which is not used
+    /// afterwards.
+    #[doc(hidden)]
+    unsafe fn drop_defined_fields(this: *mut Self);
+}
+
+// The header holds nothing to drop.
+unsafe impl UnsafeInit for Object {
+    unsafe fn drop_defined_fields(_this: *mut Self) {}
+}
+
+/// Drops the field at `field` unless it is undefined.
+///
+/// # Safety
+///
+/// `field` points to a field of an object, defined or undefined, which is not
+/// used afterwards.
+#[doc(hidden)]
+#[inline]
+pub unsafe fn drop_if_defined<F: AnyCompatible>(field: *mut F) {
+    unsafe {
+        if !F::is_undefined(field) {
+            std::ptr::drop_in_place(field);
+        }
+    }
+}
+
+/// An object of `T` with every field undefined, as C++
+/// `make_object<T>(UnsafeInit{})` makes one, for reflection to set the fields
+/// through their setters.
+///
+/// The [`ObjectRef`] views only the object's header, which is valid all along.
+///
+/// # Safety
+///
+/// Each field that all-zero bytes leave undefined
+/// ([`AnyCompatible::is_undefined`]) is set before the object is viewed as a
+/// `T` (by a cast of the reference) or the field is read as its type. Until
+/// then, only the reflection's field setters and getters reach the fields.
+pub unsafe fn unsafe_init<T: UnsafeInit>() -> ObjectRef {
+    let layout = Layout::new::<T>();
+    unsafe {
+        let ptr = std::alloc::alloc_zeroed(layout);
+        if ptr.is_null() {
+            std::alloc::handle_alloc_error(layout);
+        }
+        std::ptr::write(
+            ptr.cast::<TVMFFIObject>(),
+            TVMFFIObject {
+                combined_ref_count: 
AtomicU64::new(COMBINED_REF_COUNT_BOTH_ONE),
+                type_index: T::type_index(),
+                __padding: 0,
+                deleter: Some(unsafe_init_deleter::<T>),
+            },
+        );
+        // SAFETY: `ObjectRef` is the view of any object.
+        ObjectRef::from_data(ObjectArc::from_raw(ptr.cast::<Object>()))
+    }
+}
+
+/// The deleter of an object that [`unsafe_init`] made, which may go before
+/// reflection sets every field, as when a JSON graph lacks one.
+unsafe extern "C" fn unsafe_init_deleter<T: UnsafeInit>(ptr: *mut c_void, 
flags: i32) {
+    unsafe {
+        if flags & kTVMFFIObjectDeleterFlagBitMaskStrong as i32 != 0 {
+            T::drop_defined_fields(ptr.cast::<T>());
+        }
+        if flags & kTVMFFIObjectDeleterFlagBitMaskWeak as i32 != 0 {
+            std::alloc::dealloc(ptr.cast::<u8>(), Layout::new::<T>());
+        }
+    }
+}
+
+/// The creator of `T` (`TVMFFITypeMetadata::creator`) that `#[rs_class]`
+/// registers, as C++ `ObjectCreatorUnsafeInit<T>`: the owned handle of an
+/// object that [`unsafe_init`] made.
+///
+/// # Safety
+///
+/// `result` is valid for writes.
+#[doc(hidden)]
+pub unsafe extern "C" fn unsafe_init_creator<T: UnsafeInit>(
+    result: *mut TVMFFIObjectHandle,
+) -> i32 {
+    let made = catch_unwind(AssertUnwindSafe(|| unsafe {
+        let object = unsafe_init::<T>();
+        let handle = ObjectArc::into_raw(ObjectRef::into_data(object));
+        std::ptr::write(result, handle as TVMFFIObjectHandle);
+    }));
+    match made {
+        Ok(()) => 0,
+        Err(_) => {
+            let message = format!("panic while creating an object of `{}`", 
T::TYPE_KEY);
+            Error::set_raised(&Error::new(INTERNAL_ERROR, &message, ""));
+            -1
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::mem::MaybeUninit;
+
+    use crate::tvm_ffi_sys::dlpack::{DLDevice, DLDeviceType};
+    use crate::type_traits::AnyCompatible;
+    use crate::{Array, BigInt, String};
+
+    fn zero_is_undefined<F: AnyCompatible>() -> bool {
+        let zero = MaybeUninit::<F>::zeroed();
+        unsafe { F::is_undefined(zero.as_ptr()) }
+    }
+
+    fn is_undefined<F: AnyCompatible>(value: F) -> bool {
+        unsafe { F::is_undefined(&value) }
+    }
+
+    #[test]
+    fn all_zero_bytes_are_undefined_where_they_are_no_value() {
+        assert!(zero_is_undefined::<Array<i64>>());
+        assert!(zero_is_undefined::<String>());
+        assert!(zero_is_undefined::<BigInt>());
+        assert!(zero_is_undefined::<DLDevice>());
+
+        assert!(!zero_is_undefined::<i64>());
+        assert!(!zero_is_undefined::<bool>());
+        // `None`, through the niche of a reference or a device, or through a 
tag.
+        assert!(!zero_is_undefined::<Option<Array<i64>>>());
+        assert!(!zero_is_undefined::<Option<DLDevice>>());
+        assert!(!zero_is_undefined::<Option<String>>());
+    }
+
+    #[test]
+    fn values_are_defined() {
+        let cpu = DLDevice {
+            device_type: DLDeviceType::kDLCPU,
+            device_id: 0,
+        };
+        assert!(!is_undefined(Array::<i64>::new(vec![])));
+        assert!(!is_undefined(String::from("")));
+        assert!(!is_undefined(BigInt::from(0_i64)));
+        assert!(!is_undefined(cpu));
+        assert!(!is_undefined(Some(cpu)));
+        assert!(!is_undefined(None::<DLDevice>));
+        assert!(!is_undefined(None::<Array<i64>>));
+    }
+}
diff --git a/rust/tvm-ffi/src/string.rs b/rust/tvm-ffi/src/string.rs
index 94739a4c..d8b29d55 100644
--- a/rust/tvm-ffi/src/string.rs
+++ b/rust/tvm-ffi/src/string.rs
@@ -442,6 +442,12 @@ unsafe impl AnyCompatible for Bytes {
         "ffi.Bytes".to_string()
     }
 
+    // Bytes are small bytes or a bytes object, never None.
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        (*std::ptr::addr_of!((*field).data.type_index)) == 
TypeIndex::kTVMFFINone as i32
+    }
+
     unsafe fn copy_to_any_view(this: &Self, data: &mut TVMFFIAny) {
         *data = this.data;
     }
@@ -490,6 +496,12 @@ unsafe impl AnyCompatible for String {
         "ffi.String".to_string()
     }
 
+    // A string is a small string or a string object, never None.
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        (*std::ptr::addr_of!((*field).data.type_index)) == 
TypeIndex::kTVMFFINone as i32
+    }
+
     unsafe fn copy_to_any_view(this: &Self, data: &mut TVMFFIAny) {
         *data = this.data;
     }
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index 5f15a957..0e3abeaf 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -43,6 +43,30 @@ pub unsafe trait AnyCompatible: Sized {
         unreachable!("MATCH_ANY_EXACT is false")
     }
 
+    /// Whether `field`, which holds a value of this type or all-zero bytes,
+    /// holds all-zero bytes that are no value of this type, as a null object
+    /// reference, a string of type index None or a zero device type: a field
+    /// that a creator left undefined for reflection to set, as C++ 
`UnsafeInit`
+    /// leaves a reference undefined (null). Unlike a C++ undefined reference,
+    /// it is no value; a nullable reference is `Option<T>`, whose `None` is a
+    /// defined value.
+    ///
+    /// The field setter writes an undefined field without dropping it, the
+    /// field getter reads it as None, and the object's deleter skips it. The 
default,
+    /// false, fits a type whose all-zero bytes are a value whose drop does
+    /// nothing, as a scalar's, `Any`'s or `Optional`'s.
+    ///
+    /// # Safety
+    ///
+    /// `field` points to a value of this type or to all-zero bytes. An
+    /// implementation reads only bytes that each value initializes and, when
+    /// `Option<Self>` is niche-optimized, that its `None` initializes too.
+    #[inline]
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        let _ = field;
+        false
+    }
+
     /// the value to copy to TVMFFIAny
     unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny);
     /// consume the value to move to Any
@@ -390,6 +414,18 @@ unsafe impl<T: AnyCompatible> AnyCompatible for Option<T> {
         type_schema("Optional", &[<T as AnyCompatible>::type_schema()])
     }
 
+    unsafe fn is_undefined(field: *const Self) -> bool {
+        // With a tag, all-zero bytes are `None`. A niche-optimized `Option<T>`
+        // keeps `T`'s bytes, where all-zero bytes are `None` if `None` looks
+        // undefined to `T` (a reference's null pointer, a device's zero type),
+        // and undefined if `None` sits elsewhere in the niche.
+        if std::mem::size_of::<Self>() != std::mem::size_of::<T>() {
+            return false;
+        }
+        let none = std::mem::MaybeUninit::new(None::<T>);
+        T::is_undefined(field.cast::<T>()) && 
!T::is_undefined(none.as_ptr().cast::<T>())
+    }
+
     unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
         if let Some(ref value) = src {
             T::copy_to_any_view(value, data);
diff --git a/rust/tvm-ffi/tests/test_rs_class.rs 
b/rust/tvm-ffi/tests/test_rs_class.rs
index d14a6597..cd38a413 100644
--- a/rust/tvm-ffi/tests/test_rs_class.rs
+++ b/rust/tvm-ffi/tests/test_rs_class.rs
@@ -17,8 +17,8 @@
  * under the License.
  */
 use tvm_ffi::derive::Object;
-use tvm_ffi::object::ObjectRef;
-use tvm_ffi::reflection::{FieldFlags, ObjectDef};
+use tvm_ffi::object::{ObjectArc, ObjectRef, ObjectRefCore};
+use tvm_ffi::reflection::{drop_if_defined, FieldFlags, ObjectDef};
 use tvm_ffi::*;
 use tvm_ffi_sys::{TVMFFIFieldInfo, TVMFFIGetTypeInfo, TVMFFISEqHashKind, 
TVMFFITypeInfo};
 
@@ -90,8 +90,8 @@ pub struct RsScalarsObj {
 def_object_ref!(pub RsScalars, ObjectRef, RsScalarsObj);
 
 // `testing.TestCxxClassBase` from libtvm_ffi_testing, bound as tvm-ffi-stubgen
-// binds a complete C++ type that can have children: with a public `new` and
-// `ShallowCopy`, so that a Rust type can derive from it.
+// binds a complete C++ type that can have children: with a public `new`,
+// `ShallowCopy` and `UnsafeInit`, so that a Rust type can derive from it.
 
 #[repr(C)]
 #[derive(Object)]
@@ -119,6 +119,16 @@ impl ShallowCopy for TestCxxClassBaseObj {
     }
 }
 
+unsafe impl UnsafeInit for TestCxxClassBaseObj {
+    unsafe fn drop_defined_fields(this: *mut Self) {
+        unsafe {
+            
UnsafeInit::drop_defined_fields(std::ptr::addr_of_mut!((*this).base));
+            drop_if_defined(std::ptr::addr_of_mut!((*this).v_i64));
+            drop_if_defined(std::ptr::addr_of_mut!((*this).v_i32));
+        }
+    }
+}
+
 // `def_object_ref!` takes any object type, not only an `rs_class` one.
 def_object_ref!(pub TestCxxClassBase, ObjectRef, TestCxxClassBaseObj);
 
@@ -131,31 +141,46 @@ pub struct RsCxxChildObj {
 
 def_object_ref!(pub RsCxxChild, TestCxxClassBase, RsCxxChildObj);
 
-/// A root type with `placeholder`, whose fields have the field types tvm-ffi
-/// gives placeholders.
-#[rs_class("testing.rust.RsPlaceholder", s_eq_hash_kind = "TreeNode", 
placeholder)]
-pub struct RsPlaceholderObj {
+/// A type that defines its own `__ffi_init__`, over which C++ generates none.
+#[rs_class("testing.rust.RsOwnInit", final, reflection = 
RsOwnInitObj::reflect_methods)]
+pub struct RsOwnInitObj {
     base: Object,
+    pub v: i64,
+}
+
+def_object_ref!(pub RsOwnInit, ObjectRef, RsOwnInitObj);
+
+impl RsOwnInitObj {
+    fn reflect_methods(def: &mut ObjectDef<Self>) {
+        def.def_static(
+            "__ffi_init__",
+            |v: i64| -> Result<RsOwnInit> { 
Ok(RsOwnInitObj::new(Object::new(), 2 * v).into()) },
+            "Twice `v`.",
+        );
+    }
+}
+
+/// `init_off` fields, which `__ffi_init__` leaves all-zero: a value of these 
types.
+#[rs_class("testing.rust.RsInitOff", final)]
+pub struct RsInitOffObj {
+    base: Object,
+    pub v: i64,
+    #[reflect(init_off)]
     pub count: i64,
-    pub ratio: f64,
-    pub flag: bool,
-    pub label: tvm_ffi::String,
-    pub tags: Array<tvm_ffi::String>,
-    pub table: Map<tvm_ffi::String, i64>,
+    #[reflect(init_off)]
     pub point: Option<RsPoint>,
 }
 
-def_object_ref!(pub RsPlaceholder, ObjectRef, RsPlaceholderObj);
+def_object_ref!(pub RsInitOff, ObjectRef, RsInitOffObj);
 
-/// A subtype with `placeholder`, whose placeholder starts from its parent's.
-#[rs_class("testing.rust.RsPlaceholderChild", final, placeholder)]
-pub struct RsPlaceholderChildObj {
-    base: RsPlaceholderObj,
-    pub z: i32,
+/// An `init_off` reference, which all-zero bytes leave undefined: its 
registration fails.
+#[rs_class("testing.rust.RsInitOffReference", final)]
+pub struct RsInitOffReferenceObj {
+    base: Object,
+    #[reflect(init_off)]
+    pub items: Array<i64>,
 }
 
-def_object_ref!(pub RsPlaceholderChild, RsPlaceholder, RsPlaceholderChildObj);
-
 /// A crate that reaches tvm-ffi only through a re-export names it with 
`crate`.
 mod reexported {
     // The bare `tvm_ffi` here is not the crate, as in a crate without the 
dependency.
@@ -243,8 +268,8 @@ fn register_types() {
         RsMarkerObj::type_index();
         RsScalarsObj::type_index();
         RsCxxChildObj::type_index();
-        RsPlaceholderObj::type_index();
-        RsPlaceholderChildObj::type_index();
+        RsOwnInitObj::type_index();
+        RsInitOffObj::type_index();
         reexported::ReexportedObj::type_index();
     });
 }
@@ -388,7 +413,8 @@ fn test_shallow_copy_and_finality_are_type_attributes() {
     assert!(!copy.same_as(&original));
     assert_eq!((copy.x, copy.label.as_str()), (3, "m"));
 
-    // C++ cannot construct the type from its fields.
+    // C++ generates `__ffi_init__` from the fields as a type attribute, not a 
method.
+    assert!(Function::try_from(attr(RsPointObj::type_index(), 
"__ffi_init__")).is_ok());
     assert!(Function::from_type_key_method("testing.rust.RsPoint", 
"__ffi_init__").is_err());
 }
 
@@ -423,19 +449,112 @@ fn test_crate_path_names_a_reexported_tvm_ffi() {
     assert_eq!(field_names::<reexported::ReexportedObj>(), ["v"]);
 }
 
+fn type_attr_function(type_index: i32, name: &str) -> Function {
+    Function::try_from(get_type_attr(type_index, name).unwrap()).unwrap()
+}
+
+#[test]
+fn test_new_makes_the_object_with_its_fields_undefined() {
+    register_types();
+    let new = type_attr_function(RsPoint3Obj::type_index(), "__ffi_new__");
+    let object = ObjectRef::try_from(new.call_packed(&[]).unwrap()).unwrap();
+    assert_eq!(
+        Any::from(object.clone()).type_index(),
+        RsPoint3Obj::type_index()
+    );
+    // Reflection reads an undefined reference or string as None, as C++ reads 
a
+    // null `ObjectRef`, and a scalar of a new object as zero. The object then
+    // goes with its fields undefined.
+    let header: &Object = ObjectRefCore::data(&object);
+    let field = |name| {
+        FieldGetter::new(RsPoint3Obj::type_index(), name)
+            .unwrap()
+            .get_any(header)
+            .unwrap()
+    };
+    let none = TypeIndex::kTVMFFINone as i32;
+    assert_eq!(field("tags").type_index(), none);
+    assert_eq!(field("label").type_index(), none);
+    assert_eq!(i64::try_from(field("z")).unwrap(), 0);
+}
+
 #[test]
-fn test_placeholder_makes_the_object_from_its_parent_and_fields() {
+fn test_init_constructs_the_type_from_its_fields() {
     register_types();
-    let child = RsPlaceholderChild::from(RsPlaceholderChildObj::placeholder());
+    // The fields of the parent come first, as for a C++ type.
+    let init = type_attr_function(RsPoint3Obj::type_index(), "__ffi_init__");
+    let label = || tvm_ffi::String::from("l");
+    let tags = Array::<tvm_ffi::String>::new(vec!["a".into()]);
+    let p3 = init
+        .call_tuple((1_i64, label(), 2_i64, tags.clone(), 3_i64))
+        .unwrap();
+    let p3 = RsPoint3::try_from(p3).unwrap();
+    assert_eq!((p3.x, p3.label.as_str(), p3.z, p3.tag), (1, "l", 2, 3));
+    assert!(p3.tags.same_as(&tags));
+    drop(p3);
+
+    // An object that misses a field goes with the fields set so far, and
+    // drops the reference it holds.
+    let tags_count = || ObjectArc::strong_count(ObjectRefCore::data(&tags));
+    assert_eq!(tags_count(), 1);
+    let error = init
+        .call_tuple((1_i64, label(), 2_i64, tags.clone()))
+        .err()
+        .unwrap();
+    assert!(
+        error.message().contains("missing required argument: 'tag'"),
+        "{}",
+        error.message()
+    );
+    assert_eq!(tags_count(), 1);
+
+    // A type's own `__ffi_init__` is a method, which C++ generates none over.
+    assert!(get_type_attr(RsOwnInitObj::type_index(), 
"__ffi_init__").is_none());
+    let own_init =
+        Function::from_type_key_method("testing.rust.RsOwnInit", 
"__ffi_init__").unwrap();
+    let own = 
RsOwnInit::try_from(own_init.call_tuple((3_i64,)).unwrap()).unwrap();
+    assert_eq!(own.v, 6);
+}
+
+#[test]
+fn test_json_graph_reads_back_the_fields() {
+    register_types();
+    let round_trip = |value: Any| {
+        let json = call(
+            "ffi.ToJSONGraphString",
+            &[AnyView::from(&value), AnyView::from(&Any::new())],
+        );
+        call("ffi.FromJSONGraphString", &[AnyView::from(&json)])
+    };
+    let p3 = point3(1, 2);
+    let back = RsPoint3::try_from(round_trip(Any::from(p3.clone()))).unwrap();
+    assert!(!back.same_as(&p3));
+    assert!(structural_equal(&back, &p3));
     assert_eq!(
-        (child.count, child.ratio, child.flag, child.z),
-        (0, 0.0, false, 0)
+        (back.label.as_str(), back.tags.len(), back.tag),
+        ("p3", 2, 7)
+    );
+
+    // The C++ fields of a subtype of a C++ type, which the C++ setters set.
+    let child = cxx_child(7, 8, "c");
+    let back = 
RsCxxChild::try_from(round_trip(Any::from(child.clone()))).unwrap();
+    assert!(!back.same_as(&child));
+    assert_eq!((back.v_i64, back.v_i32, back.name.as_str()), (7, 8, "c"));
+}
+
+#[test]
+fn test_init_off_fields_take_all_zero_bytes_as_a_value() {
+    register_types();
+    let init = type_attr_function(RsInitOffObj::type_index(), "__ffi_init__");
+    let value = 
RsInitOff::try_from(init.call_tuple((5_i64,)).unwrap()).unwrap();
+    assert_eq!((value.v, value.count), (5, 0));
+    assert!(value.point.is_none());
+
+    let registered = 
std::panic::catch_unwind(RsInitOffReferenceObj::type_index);
+    let message = registered.err().unwrap();
+    let message = message.downcast_ref::<std::string::String>().unwrap();
+    assert!(
+        message.contains("`init_off` field `items` of 
`testing.rust.RsInitOffReference`"),
+        "{message}"
     );
-    assert_eq!(child.label.as_str(), "");
-    assert!(child.tags.is_empty() && child.table.is_empty() && 
child.point.is_none());
-    // A valid object of its type, which the reflection sees as any other.
-    let other = RsPlaceholderChild::from(RsPlaceholderChildObj::placeholder());
-    assert!(!other.same_as(&child));
-    assert!(structural_equal(&child, &other));
-    assert!(structural_equal(&deep_copy(&child), &child));
 }
diff --git a/tests/python/test_stubgen_rust.py 
b/tests/python/test_stubgen_rust.py
index 0ed51f2a..31b54513 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -779,6 +779,16 @@ impl ShallowCopy for ExprObj {
             ty: Clone::clone(&self.ty),
         }
     }
+}
+
+unsafe impl UnsafeInit for ExprObj {
+    unsafe fn drop_defined_fields(this: *mut Self) {
+        unsafe {
+            UnsafeInit::drop_defined_fields(addr_of_mut!((*this).base));
+            drop_if_defined(addr_of_mut!((*this).span));
+            drop_if_defined(addr_of_mut!((*this).ty));
+        }
+    }
 }"""
 
 ADD_EXPECTED = """\
@@ -849,9 +859,12 @@ def test_render_complete_expr_golden() -> None:
     assert text == EXPR_EXPECTED
     assert _uses(imports) == {
         "std::ops::Deref",
+        "std::ptr::addr_of_mut",
         "tvm_ffi::Object",
         "tvm_ffi::ObjectArc",
         "tvm_ffi::ShallowCopy",
+        "tvm_ffi::UnsafeInit",
+        "tvm_ffi::reflection::drop_if_defined",
     }
 
 
@@ -981,6 +994,46 @@ def test_complete_optional_field_mirrors() -> None:
     assert "tvm_ffi::Optional" in _uses(imports)
 
 
+def test_unsafe_init_drops_each_field_by_its_all_zero_bytes() -> None:
+    """`UnsafeInit` skips an undefined field; a `TVMFFIAny` cell drops as is, 
an enum not at all."""
+    info = _info(
+        "demo.Mixed",
+        (
+            _field("count", TypeSchema("Optional", (TypeSchema("int"),)), 24, 
16),
+            _field("value", "Any", 40, 16),
+            _field("items", TypeSchema("Optional", (TypeSchema("Array"),)), 
56, 8),
+            _field("name", "str", 64, 16),
+            _field("kind", "int", 80, 8),
+        ),
+        total_size=88,
+    )
+    imports = RUST.new_imports()
+    RUST.add_directive(imports, "enum", "demo.Mixed.kind -> Kind(i64) { A=0 
}", 1)
+    text, imports = _render(info, imports)
+    assert (
+        "unsafe impl UnsafeInit for MixedObj {\n"
+        "    unsafe fn drop_defined_fields(this: *mut Self) {\n"
+        "        unsafe {\n"
+        "            
UnsafeInit::drop_defined_fields(addr_of_mut!((*this).base));\n"
+        "            drop_in_place(addr_of_mut!((*this).count));\n"
+        "            drop_in_place(addr_of_mut!((*this).value));\n"
+        "            drop_if_defined(addr_of_mut!((*this).items));\n"
+        "            drop_if_defined(addr_of_mut!((*this).name));\n"
+        "        }\n"
+        "    }\n"
+        "}"
+    ) in text, text
+    assert {"std::ptr::drop_in_place", "tvm_ffi::reflection::drop_if_defined"} 
<= _uses(imports)
+
+
+def test_final_type_has_no_unsafe_init() -> None:
+    """No Rust type derives from a final type, which needs no `UnsafeInit`."""
+    info = _info("demo.Leaf", (_field("a", "int", 24, 8),), total_size=32, 
is_final=True)
+    text, imports = _render(info)
+    assert "UnsafeInit" not in text
+    assert "tvm_ffi::UnsafeInit" not in _uses(imports)
+
+
 def test_complete_list_field_mirrors() -> None:
     """A `List<T>` field is an object handle and mirrors like `Array<T>`."""
     info = _info(

Reply via email to