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 c3424d0b [Rust] IntoArg, IntoPackedArg(s), Map key lookup by IntoArg 
(#963)
c3424d0b is described below

commit c3424d0b318a84468c5556ff72b4466067865a82
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Nov 8 09:28:40 2026 -0500

    [Rust] IntoArg, IntoPackedArg(s), Map key lookup by IntoArg (#963)
    
    - `IntoArg<T>` replaces `IntoObjectRef<T>`: a `T`, a `&T` or a subtype
    where a `T` is expected, for every container element rather than object
    references only, plus `&str` -> `String` and `&[u8]` -> `Bytes`.
    `into_arg` returns `T` or `&T`; `into_owned` moves or clones as
    `Cow::into_owned`. The stub generator emits `impl IntoArg<T>` /
    `.into_owned()`.
    - The packed-call argument traits (`IntoArgHolder`,
    `IntoArgHolderTuple`, `ArgIntoRef`, `PackedArg`, `TupleAsPackedArgs`)
    collapse into `function::PackedCallArg` and `function::PackedCallArgs`.
    `call_tuple` and `into_typed_fn!` share one path, so `call_tuple` also
    takes `&str` and `&[u8]`; `call_tuple_with_len` is removed.
    - `Map::get` and `Map::contains_key` take `impl IntoArg<K>`: `get(&k)`,
    `get(k)`, `get(&sub)` and `get("key")` all work.
    - `type_traits.rs` orders its traits by dependency, each followed by its
    impls, and the trait docs are tightened.
---
 docs/guides/rust_lang_guide.rst               |   2 +-
 python/tvm_ffi/stub/rust_generator/codegen.py |  14 +-
 python/tvm_ffi/stub/rust_generator/consts.py  |   4 +-
 rust/tvm-ffi-macros/src/object_macros.rs      |  16 -
 rust/tvm-ffi-macros/src/rs_object.rs          |   2 +-
 rust/tvm-ffi/src/any.rs                       |   2 +
 rust/tvm-ffi/src/collections/array.rs         |  25 +-
 rust/tvm-ffi/src/collections/list.rs          |   2 +
 rust/tvm-ffi/src/collections/map.rs           |  43 +-
 rust/tvm-ffi/src/collections/tensor.rs        |  38 +-
 rust/tvm-ffi/src/extra/module.rs              |  18 +-
 rust/tvm-ffi/src/extra/unchanged.rs           |   3 +
 rust/tvm-ffi/src/function.rs                  |  66 +--
 rust/tvm-ffi/src/function_internal.rs         | 297 ++--------
 rust/tvm-ffi/src/lib.rs                       |   6 +-
 rust/tvm-ffi/src/macros.rs                    | 150 ++---
 rust/tvm-ffi/src/object.rs                    |  72 ---
 rust/tvm-ffi/src/optional.rs                  |   2 +
 rust/tvm-ffi/src/type_traits.rs               | 812 ++++++++++++++------------
 rust/tvm-ffi/tests/test_big_int.rs            |   2 +-
 rust/tvm-ffi/tests/test_function.rs           |   2 +-
 rust/tvm-ffi/tests/test_function_metadata.rs  |   6 +-
 rust/tvm-ffi/tests/test_map.rs                |  64 +-
 rust/tvm-ffi/tests/test_match_any.rs          |   2 +-
 rust/tvm-ffi/tests/test_module.rs             |   4 +-
 rust/tvm-ffi/tests/test_rs_object.rs          |  22 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs  |  16 +-
 tests/python/test_stubgen_rust.py             |  72 +--
 28 files changed, 765 insertions(+), 999 deletions(-)

diff --git a/docs/guides/rust_lang_guide.rst b/docs/guides/rust_lang_guide.rst
index 4f476985..3fa3a679 100644
--- a/docs/guides/rust_lang_guide.rst
+++ b/docs/guides/rust_lang_guide.rst
@@ -417,7 +417,7 @@ names the reference to the parent's object:
 ``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 as ``impl Into<T>``. A constructor on the reference takes 
an
-object reference as ``impl IntoObjectRef<T>``, as the ones ``tvm-ffi-stubgen``
+object reference as ``impl IntoArg<T>``, as the ones ``tvm-ffi-stubgen``
 generates do. The reference dereferences to the object
 (``#[object_ref(skip_deref)]`` opts out), and its private ``from_object`` 
wraps a new
 object for the type's constructors. The reference also converts into 
``parent``,
diff --git a/python/tvm_ffi/stub/rust_generator/codegen.py 
b/python/tvm_ffi/stub/rust_generator/codegen.py
index b8eb3f46..a7f9acd9 100644
--- a/python/tvm_ffi/stub/rust_generator/codegen.py
+++ b/python/tvm_ffi/stub/rust_generator/codegen.py
@@ -25,7 +25,7 @@ the verdict of :mod:`tvm_ffi.stub.layout`:
   width, pinned by a ``const`` size/alignment assertion. ``<Leaf>Obj::new``
   and the wrapper's ``new`` take every field root to leaf: the former each as
   its own type, the latter a scalar as its own type, an object reference as
-  ``impl IntoObjectRef`` and anything else as ``impl Into``. ``custom-new``
+  ``impl IntoArg`` and anything else as ``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_object``): its
@@ -140,13 +140,13 @@ def _is_object_ref(schema: TypeSchema) -> bool:
 def _param_type(param: _Param, imports: RustImports) -> str:
     """Type a parameter of the wrapper's allocator.
 
-    A scalar as itself, an object reference as ``impl IntoObjectRef`` 
(recording its ``use``,
-    which also brings ``into_owned_object_ref`` into scope), anything else as 
``impl Into``.
+    A scalar as itself, an object reference as ``impl IntoArg`` (recording its 
``use``,
+    which also brings ``into_owned`` into scope), anything else as ``impl 
Into``.
     """
     if param.rust_type in C_RUST.RUST_EXACT_PARAMS:
         return param.rust_type
     if param.is_object:
-        return f"impl 
{imports.record('tvm_ffi::IntoObjectRef')}<{param.rust_type}>"
+        return f"impl {imports.record('tvm_ffi::IntoArg')}<{param.rust_type}>"
     return f"impl Into<{param.rust_type}>"
 
 
@@ -155,7 +155,7 @@ def _forward(param: _Param) -> str:
     ident = rust_ident(param.name)
     if param.rust_type in C_RUST.RUST_EXACT_PARAMS:
         return ident
-    return f"{ident}.into_owned_object_ref()" if param.is_object else 
f"{ident}.into()"
+    return f"{ident}.into_owned()" if param.is_object else f"{ident}.into()"
 
 
 def _check_width(target: str, field: NamedTypeSchema, rust_type: str, width: 
int) -> None:
@@ -694,7 +694,7 @@ class _ObjectRenderer:
     ) -> list[str]:
         """Render ``<head>(<params>) -> Self { let <call>; Self { <init> } }`` 
in an ``impl`` block.
 
-        ``generic`` takes a parameter that is not a scalar as ``impl 
IntoObjectRef`` or
+        ``generic`` takes a parameter that is not a scalar as ``impl IntoArg`` 
or
         ``impl Into`` (:func:`_param_type`).
         """
         plist = [
@@ -716,7 +716,7 @@ class _ObjectRenderer:
         ``<Leaf>Obj::new`` takes every field as its own type, so the parent's, 
generated
         or hand-written, takes what it hands on. The wrapper's ``new`` takes a 
scalar
         field as its own type, as ``#[rs_object]``'s ``new`` does, an object 
reference as
-        ``impl IntoObjectRef`` and any other as ``impl Into`` 
(:func:`_param_type`), and
+        ``impl IntoArg`` and any other as ``impl Into`` (:func:`_param_type`), 
and
         converts it on the way to ``<Leaf>Obj::new``.
         ``custom-new`` names the wrapper's allocator ``from_fields`` instead.
         ``<Leaf>Obj::new`` of a type that is not final is public: a subtype 
defined in Rust
diff --git a/python/tvm_ffi/stub/rust_generator/consts.py 
b/python/tvm_ffi/stub/rust_generator/consts.py
index 557e4ae2..4c341890 100644
--- a/python/tvm_ffi/stub/rust_generator/consts.py
+++ b/python/tvm_ffi/stub/rust_generator/consts.py
@@ -112,13 +112,13 @@ RUST_SCALAR_WIDTHS = {
 
 #: Parameters of the wrapper's allocator taken as their own type, as 
``#[rs_object]``'s
 #: ``new`` takes them: a literal infers only from its exact type. An object 
reference is
-#: ``impl IntoObjectRef<T>`` and any other parameter ``impl Into<T>``; 
``<Leaf>Obj::new``
+#: ``impl IntoArg<T>`` and any other parameter ``impl Into<T>``; 
``<Leaf>Obj::new``
 #: takes every one as its own type.
 RUST_EXACT_PARAMS = frozenset(
     {"bool", "i8", "i16", "i32", "i64", "isize", "u8", "u16", "u32", "u64", 
"usize", "f32", "f64"}
 )
 
-#: Builtin origins whose Rust type is an object reference, taken as ``impl 
IntoObjectRef<T>``.
+#: Builtin origins whose Rust type is an object reference, taken as ``impl 
IntoArg<T>``.
 #: A type key outside ``ffi.*`` is one too: a generated or ``ty-map``'d 
binding. ``Shape``,
 #: like ``Array`` / ``List`` / ``Map``, is a container taken as ``impl 
Into<T>``, which also
 #: accepts what it is built from (``From<impl AsRef<[i64]>>``).
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs 
b/rust/tvm-ffi-macros/src/object_macros.rs
index 58eef552..2ba50aca 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -725,22 +725,6 @@ pub(crate) fn expand_object_ref(
             }
         }
 
-        impl #tvm_ffi_crate::object::IntoObjectRef<#struct_name> for 
#struct_name {
-            type Output = Self;
-            #[inline(always)]
-            fn into_object_ref(self) -> Self {
-                self
-            }
-        }
-
-        impl<'a> #tvm_ffi_crate::object::IntoObjectRef<#struct_name> for &'a 
#struct_name {
-            type Output = &'a #struct_name;
-            #[inline(always)]
-            fn into_object_ref(self) -> &'a #struct_name {
-                self
-            }
-        }
-
         impl ::std::convert::From<&#struct_name> for #struct_name {
             #[inline]
             fn from(value: &#struct_name) -> Self {
diff --git a/rust/tvm-ffi-macros/src/rs_object.rs 
b/rust/tvm-ffi-macros/src/rs_object.rs
index da53a2e3..26f32f19 100644
--- a/rust/tvm-ffi-macros/src/rs_object.rs
+++ b/rust/tvm-ffi-macros/src/rs_object.rs
@@ -160,7 +160,7 @@ fn expand(args: &RsObjectArgs, item: &DeriveInput) -> 
syn::Result<TokenStream2>
     let base_ty = &base.ty;
     // `new` takes a scalar as its own type, so that a literal infers as it, 
and any
     // other field as `impl Into`. Tokens alone do not tell an object 
reference from a
-    // value, so this object-level allocator does not take `impl 
IntoObjectRef` as a
+    // value, so this object-level allocator does not take `impl IntoArg` as a
     // reference's constructor does.
     let params = fields.iter().map(|field| {
         let (name, ty) = (&field.ident, &field.ty);
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index a5f05243..54f6498f 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -605,8 +605,10 @@ crate::impl_any_compatible_conversions!(
 );
 // Only out of `Any`: a raw pointer and the root reference are not call 
arguments.
 crate::impl_try_from_any!(*mut core::ffi::c_void, crate::object::ObjectRef);
+crate::impl_into_arg!(Any, *mut core::ffi::c_void, crate::object::ObjectRef);
 
 crate::impl_try_from_any_for_parametric!(Option<T>);
+crate::impl_into_arg!(impl[T: AnyCompatible + Clone] Option<T>);
 
 /// The conversion of a typed function argument, whose error names the
 /// argument's index.
diff --git a/rust/tvm-ffi/src/collections/array.rs 
b/rust/tvm-ffi/src/collections/array.rs
index 8d28473b..891e7af6 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -25,8 +25,9 @@ use crate::any::TryFromTemp;
 use crate::collections::list::seq_cells;
 use crate::derive::Object;
 use crate::object::ObjectCoreWithExtraItems;
-use crate::object::{IntoObjectRef, Object, ObjectArc};
+use crate::object::{Object, ObjectArc};
 use crate::type_traits::{ContainerElement, RefFromAnyUnchecked, TypeSubsumes};
+use crate::IntoArg;
 use crate::{Any, AnyCompatible, AnyView, ObjectRefCore};
 use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 use tvm_ffi_sys::{TVMFFIAny, TVMFFIObject};
@@ -161,22 +162,28 @@ impl<T: ContainerElement + Clone> From<&Array<T>> for 
Array<T> {
     }
 }
 
-impl<T, U: TypeSubsumes<T>> IntoObjectRef<Array<U>> for Array<T> {
+impl<T, U: TypeSubsumes<T>> IntoArg<Array<U>> for Array<T> {
     type Output = Array<U>;
     #[inline(always)]
-    fn into_object_ref(self) -> Array<U> {
+    fn into_arg(self) -> Array<U> {
+        self.upcast()
+    }
+    #[inline(always)]
+    fn into_owned(self) -> Array<U> {
         self.upcast()
     }
 }
 
-impl<'a, T, U: TypeSubsumes<T> + ContainerElement + Clone + 'a> 
IntoObjectRef<Array<U>>
-    for &'a Array<T>
-{
+impl<'a, T, U: TypeSubsumes<T> + ContainerElement + Clone + 'a> 
IntoArg<Array<U>> for &'a Array<T> {
     type Output = &'a Array<U>;
     #[inline(always)]
-    fn into_object_ref(self) -> &'a Array<U> {
+    fn into_arg(self) -> &'a Array<U> {
         self.upcast_ref()
     }
+    #[inline(always)]
+    fn into_owned(self) -> Array<U> {
+        self.upcast_ref().clone()
+    }
 }
 
 /// An array upcasts to the root [`ObjectRef`](crate::object::ObjectRef).
@@ -220,7 +227,7 @@ impl<T> Array<T> {
     /// let objects: Array<ObjectRef> = shapes.clone().upcast();
     /// assert!(objects[0].same_as(&shapes[0]));
     /// ```
-    #[inline]
+    #[inline(always)]
     pub fn upcast<U: TypeSubsumes<T>>(self) -> Array<U> {
         Array {
             data: self.data,
@@ -229,7 +236,7 @@ impl<T> Array<T> {
     }
 
     /// Borrow this array as an `Array<U>`, as [`upcast`](Self::upcast) does.
-    #[inline]
+    #[inline(always)]
     pub fn upcast_ref<U: TypeSubsumes<T>>(&self) -> &Array<U> {
         // SAFETY: `Array<T>` and `Array<U>` are both `repr(C)` over one
         // `ObjectArc<ArrayObj>`, and every `T` cell is a valid `U` cell.
diff --git a/rust/tvm-ffi/src/collections/list.rs 
b/rust/tvm-ffi/src/collections/list.rs
index 1951145f..6743c248 100644
--- a/rust/tvm-ffi/src/collections/list.rs
+++ b/rust/tvm-ffi/src/collections/list.rs
@@ -802,6 +802,8 @@ where
     }
 }
 
+crate::impl_into_arg!(impl[T: ContainerElement] List<T>);
+
 impl<'b, T: ContainerElement> TryFrom<&'b Any> for List<T> {
     type Error = Error;
 
diff --git a/rust/tvm-ffi/src/collections/map.rs 
b/rust/tvm-ffi/src/collections/map.rs
index 511cf96d..04232e1c 100644
--- a/rust/tvm-ffi/src/collections/map.rs
+++ b/rust/tvm-ffi/src/collections/map.rs
@@ -29,6 +29,7 @@
 // hashing (`AnyHash`/`AnyEqual`) and dense/small probing, just as the Python
 // bindings also delegate.
 
+use std::borrow::Borrow;
 use std::fmt::Debug;
 use std::marker::PhantomData;
 use std::ops::Deref;
@@ -37,9 +38,9 @@ use crate::any::TryFromTemp;
 use crate::derive::Object;
 use crate::extra::cold_init_lazy_lock::ColdInitLazyLock;
 use crate::function::Function;
-use crate::object::IntoObjectRef;
 use crate::object::{Object, ObjectArc};
 use crate::type_traits::{ContainerElement, TypeSubsumes};
+use crate::IntoArg;
 use crate::{Any, AnyCompatible, AnyView, Error, ObjectRefCore, Result};
 use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 use tvm_ffi_sys::{TVMFFIAny, TVMFFIObject};
@@ -121,8 +122,8 @@ pub struct MapObj {
 ///     .into_iter()
 ///     .collect();
 /// assert_eq!(map.len(), 2);
-/// assert_eq!(map.get(&String::from("b"))?, Some(2));
-/// assert_eq!(map.get(&String::from("c"))?, None);
+/// assert_eq!(map.get("b")?, Some(2));
+/// assert_eq!(map.get("c")?, None);
 /// # Ok::<(), tvm_ffi::Error>(())
 /// ```
 #[repr(C)]
@@ -285,9 +286,10 @@ where
     /// is not hashable.
     ///
     /// The result for a `key` of another type than the map's keys is
-    /// unspecified, as for [`Map::get`].
-    pub fn contains_key(&self, key: &K) -> Result<bool> {
-        let present = self.try_contains_key(key)?;
+    /// unspecified, as for [`Map::get`]. Code generic over `K` that passes a
+    /// `&K` adds the bound `for<'a> &'a K: IntoArg<K>`.
+    pub fn contains_key(&self, key: impl IntoArg<K>) -> Result<bool> {
+        let present = self.try_contains_key(key.into_arg().borrow())?;
         if !present {
             self.debug_assert_key_type();
         }
@@ -304,9 +306,14 @@ where
     /// The result for a `key` of another type than the map's keys is
     /// unspecified: it currently reads as absent, since a lookup hashes the 
key
     /// instead of reading a stored one. Debug builds assert against it.
-    pub fn get(&self, key: &K) -> Result<Option<V>> {
+    ///
+    /// A `&str` key builds a temporary `String` for the runtime lookup. Code
+    /// generic over `K` that passes a `&K` adds the bound
+    /// `for<'a> &'a K: IntoArg<K>`.
+    pub fn get(&self, key: impl IntoArg<K>) -> Result<Option<V>> {
+        let key = key.into_arg();
         let result = crate::cached_global_func!("ffi.MapGetItemOrMissing")
-            .call_packed(&[AnyView::from(self), element_view(key)])?;
+            .call_packed(&[AnyView::from(self), element_view(key.borrow())])?;
         let raw = result.as_raw_ffi_any();
         if raw.type_index >= TypeIndex::kTVMFFIStaticObjectBegin as i32
             && unsafe { raw.data_union.v_obj } as usize == *MISSING_OBJECT
@@ -472,7 +479,7 @@ impl<K, V> Map<K, V> {
     /// `Map<KU, VU>(const Map<K, V>&)` does for bases `KU` and `VU`.
     ///
     /// It is a method, not `From`, for the reasons on 
[`Array::upcast`](crate::Array::upcast).
-    #[inline]
+    #[inline(always)]
     pub fn upcast<KU: TypeSubsumes<K>, VU: TypeSubsumes<V>>(self) -> Map<KU, 
VU> {
         Map {
             data: self.data,
@@ -481,7 +488,7 @@ impl<K, V> Map<K, V> {
     }
 
     /// Borrow this map as a `Map<KU, VU>`, as [`upcast`](Self::upcast) does.
-    #[inline]
+    #[inline(always)]
     pub fn upcast_ref<KU: TypeSubsumes<K>, VU: TypeSubsumes<V>>(&self) -> 
&Map<KU, VU> {
         // SAFETY: both are `repr(C)` over one `ObjectArc<MapObj>`, and every
         // entry of `Map<K, V>` is a valid entry of `Map<KU, VU>`.
@@ -489,22 +496,30 @@ impl<K, V> Map<K, V> {
     }
 }
 
-impl<K, V, KU: TypeSubsumes<K>, VU: TypeSubsumes<V>> IntoObjectRef<Map<KU, 
VU>> for Map<K, V> {
+impl<K, V, KU: TypeSubsumes<K>, VU: TypeSubsumes<V>> IntoArg<Map<KU, VU>> for 
Map<K, V> {
     type Output = Map<KU, VU>;
     #[inline(always)]
-    fn into_object_ref(self) -> Map<KU, VU> {
+    fn into_arg(self) -> Map<KU, VU> {
+        self.upcast()
+    }
+    #[inline(always)]
+    fn into_owned(self) -> Map<KU, VU> {
         self.upcast()
     }
 }
 
-impl<'a, K, V, KU: TypeSubsumes<K> + 'a, VU: TypeSubsumes<V> + 'a> 
IntoObjectRef<Map<KU, VU>>
+impl<'a, K, V, KU: TypeSubsumes<K> + 'a, VU: TypeSubsumes<V> + 'a> 
IntoArg<Map<KU, VU>>
     for &'a Map<K, V>
 {
     type Output = &'a Map<KU, VU>;
     #[inline(always)]
-    fn into_object_ref(self) -> &'a Map<KU, VU> {
+    fn into_arg(self) -> &'a Map<KU, VU> {
         self.upcast_ref()
     }
+    #[inline(always)]
+    fn into_owned(self) -> Map<KU, VU> {
+        self.upcast_ref().clone()
+    }
 }
 
 // --- Iterators ---
diff --git a/rust/tvm-ffi/src/collections/tensor.rs 
b/rust/tvm-ffi/src/collections/tensor.rs
index 0cee1734..24ec0200 100644
--- a/rust/tvm-ffi/src/collections/tensor.rs
+++ b/rust/tvm-ffi/src/collections/tensor.rs
@@ -694,36 +694,26 @@ impl<'a> From<&TensorView<'a>> for AnyView<'a> {
 }
 
 // Passed to a function as a `DLTensor*`.
-impl<'a> crate::function_internal::IntoArgHolder for TensorView<'a> {
-    type Target = TensorView<'a>;
-    fn into_arg_holder(self) -> Self::Target {
+impl<'a> crate::function_internal::PackedCallArg for TensorView<'a> {
+    type Holder = Self;
+    #[inline(always)]
+    fn into_packed_arg(self) -> Self {
         self
     }
-}
-
-impl<'a, 'b> crate::function_internal::IntoArgHolder for &'b TensorView<'a> {
-    type Target = &'b TensorView<'a>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
+    #[inline(always)]
+    fn as_packed_arg(holder: &Self::Holder) -> AnyView<'_> {
+        AnyView::from(holder)
     }
 }
 
-impl<'a> crate::function_internal::ArgIntoRef for TensorView<'a> {
-    type Target = TensorView<'a>;
-    fn to_ref(&self) -> &Self::Target {
+impl<'a, 'b> crate::function_internal::PackedCallArg for &'b TensorView<'a> {
+    type Holder = Self;
+    #[inline(always)]
+    fn into_packed_arg(self) -> Self {
         self
     }
-}
-
-impl<'a> crate::function_internal::ArgIntoRef for &TensorView<'a> {
-    type Target = TensorView<'a>;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
-
-impl crate::function_internal::PackedArg for TensorView<'_> {
-    fn as_packed_arg(&self) -> AnyView<'_> {
-        AnyView::from(self)
+    #[inline(always)]
+    fn as_packed_arg(holder: &Self::Holder) -> AnyView<'_> {
+        AnyView::from(*holder)
     }
 }
diff --git a/rust/tvm-ffi/src/extra/module.rs b/rust/tvm-ffi/src/extra/module.rs
index fbebcc88..adee30d0 100644
--- a/rust/tvm-ffi/src/extra/module.rs
+++ b/rust/tvm-ffi/src/extra/module.rs
@@ -60,7 +60,7 @@ impl Module {
     pub fn load_from_file<Str: AsRef<str>>(file_name: Str) -> Result<Module> {
         let file_name = crate::string::String::from(file_name);
         crate::cached_global_func!("ffi.ModuleLoadFromFile")
-            .call_tuple_with_len::<1, _>((file_name,))?
+            .call_tuple((file_name,))?
             .try_into()
     }
 
@@ -81,7 +81,7 @@ impl Module {
     pub fn get_function<Str: AsRef<str>>(&self, name: Str) -> Result<Function> 
{
         let name = crate::string::String::from(name);
         crate::cached_global_func!("ffi.ModuleGetFunction")
-            .call_tuple_with_len::<3, _>((self, name, false))?
+            .call_tuple((self, name, false))?
             .try_into()
     }
 
@@ -90,7 +90,7 @@ impl Module {
     pub fn get_function_with_imports<Str: AsRef<str>>(&self, name: Str) -> 
Result<Function> {
         let name = crate::string::String::from(name);
         crate::cached_global_func!("ffi.ModuleGetFunction")
-            .call_tuple_with_len::<3, _>((self, name, true))?
+            .call_tuple((self, name, true))?
             .try_into()
     }
 
@@ -106,7 +106,7 @@ impl Module {
     pub fn implements_function<Str: AsRef<str>>(&self, name: Str) -> 
Result<bool> {
         let name = String::from(name);
         crate::cached_global_func!("ffi.ModuleImplementsFunction")
-            .call_tuple_with_len::<3, _>((self, name, false))?
+            .call_tuple((self, name, false))?
             .try_into()
     }
 
@@ -115,7 +115,7 @@ impl Module {
     pub fn implements_function_with_imports<Str: AsRef<str>>(&self, name: Str) 
-> Result<bool> {
         let name = String::from(name);
         crate::cached_global_func!("ffi.ModuleImplementsFunction")
-            .call_tuple_with_len::<3, _>((self, name, true))?
+            .call_tuple((self, name, true))?
             .try_into()
     }
 
@@ -135,7 +135,7 @@ impl Module {
     pub fn get_function_metadata<Str: AsRef<str>>(&self, name: Str) -> 
Result<Option<String>> {
         let name = String::from(name);
         crate::cached_global_func!("ffi.ModuleGetFunctionMetadata")
-            .call_tuple_with_len::<3, _>((self, name, false))?
+            .call_tuple((self, name, false))?
             .try_into()
     }
 
@@ -147,7 +147,7 @@ impl Module {
     ) -> Result<Option<String>> {
         let name = String::from(name);
         crate::cached_global_func!("ffi.ModuleGetFunctionMetadata")
-            .call_tuple_with_len::<3, _>((self, name, true))?
+            .call_tuple((self, name, true))?
             .try_into()
     }
 
@@ -166,7 +166,7 @@ impl Module {
     pub fn get_function_doc<Str: AsRef<str>>(&self, name: Str) -> 
Result<Option<String>> {
         let name = String::from(name);
         crate::cached_global_func!("ffi.ModuleGetFunctionDoc")
-            .call_tuple_with_len::<3, _>((self, name, false))?
+            .call_tuple((self, name, false))?
             .try_into()
     }
 
@@ -178,7 +178,7 @@ impl Module {
     ) -> Result<Option<String>> {
         let name = String::from(name);
         crate::cached_global_func!("ffi.ModuleGetFunctionDoc")
-            .call_tuple_with_len::<3, _>((self, name, true))?
+            .call_tuple((self, name, true))?
             .try_into()
     }
 }
diff --git a/rust/tvm-ffi/src/extra/unchanged.rs 
b/rust/tvm-ffi/src/extra/unchanged.rs
index 4a0c8182..bff097a2 100644
--- a/rust/tvm-ffi/src/extra/unchanged.rs
+++ b/rust/tvm-ffi/src/extra/unchanged.rs
@@ -322,6 +322,8 @@ impl<T: ContainerElement> Clone for UnchangedOr<T> {
     }
 }
 
+crate::impl_into_arg!(impl[T: ContainerElement] UnchangedOr<T>);
+
 impl<'b, T: ContainerElement> TryFrom<&'b Any> for UnchangedOr<T> {
     type Error = Error;
     #[inline]
@@ -392,6 +394,7 @@ unsafe impl AnyCompatible for Unchanged {
 }
 
 crate::impl_try_from_any!(Unchanged);
+crate::impl_into_arg!(Unchanged);
 
 // SAFETY: the carrier contains either the resource-free marker or exactly
 // T's owning representation. Borrowing never acquires ownership; moving
diff --git a/rust/tvm-ffi/src/function.rs b/rust/tvm-ffi/src/function.rs
index 5f709ced..064a42a2 100644
--- a/rust/tvm-ffi/src/function.rs
+++ b/rust/tvm-ffi/src/function.rs
@@ -19,10 +19,12 @@
 //! [`Function`], a callable shared with C++ and Python, and the global 
function
 //! registry.
 
+pub use crate::function_internal::{PackedCallArg, PackedCallArgs};
+
 use crate::any::{Any, AnyView};
 use crate::derive::{Object, ObjectRef};
 use crate::error::{Error, Result};
-use crate::function_internal::{AsPackedCallable, TupleAsPackedArgs};
+use crate::function_internal::AsPackedCallable;
 use crate::object::{Object, ObjectArc, ObjectCore};
 use crate::type_traits::AnyCompatible;
 use tvm_ffi_sys::{
@@ -194,7 +196,8 @@ impl Function {
     /// Call the function with the arguments in the tuple `tuple_args`.
     ///
     /// An element may be a value or a reference to one; a reference passes an
-    /// object without retaining it.
+    /// object without retaining it, and a `&str` or `&[u8]` passes as a
+    /// `String` or `Bytes`.
     ///
     /// # Arguments
     ///
@@ -209,63 +212,8 @@ impl Function {
     /// Returns the error that the function raises.
     ///
     /// See also [`Function`].
-    pub fn call_tuple<TupleType>(&self, tuple_args: TupleType) -> Result<Any>
-    where
-        TupleType: TupleAsPackedArgs,
-    {
-        // This is a workaround for Rust's requirement that stack allocation 
size
-        // must be known at compile time for generic types.
-        // While we know args_len is a constant, Rust doesn't allow us to 
directly
-        // declare [AnyView::new(); args_len] in generic contexts.
-        //
-        // We use a small vector optimization pattern:
-        // 1. First allocate a small stack buffer (stack_args)
-        // 2. If args_len exceeds STACK_LEN, allocate a heap buffer (heap_args)
-        // 3. Use the appropriate buffer based on size
-        //
-        // Since args_len is a compile-time constant, the compiler should 
optimize
-        // away the unused branch, making this approach efficient.
-        const STACK_LEN: usize = 4;
-        let mut stack_args = [AnyView::new(); STACK_LEN];
-        let mut heap_args = Vec::<AnyView>::new();
-        let args_len = <TupleType as TupleAsPackedArgs>::LEN;
-        // get packed arguments
-        let packed_args: &mut [AnyView] = if args_len <= STACK_LEN {
-            &mut stack_args[..args_len]
-        } else {
-            heap_args.resize(args_len, AnyView::new());
-            &mut heap_args[..args_len]
-        };
-        tuple_args.fill_any_view(packed_args);
-        self.call_packed(packed_args)
-    }
-    /// Call the function with the `LEN` arguments in `tuple_args`.
-    ///
-    /// This is [`call_tuple`](Self::call_tuple) for an argument count known at
-    /// compile time, without its small-vector overhead.
-    ///
-    /// # Arguments
-    ///
-    /// * `tuple_args` - The `LEN` arguments, as a tuple.
-    ///
-    /// # Returns
-    ///
-    /// The function's result.
-    ///
-    /// # Errors
-    ///
-    /// Returns the error that the function raises.
-    #[doc(hidden)]
-    pub fn call_tuple_with_len<const LEN: usize, TupleType>(
-        &self,
-        tuple_args: TupleType,
-    ) -> Result<Any>
-    where
-        TupleType: TupleAsPackedArgs,
-    {
-        let mut packed_args = [AnyView::new(); LEN];
-        tuple_args.fill_any_view(&mut packed_args);
-        self.call_packed(&packed_args)
+    pub fn call_tuple(&self, tuple_args: impl PackedCallArgs) -> Result<Any> {
+        tuple_args.with_packed_args(|packed_args| 
self.call_packed(packed_args))
     }
     /// Look up the global function registered as `name`.
     ///
diff --git a/rust/tvm-ffi/src/function_internal.rs 
b/rust/tvm-ffi/src/function_internal.rs
index b8fc801c..885a0a21 100644
--- a/rust/tvm-ffi/src/function_internal.rs
+++ b/rust/tvm-ffi/src/function_internal.rs
@@ -169,273 +169,88 @@ impl_as_packed_callable!(6; T0, T1, T2, T3, T4, T5);
 impl_as_packed_callable!(7; T0, T1, T2, T3, T4, T5, T6);
 impl_as_packed_callable!(8; T0, T1, T2, T3, T4, T5, T6, T7);
 
-//--------------------------------------------------------------
-// IntoArgHolder, helper to convert to canonical holding type
-//
-// This is needed sometimes for reference types that may need to
-// be converted to value types.
-//--------------------------------------------------------------
-pub trait IntoArgHolder {
-    type Target;
-    fn into_arg_holder(self) -> Self::Target;
+/// One argument of a packed call: held for the call, then viewed as an FFI
+/// value. `&str` and `&[u8]` are held as `String` and `Bytes`. Implemented by
+/// `#[derive(ObjectRef)]` and `impl_any_compatible_conversions!`.
+pub trait PackedCallArg {
+    /// What keeps the argument alive during the call.
+    type Holder;
+    fn into_packed_arg(self) -> Self::Holder;
+    fn as_packed_arg(holder: &Self::Holder) -> AnyView<'_>;
 }
 
 // `Any` itself is an argument; every other value type is listed in `any.rs`.
-crate::impl_into_arg_holder_default!(Any);
+crate::impl_into_packed_arg!(Any);
 
-// string will be converted to String for argument passing
-impl IntoArgHolder for &str {
-    type Target = String;
-    fn into_arg_holder(self) -> Self::Target {
-        String::from(self)
-    }
-}
-
-// string will be converted to String for argument passing
-impl IntoArgHolder for &[u8] {
-    type Target = Bytes;
-    fn into_arg_holder(self) -> Self::Target {
-        Bytes::from(self)
-    }
-}
-
-// helper trait to implement IntoArgHolderTuple to apply into_arg_holder to 
each element
-pub trait IntoArgHolderTuple {
-    type Target;
-    fn into_arg_holder_tuple(self) -> Self::Target;
-}
-
-macro_rules! impl_into_arg_holder_tuple {
-    ( $($T:ident),* ; $($idx:tt),* ) => {
-        impl<$($T),*> $crate::function_internal::IntoArgHolderTuple for 
($($T,)*)
-        where
-            $($T: IntoArgHolder),* {
-            type Target = ($($T::Target,)*);
-
-            fn into_arg_holder_tuple(self) -> Self::Target {
-                ($(self.$idx.into_arg_holder(),)*)
-            }
-        }
-    };
-}
-
-impl IntoArgHolderTuple for () {
-    type Target = ();
-    fn into_arg_holder_tuple(self) -> Self::Target {}
-}
-impl_into_arg_holder_tuple!(T0; 0);
-impl_into_arg_holder_tuple!(T0, T1; 0, 1);
-impl_into_arg_holder_tuple!(T0, T1, T2; 0, 1, 2);
-impl_into_arg_holder_tuple!(T0, T1, T2, T3; 0, 1, 2, 3);
-impl_into_arg_holder_tuple!(T0, T1, T2, T3, T4; 0, 1, 2, 3, 4);
-impl_into_arg_holder_tuple!(T0, T1, T2, T3, T4, T5; 0, 1, 2, 3, 4, 5);
-impl_into_arg_holder_tuple!(T0, T1, T2, T3, T4, T5, T6; 0, 1, 2, 3, 4, 5, 6);
-impl_into_arg_holder_tuple!(T0, T1, T2, T3, T4, T5, T6, T7; 0, 1, 2, 3, 4, 5, 
6, 7);
-
-//------------------------------------------------------------
-// ArgIntoRef
-//
-// Helper to turn argument type to reference type
-// This is effectively AsRef<T> but removes the need of T
-//-----------------------------------------------------------
-pub trait ArgIntoRef {
-    type Target;
-    fn to_ref(&self) -> &Self::Target;
-}
-
-/// Convert a canonical argument holder into its packed ABI view.
-#[doc(hidden)]
-pub trait PackedArg {
-    fn as_packed_arg(&self) -> AnyView<'_>;
-}
-
-impl<T: AnyCompatible> PackedArg for T {
+impl PackedCallArg for &str {
+    type Holder = String;
     #[inline]
-    fn as_packed_arg(&self) -> AnyView<'_> {
-        AnyView::from(self)
+    fn into_packed_arg(self) -> String {
+        String::from(self)
     }
-}
-
-impl PackedArg for Any {
-    #[inline]
-    fn as_packed_arg(&self) -> AnyView<'_> {
-        AnyView::from(self)
+    #[inline(always)]
+    fn as_packed_arg(holder: &String) -> AnyView<'_> {
+        AnyView::from(holder)
     }
 }
 
-impl<T> PackedArg for RValueRef<T>
-where
-    T: ObjectRefCore,
-{
+impl PackedCallArg for &[u8] {
+    type Holder = Bytes;
     #[inline]
-    fn as_packed_arg(&self) -> AnyView<'_> {
-        AnyView::from(self)
-    }
-}
-
-// `Any` itself is an argument; every other value type is listed in `any.rs`.
-crate::impl_arg_into_ref!(Any);
-
-// Generic holders require explicit implementations rather than scalar macro 
entries.
-impl<T: AnyCompatible> IntoArgHolder for Option<T> {
-    type Target = Self;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-
-impl<'a, T: AnyCompatible> IntoArgHolder for &'a Option<T> {
-    type Target = &'a Option<T>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-
-impl<T: AnyCompatible> ArgIntoRef for Option<T> {
-    type Target = Self;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
-
-impl<T: AnyCompatible> ArgIntoRef for &Option<T> {
-    type Target = Option<T>;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
-
-impl<T: ContainerElement + Clone> IntoArgHolder for crate::Array<T> {
-    type Target = crate::Array<T>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-impl<'a, T: ContainerElement + Clone> IntoArgHolder for &'a crate::Array<T> {
-    type Target = &'a crate::Array<T>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-impl<T: ContainerElement + Clone> ArgIntoRef for crate::Array<T> {
-    type Target = crate::Array<T>;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
-impl<T: ContainerElement + Clone> ArgIntoRef for &crate::Array<T> {
-    type Target = crate::Array<T>;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
-
-impl<T> IntoArgHolder for RValueRef<T>
-where
-    T: ObjectRefCore,
-{
-    type Target = Self;
-    fn into_arg_holder(self) -> Self::Target {
-        self
+    fn into_packed_arg(self) -> Bytes {
+        Bytes::from(self)
     }
-}
-
-impl<T> ArgIntoRef for RValueRef<T>
-where
-    T: ObjectRefCore,
-{
-    type Target = Self;
-    fn to_ref(&self) -> &Self::Target {
-        self
+    #[inline(always)]
+    fn as_packed_arg(holder: &Bytes) -> AnyView<'_> {
+        AnyView::from(holder)
     }
 }
 
-impl<K: ContainerElement, V: ContainerElement> IntoArgHolder for crate::Map<K, 
V> {
-    type Target = crate::Map<K, V>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-impl<'a, K: ContainerElement, V: ContainerElement> IntoArgHolder for &'a 
crate::Map<K, V> {
-    type Target = &'a crate::Map<K, V>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-impl<K: ContainerElement, V: ContainerElement> ArgIntoRef for crate::Map<K, V> 
{
-    type Target = crate::Map<K, V>;
-    fn to_ref(&self) -> &Self::Target {
+impl<T: ObjectRefCore> PackedCallArg for RValueRef<T> {
+    type Holder = Self;
+    #[inline(always)]
+    fn into_packed_arg(self) -> Self {
         self
     }
-}
-impl<K: ContainerElement, V: ContainerElement> ArgIntoRef for &crate::Map<K, 
V> {
-    type Target = crate::Map<K, V>;
-    fn to_ref(&self) -> &Self::Target {
-        self
+    #[inline(always)]
+    fn as_packed_arg(holder: &Self::Holder) -> AnyView<'_> {
+        AnyView::from(holder)
     }
 }
 
-impl<T: ContainerElement> IntoArgHolder for crate::List<T> {
-    type Target = crate::List<T>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-impl<'a, T: ContainerElement> IntoArgHolder for &'a crate::List<T> {
-    type Target = &'a crate::List<T>;
-    fn into_arg_holder(self) -> Self::Target {
-        self
-    }
-}
-impl<T: ContainerElement> ArgIntoRef for crate::List<T> {
-    type Target = crate::List<T>;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
-impl<T: ContainerElement> ArgIntoRef for &crate::List<T> {
-    type Target = crate::List<T>;
-    fn to_ref(&self) -> &Self::Target {
-        self
-    }
-}
+// Generic types, which the per-type lists cannot name.
+crate::impl_into_packed_arg!(impl[T: AnyCompatible] Option<T>);
+crate::impl_into_packed_arg!(impl[T: ContainerElement + Clone] 
crate::Array<T>);
+crate::impl_into_packed_arg!(impl[K: ContainerElement, V: ContainerElement] 
crate::Map<K, V>);
+crate::impl_into_packed_arg!(impl[T: ContainerElement] crate::List<T>);
 
-//-----------------------------------------------------------
-// TupleAsPackedArgs
-//
-// Helper to turn tuple type to packed arguments
-//-----------------------------------------------------------
-pub trait TupleAsPackedArgs {
+/// The arguments of a packed call: a tuple of 0 to 8 [`PackedCallArg`] values.
+pub trait PackedCallArgs {
     const LEN: usize;
-    fn fill_any_view<'a>(&'a self, any_view: &mut [AnyView<'a>]);
+    /// Call `f` with the arguments as views, which live until `f` returns.
+    fn with_packed_args<R>(self, f: impl FnOnce(&[AnyView<'_>]) -> R) -> R;
 }
 
-macro_rules! impl_tuple_as_packed_args {
-    ( $len:expr; $($T:ident),* ; $($idx:tt),* ) => {
-        impl<$($T),*> TupleAsPackedArgs for ($($T,)*)
-        where
-            $(
-                $T: ArgIntoRef,
-                $T::Target: PackedArg,
-            )*
-        {
+macro_rules! impl_into_packed_args {
+    ($len:literal; $($T:ident),*; $($idx:tt),*) => {
+        impl<$($T: PackedCallArg),*> PackedCallArgs for ($($T,)*) {
             const LEN: usize = $len;
-
-            fn fill_any_view<'a>(&'a self, _any_view: &mut [AnyView<'a>]) {
-                $(
-                    _any_view[$idx] = self.$idx.to_ref().as_packed_arg();
-                )*
+            #[inline(always)]
+            fn with_packed_args<R>(self, f: impl FnOnce(&[AnyView<'_>]) -> R) 
-> R {
+                let _holders = ($(self.$idx.into_packed_arg(),)*);
+                let views: [AnyView<'_>; $len] = 
[$($T::as_packed_arg(&_holders.$idx)),*];
+                f(&views)
             }
         }
     };
 }
 
-impl_tuple_as_packed_args!(0;;);
-impl_tuple_as_packed_args!(1; T0; 0);
-impl_tuple_as_packed_args!(2; T0, T1; 0, 1);
-impl_tuple_as_packed_args!(3; T0, T1, T2; 0, 1, 2);
-impl_tuple_as_packed_args!(4; T0, T1, T2, T3; 0, 1, 2, 3);
-impl_tuple_as_packed_args!(5; T0, T1, T2, T3, T4; 0, 1, 2, 3, 4);
-impl_tuple_as_packed_args!(6; T0, T1, T2, T3, T4, T5; 0, 1, 2, 3, 4, 5);
-impl_tuple_as_packed_args!(7; T0, T1, T2, T3, T4, T5, T6; 0, 1, 2, 3, 4, 5, 6);
-impl_tuple_as_packed_args!(8; T0, T1, T2, T3, T4, T5, T6, T7; 0, 1, 2, 3, 4, 
5, 6, 7);
+impl_into_packed_args!(0;;);
+impl_into_packed_args!(1; T0; 0);
+impl_into_packed_args!(2; T0, T1; 0, 1);
+impl_into_packed_args!(3; T0, T1, T2; 0, 1, 2);
+impl_into_packed_args!(4; T0, T1, T2, T3; 0, 1, 2, 3);
+impl_into_packed_args!(5; T0, T1, T2, T3, T4; 0, 1, 2, 3, 4);
+impl_into_packed_args!(6; T0, T1, T2, T3, T4, T5; 0, 1, 2, 3, 4, 5);
+impl_into_packed_args!(7; T0, T1, T2, T3, T4, T5, T6; 0, 1, 2, 3, 4, 5, 6);
+impl_into_packed_args!(8; T0, T1, T2, T3, T4, T5, T6, T7; 0, 1, 2, 3, 4, 5, 6, 
7);
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 8dd0421a..41229d66 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -117,11 +117,11 @@ pub use crate::extra::structural_visit::{
 pub use crate::extra::structural_walk::{structural_walk, IntoWalkResult, 
WalkOrder, WalkResult};
 pub use crate::extra::unchanged::{Unchanged, UnchangedOr};
 pub use crate::function::Function;
-pub use crate::object::{IntoObjectRef, Object, ObjectArc, ObjectCore, 
ObjectRef, ObjectRefCore};
+pub use crate::object::{Object, ObjectArc, ObjectCore, ObjectRef, 
ObjectRefCore};
 pub use crate::optional::Optional;
 pub use crate::static_init::run_static_init_blocks;
 pub use crate::string::{Bytes, String};
-pub use crate::type_traits::AnyCompatible;
+pub use crate::type_traits::{AnyCompatible, IntoArg};
 /// Generate runtime type dispatch from the `prefix_*` methods of an impl.
 ///
 /// `#[dispatch(prefix, (First, Args...), Ret)]` on an inherent impl makes its
@@ -260,7 +260,7 @@ pub use tvm_ffi_macros::match_any;
 ///   infers as it, and any other field as `impl Into<T>`. This is the
 ///   object-level allocator, which sees only tokens and so cannot tell an
 ///   object reference from a value; a constructor on the reference takes an
-///   object reference as [`impl IntoObjectRef<T>`](IntoObjectRef), as the
+///   object reference as [`impl IntoArg<T>`](IntoArg), as the
 ///   ones `tvm-ffi-stubgen` generates do.
 ///
 /// # Arguments
diff --git a/rust/tvm-ffi/src/macros.rs b/rust/tvm-ffi/src/macros.rs
index 30fb5627..31ff2d91 100644
--- a/rust/tvm-ffi/src/macros.rs
+++ b/rust/tvm-ffi/src/macros.rs
@@ -232,7 +232,7 @@ macro_rules! attach_context {
 /// its base object-reference types: `From<Source>` and `From<&Source>` into
 /// `Target`, `AsRef<Target>`, which borrows a `&Source` as `&Target`
 /// without touching the reference count, and
-/// [`IntoObjectRef<Target>`](crate::object::IntoObjectRef) for `Source` and 
`&Source`,
+/// [`IntoArg<Target>`](crate::IntoArg) for `Source` and `&Source`,
 /// and [`TypeSubsumes<Source>`](crate::type_traits::TypeSubsumes) for 
`Target`, so
 /// [`Array::upcast`](crate::Array::upcast) turns an `Array<Source>` into an
 /// `Array<Target>`. It also implements the strict downcast back, 
`TryFrom<Target>` and
@@ -290,20 +290,28 @@ macro_rules! impl_object_ref_upcast {
             // `target`, and an object reference never changes its object.
             unsafe impl $crate::type_traits::TypeSubsumes<$source> for $target 
{}
 
-            impl $crate::object::IntoObjectRef<$target> for $source {
+            impl $crate::IntoArg<$target> for $source {
                 type Output = $target;
                 #[inline(always)]
-                fn into_object_ref(self) -> $target {
+                fn into_arg(self) -> $target {
+                    ::core::convert::Into::into(self)
+                }
+                #[inline(always)]
+                fn into_owned(self) -> $target {
                     ::core::convert::Into::into(self)
                 }
             }
 
-            impl<'a> $crate::object::IntoObjectRef<$target> for &'a $source {
+            impl<'a> $crate::IntoArg<$target> for &'a $source {
                 type Output = &'a $target;
                 #[inline(always)]
-                fn into_object_ref(self) -> &'a $target {
+                fn into_arg(self) -> &'a $target {
                     ::core::convert::AsRef::<$target>::as_ref(self)
                 }
+                #[inline(always)]
+                fn into_owned(self) -> $target {
+                    ::core::convert::Into::into(self)
+                }
             }
 
             impl ::core::convert::AsRef<$target> for $source {
@@ -490,8 +498,8 @@ macro_rules! impl_object_ref_downcast_ref {
 macro_rules! impl_any_compatible_conversions {
     ($($t:ty),* $(,)?) => {
         $crate::impl_try_from_any!($($t),*);
-        $crate::impl_arg_into_ref!($($t),*);
-        $crate::impl_into_arg_holder_default!($($t),*);
+        $crate::impl_into_arg!($($t),*);
+        $crate::impl_into_packed_arg!($($t),*);
     };
 }
 
@@ -537,6 +545,40 @@ macro_rules! impl_try_from_any {
     };
 }
 
+/// `IntoArg<T>` for `T` and `&T`; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_into_arg {
+    ($($t:ty),* $(,)?) => {
+        $($crate::impl_into_arg!(impl[] $t);)*
+    };
+    (impl[$($g:tt)*] $t:ty) => {
+            impl<$($g)*> $crate::IntoArg<$t> for $t {
+                type Output = $t;
+                #[inline(always)]
+                fn into_arg(self) -> $t {
+                    self
+                }
+                #[inline(always)]
+                fn into_owned(self) -> $t {
+                    self
+                }
+            }
+
+            impl<'a, $($g)*> $crate::IntoArg<$t> for &'a $t {
+                type Output = &'a $t;
+                #[inline(always)]
+                fn into_arg(self) -> &'a $t {
+                    self
+                }
+                #[inline(always)]
+                fn into_owned(self) -> $t {
+                    ::core::clone::Clone::clone(self)
+                }
+            }
+    };
+}
+
 /// Macro to implement `TryFrom<AnyView>` and `TryFrom<Any>` for generic types 
like `Option<T>`
 #[doc(hidden)]
 #[macro_export]
@@ -582,48 +624,38 @@ macro_rules! impl_try_from_any_for_parametric {
     };
 }
 
-/// Typed-function arguments; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
+/// Packed-call arguments; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
 #[doc(hidden)]
 #[macro_export]
-macro_rules! impl_into_arg_holder_default {
-    ($($t:ty),*) => {
-        $(
-            impl $crate::function_internal::IntoArgHolder for $t {
-                type Target = $t;
-                fn into_arg_holder(self) -> Self::Target {
-                    self
-                }
+macro_rules! impl_into_packed_arg {
+    ($($t:ty),* $(,)?) => {
+        $($crate::impl_into_packed_arg!(impl[] $t);)*
+    };
+    (impl[$($g:tt)*] $t:ty) => {
+        impl<$($g)*> $crate::function_internal::PackedCallArg for $t {
+            type Holder = $t;
+            #[inline(always)]
+            fn into_packed_arg(self) -> $t {
+                self
             }
-            impl<'a> $crate::function_internal::IntoArgHolder for &'a $t {
-                type Target = &'a $t;
-                fn into_arg_holder(self) -> Self::Target {
-                    self
-                }
+            #[inline(always)]
+            fn as_packed_arg(holder: &Self::Holder) -> 
$crate::any::AnyView<'_> {
+                $crate::any::AnyView::from(holder)
             }
-        )*
-    };
-}
+        }
 
-/// `call_tuple` arguments; part of 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions).
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_arg_into_ref {
-    ($($t:ty),*) => {
-        $(
-            impl $crate::function_internal::ArgIntoRef for $t {
-                type Target = $t;
-                fn to_ref(&self) -> &Self::Target {
-                    &self
-                }
+        impl<'a, $($g)*> $crate::function_internal::PackedCallArg for &'a $t {
+            type Holder = &'a $t;
+            #[inline(always)]
+            fn into_packed_arg(self) -> &'a $t {
+                self
             }
-            impl<'a> $crate::function_internal::ArgIntoRef for &'a $t {
-                type Target = $t;
-                fn to_ref(&self) -> &Self::Target {
-                    &self
-                }
+            #[inline(always)]
+            fn as_packed_arg(holder: &Self::Holder) -> 
$crate::any::AnyView<'_> {
+                $crate::any::AnyView::from(*holder)
             }
-        )*
-    }
+        }
+    };
 }
 
 // ----------------------------------------------------------------------------
@@ -821,7 +853,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident() -> $ret_ty:ty) => {{
         let _f = $f;
         move || -> $ret_ty {
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<0, 
_>(())?)
+            ::core::convert::TryInto::try_into(_f.call_tuple(())?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -829,9 +861,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident($t0:ty) -> $ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0| -> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0,).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<1, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0,))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -839,9 +869,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident($t0:ty, $t1:ty) -> $ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1| -> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<2, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -849,9 +877,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident($t0:ty, $t1:ty, $t2:ty) -> $ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1, a2: $t2| -> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1, a2).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<3, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1, a2))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -859,9 +885,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident($t0:ty, $t1:ty, $t2:ty, $t3:ty) -> $ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1, a2: $t2, a3: $t3| -> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1, a2, a3).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<4, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1, a2, 
a3))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -869,9 +893,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident($t0:ty, $t1:ty, $t2:ty, $t3:ty, $t4:ty) -> 
$ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1, a2: $t2, a3: $t3, a4: $t4| -> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1, a2, a3, a4).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<5, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1, a2, a3, 
a4))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -879,9 +901,7 @@ macro_rules! into_typed_fn {
     ($f:expr, $trait:ident($t0:ty, $t1:ty, $t2:ty, $t3:ty, $t4:ty, $t5:ty) -> 
$ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1, a2: $t2, a3: $t3, a4: $t4, a5: $t5| -> $ret_ty 
{
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1, a2, a3, a4, a5).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<6, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1, a2, a3, 
a4, a5))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -890,9 +910,7 @@ macro_rules! into_typed_fn {
         -> $ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1, a2: $t2, a3: $t3, a4: $t4, a5: $t5, a6: $t6| 
-> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1, a2, a3, a4, a5, 
a6).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<7, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1, a2, a3, 
a4, a5, a6))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
@@ -901,9 +919,7 @@ macro_rules! into_typed_fn {
         -> $ret_ty:ty) => {{
         let _f = $f;
         move |a0: $t0, a1: $t1, a2: $t2, a3: $t3, a4: $t4, a5: $t5, a6: $t6, 
a7: $t7| -> $ret_ty {
-            use $crate::function_internal::IntoArgHolderTuple;
-            let tuple_args = (a0, a1, a2, a3, a4, a5, a6, 
a7).into_arg_holder_tuple();
-            ::core::convert::TryInto::try_into(_f.call_tuple_with_len::<8, 
_>(tuple_args)?)
+            ::core::convert::TryInto::try_into(_f.call_tuple((a0, a1, a2, a3, 
a4, a5, a6, a7))?)
                 .map_err(::core::convert::Into::into)
         }
     }};
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index a937e6f3..c9d4a3ff 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -792,78 +792,6 @@ unsafe impl<T: ObjectRefAnyCompatible> AnyCompatible for T 
{
     }
 }
 
-/// A value that converts into the object reference `T`, owned or borrowed.
-///
-/// An owned reference converts into an owned `T`, and a borrowed one borrows
-/// as `&T` without changing a reference count, as [`IntoIterator`] consumes a
-/// `Vec` but borrows from a `&Vec`. 
[`impl_object_ref_upcast!`](crate::impl_object_ref_upcast)
-/// implements it for each derived reference and its reference into the base,
-/// and `#[derive(ObjectRef)]` for the type itself. A function that only reads
-/// the value borrows the output with `.borrow()`; one that keeps it takes
-/// ownership with [`into_owned_object_ref`](Self::into_owned_object_ref), 
which clones only a
-/// borrowed reference.
-///
-/// # Examples
-///
-/// ```
-/// use std::borrow::Borrow;
-/// use tvm_ffi::derive::ObjectRef;
-/// use tvm_ffi::object::{IntoObjectRef, ObjectRef, ObjectRefCore};
-/// use tvm_ffi::{rs_object, Object, ObjectArc};
-///
-/// #[rs_object("my_ext.MyPair", final)]
-/// pub struct MyPairObj {
-///     base_: Object,
-///     pub first: i64,
-///     pub second: i64,
-/// }
-///
-/// #[derive(Clone, ObjectRef)]
-/// pub struct MyPair {
-///     data: ObjectArc<MyPairObj>,
-/// }
-///
-/// fn same(a: impl IntoObjectRef<ObjectRef>, b: &ObjectRef) -> bool {
-///     a.into_object_ref().borrow().same_as(b)
-/// }
-///
-/// let pair = MyPair::from_object(MyPairObj::new(Object::new(), 1, 2));
-/// let base: ObjectRef = pair.clone().into();
-/// assert!(same(&pair, &base)); // borrowed as `&ObjectRef`, no new reference
-/// assert!(same(pair.clone(), &base)); // moved into an `ObjectRef`
-///
-/// // A function that keeps the value takes ownership.
-/// fn keep(value: impl IntoObjectRef<ObjectRef>) -> ObjectRef {
-///     value.into_owned_object_ref()
-/// }
-/// assert!(keep(&pair).same_as(&base));
-/// ```
-pub trait IntoObjectRef<T> {
-    /// `T` for an owned value, or `&T` for a borrowed one.
-    type Output: std::borrow::Borrow<T> + Into<T>;
-
-    /// Convert into the reference `T`.
-    ///
-    /// # Returns
-    ///
-    /// The owned or borrowed reference.
-    fn into_object_ref(self) -> Self::Output;
-
-    /// Convert into an owned `T`, as 
[`Cow::into_owned`](std::borrow::Cow::into_owned)
-    /// does: an owned value moves, and only a borrowed reference is cloned.
-    ///
-    /// # Returns
-    ///
-    /// The owned reference.
-    #[inline(always)]
-    fn into_owned_object_ref(self) -> T
-    where
-        Self: Sized,
-    {
-        self.into_object_ref().into()
-    }
-}
-
 /// An owning, hashable identity key for an FFI object.
 ///
 /// The retained strong reference prevents the allocation address from being
diff --git a/rust/tvm-ffi/src/optional.rs b/rust/tvm-ffi/src/optional.rs
index 250ed0eb..d17208df 100644
--- a/rust/tvm-ffi/src/optional.rs
+++ b/rust/tvm-ffi/src/optional.rs
@@ -250,6 +250,8 @@ unsafe impl<T: OptionalCompatible> AnyCompatible for 
Optional<T> {
     }
 }
 
+crate::impl_into_arg!(impl[T: OptionalCompatible] Optional<T>);
+
 impl<T: OptionalCompatible> Default for Optional<T> {
     /// The empty optional, holding `None`.
     #[inline]
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index adef17ea..ad7fef78 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -16,28 +16,34 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-//! [`AnyCompatible`], how a Rust type converts to and from FFI values.
+//! Traits that map Rust types to TVM-FFI values.
 
 use crate::any::{Any, AnyView};
+use std::borrow::Borrow;
 use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
 use tvm_ffi_sys::{TVMFFIAny, TVMFFIGetTypeInfo, TVMFFIObject};
 
-//-----------------------------------------------------
-// AnyCompatible
-//-----------------------------------------------------
-/// A type that an [`Any`] can hold: how its values are stored in, and read
-/// from, a `TVMFFIAny` cell.
+/// A type whose values an FFI cell can hold: how a value is stored in a
+/// `TVMFFIAny` and read back.
+///
+/// [`AnyView`] and [`TensorView`](crate::TensorView) borrow their value, so
+/// they are not. Containers and generic code bound on [`ContainerElement`] to
+/// also accept [`Any`].
+///
+/// # Implementing
 ///
-/// `#[derive(ObjectRef)]` implements it for object references; other types
-/// implement it by hand.
+/// Object references derive it with `#[derive(ObjectRef)]`. Any other type
+/// implements it by hand, then calls
+/// 
[`impl_any_compatible_conversions!`](crate::impl_any_compatible_conversions),
+/// which adds its `From`, `TryFrom` and argument conversions. Coherence keeps
+/// those out of this trait, so without the macro the type is stored but not
+/// converted.
 ///
 /// # Safety
-/// Implementations must encode and decode `TVMFFIAny` cells in the layout of
-/// their FFI type index, and every `*_after_check` function must be sound for
-/// each cell that `check_any_strict` accepts. 
[`is_undefined`](Self::is_undefined)
-/// returns false for every value, and true for all-zero bytes unless they are
-/// a value whose drop does nothing: reflection drops and reads a field it
-/// does not report undefined.
+/// - A value is written in the cell layout of its type index, and read back 
from it.
+/// - Every `*_after_check` method is sound on each cell that 
`check_any_strict` accepts.
+/// - `is_undefined` is false for every value. It is true for all-zero bytes,
+///   unless those bytes are a value whose drop does nothing.
 pub unsafe trait AnyCompatible: Sized {
     /// Whether the `AnyView -> Self` conversion matches one exact leaf type.
     ///
@@ -173,97 +179,20 @@ pub unsafe trait AnyCompatible: Sized {
     }
 }
 
-/// The type key of the runtime type index `type_index`.
-pub(crate) fn runtime_type_key(type_index: i32) -> String {
-    unsafe {
-        let info = TVMFFIGetTypeInfo(type_index);
-        assert!(!info.is_null(), "TVMFFIGetTypeInfo returned null");
-        (*info).type_key.as_str().to_string()
-    }
-}
-
-/// Writes a type schema, `{"type":"<origin>"}` or, with type arguments,
-/// `{"type":"<origin>","args":[...]}`.
-pub(crate) fn type_schema(origin: &str, args: &[String]) -> String {
-    if args.is_empty() {
-        format!(r#"{{"type":"{origin}"}}"#)
-    } else {
-        format!(r#"{{"type":"{origin}","args":[{}]}}"#, args.join(","))
-    }
-}
-
-/// The metadata recorded for a function or field of type schema
-/// `type_schema`: `{"type_schema":<type_schema as a JSON string>}`.
-pub(crate) fn type_schema_metadata(type_schema: &str) -> String {
-    format!(r#"{{"type_schema":{}}}"#, escape_string_json(type_schema))
-}
-
-/// `text` as a JSON string literal, escaped so that the metadata matches the
-/// C++ runtime's byte for byte.
-fn escape_string_json(text: &str) -> String {
-    let mut out = String::with_capacity(text.len() + 2);
-    out.push('"');
-    for ch in text.chars() {
-        match ch {
-            '"' => out.push_str("\\\""),
-            '\\' => out.push_str("\\\\"),
-            '/' => out.push_str("\\/"),
-            '\u{8}' => out.push_str("\\b"),
-            '\u{c}' => out.push_str("\\f"),
-            '\n' => out.push_str("\\n"),
-            '\r' => out.push_str("\\r"),
-            '\t' => out.push_str("\\t"),
-            ch if (ch as u32) < 0x20 || ch == '\u{7f}' => {
-                out.push_str(&format!("\\u{:04x}", ch as u32))
-            }
-            ch => out.push(ch),
-        }
-    }
-    out.push('"');
-    out
-}
-
-/// The type schema of a function parameter or return type.
-///
-/// This is [`AnyCompatible::type_schema`] for a type that converts through
-/// [`Any`], and `{"type":"Any"}` for `Any` and `AnyView` themselves.
-pub(crate) trait TypeSchema {
-    fn type_schema() -> String;
-}
-
-impl<T: AnyCompatible> TypeSchema for T {
-    fn type_schema() -> String {
-        <T as AnyCompatible>::type_schema()
-    }
-}
-
-impl TypeSchema for Any {
-    fn type_schema() -> String {
-        type_schema("Any", &[])
-    }
-}
-
-impl TypeSchema for AnyView<'_> {
-    fn type_schema() -> String {
-        type_schema("Any", &[])
-    }
-}
-
-/// A value an FFI container such as [`Array`](crate::Array) or
-/// [`Map`](crate::Map) holds: every [`AnyCompatible`] type, and [`Any`].
+/// A value an FFI container such as [`Array`](crate::Array) holds: every
+/// [`AnyCompatible`] type, and [`Any`].
 ///
-/// Use it as the bound of code generic over container elements, as
-/// `Array<T: ContainerElement>` is. The trait is sealed: implement
-/// [`AnyCompatible`] instead, and the type is an element.
+/// Sealed: implement [`AnyCompatible`] to make a type an element.
 pub trait ContainerElement: container_element_ops::Ops {}
 
 impl<T: AnyCompatible> ContainerElement for T {}
 impl ContainerElement for Any {}
 
-/// Every `Source` value is a valid `Self` as stored, so a container of
-/// `Source` is a container of `Self` without a copy, as C++
-/// `type_subsumes_v<Self, Source>`. [`Array::upcast`](crate::Array::upcast)
-/// and [`Map::upcast`](crate::Map::upcast) use it.
+/// `Self` subsumes `Source`: every `Source` value, as stored, is a valid
+/// `Self`, a compile-time fact checked by no code, as C++
+/// `type_subsumes_v<Self, Source>`. So a container of `Source` is a container
+/// of `Self` without a copy, as [`Array::upcast`](crate::Array::upcast) and
+/// [`Map::upcast`](crate::Map::upcast) use.
 ///
 /// It holds for every type and itself, and for a base object reference and
 /// each derived one, which 
[`impl_object_ref_upcast!`](crate::impl_object_ref_upcast)
@@ -277,6 +206,48 @@ pub unsafe trait TypeSubsumes<Source: ?Sized> {}
 
 unsafe impl<T: ?Sized> TypeSubsumes<T> for T {}
 
+/// A `T`, a `&T` or a subtype of `T`, taken where a `T` is expected; a `&str`
+/// or `&[u8]` converts into a `String` or `Bytes`.
+///
+/// [`into_arg`](Self::into_arg) returns a `T` or a `&T` without changing a
+/// reference count, but those two conversions allocate;
+/// [`into_owned`](Self::into_owned) moves an owned value and clones only a
+/// borrowed one, as [`Cow::into_owned`](std::borrow::Cow::into_owned).
+pub trait IntoArg<T> {
+    /// `T` when owned, `&T` when borrowed.
+    type Output: Borrow<T>;
+
+    /// Convert into a `T` or a `&T`.
+    fn into_arg(self) -> Self::Output;
+
+    /// Convert into an owned `T`, cloning only a borrowed value.
+    fn into_owned(self) -> T;
+}
+
+impl IntoArg<crate::String> for &str {
+    type Output = crate::String;
+    #[inline]
+    fn into_arg(self) -> crate::String {
+        crate::String::from(self)
+    }
+    #[inline]
+    fn into_owned(self) -> crate::String {
+        crate::String::from(self)
+    }
+}
+
+impl IntoArg<crate::string::Bytes> for &[u8] {
+    type Output = crate::string::Bytes;
+    #[inline]
+    fn into_arg(self) -> crate::string::Bytes {
+        crate::string::Bytes::from(self)
+    }
+    #[inline]
+    fn into_owned(self) -> crate::string::Bytes {
+        crate::string::Bytes::from(self)
+    }
+}
+
 /// A type whose value an FFI cell stores in place, so `&Self` borrows from the
 /// cell, as [`Array`](crate::Array) indexing does.
 ///
@@ -384,13 +355,6 @@ unsafe impl<T: crate::object::ObjectRefCore> 
RefFromAnyUnchecked for Option<T> {
     }
 }
 
-unsafe impl<T: crate::object::ObjectRefCore> DowncastRefFromAny for Option<T> {
-    #[inline(always)]
-    unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> {
-        downcast_ref_from_any_in_place::<Self>(cell)
-    }
-}
-
 /// A type that [`Any::downcast_ref`] and [`AnyView::downcast_ref`] borrow from
 /// an FFI cell in place, without a reference count change: object types
 /// (`ArrayObj`), managed references (`Array<T>`, `String`, any
@@ -398,7 +362,8 @@ unsafe impl<T: crate::object::ObjectRefCore> 
DowncastRefFromAny for Option<T> {
 /// reference and scalars such as `i64`.
 ///
 /// Name it to bound a generic borrow, as in `fn get<T: DowncastRefFromAny>`.
-/// A reference that implements `ObjectRefCore` by hand implements it with
+/// Object types, derived references and containers implement it, not code by
+/// hand: a reference with a hand-written `ObjectRefCore` calls
 /// [`impl_object_ref_downcast_ref!`](crate::impl_object_ref_downcast_ref).
 ///
 /// # Safety
@@ -447,10 +412,59 @@ pub unsafe fn downcast_ref_from_any_in_place<T: 
RefFromAnyUnchecked>(
     }
 }
 
+unsafe impl<T: crate::object::ObjectRefCore> DowncastRefFromAny for Option<T> {
+    #[inline(always)]
+    unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> {
+        downcast_ref_from_any_in_place::<Self>(cell)
+    }
+}
+
+macro_rules! impl_downcast_ref_from_any {
+    ($($t:ty),* $(,)?) => {
+        $(unsafe impl DowncastRefFromAny for $t {
+            #[inline(always)]
+            unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> 
{
+                downcast_ref_from_any_in_place::<Self>(cell)
+            }
+        })*
+    };
+}
+
+// An `&Any` must own what it points at, as an `Any` does: a view's cell may
+// instead borrow a C string, byte array, `DLTensor` or rvalue slot, whose
+// `&Any` would clone into an owner of a borrowed resource. Those are refused;
+// an `Any` or container cell never holds them.
+unsafe impl DowncastRefFromAny for Any {
+    #[inline(always)]
+    unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> {
+        let index = cell.type_index;
+        let borrowed = index == TypeIndex::kTVMFFIRawStr as i32
+            || index == TypeIndex::kTVMFFIByteArrayPtr as i32
+            || index == TypeIndex::kTVMFFIDLTensorPtr as i32
+            || index == TypeIndex::kTVMFFIObjectRValueRef as i32;
+        if borrowed {
+            None
+        } else {
+            Some(Any::ref_from_any_unchecked(cell))
+        }
+    }
+}
+
+impl_downcast_ref_from_any!(
+    crate::String,
+    crate::string::Bytes,
+    i64,
+    f64,
+    crate::big_int::BigInt,
+    tvm_ffi_sys::dlpack::DLDataType,
+    tvm_ffi_sys::dlpack::DLDevice,
+);
+
 /// A type that an object reference's [`ObjectRefCore::downcast_ref`] borrows
 /// from the reference's object pointer: object types (`ArrayObj`), and managed
 /// references that are exactly one object pointer (`Array<T>`, every
-/// `#[derive(ObjectRef)]` type).
+/// `#[derive(ObjectRef)]` type). Object types, derived references and
+/// containers implement it, not code by hand.
 ///
 /// [`ObjectRefCore::downcast_ref`]: crate::object::ObjectRefCore::downcast_ref
 ///
@@ -532,249 +546,143 @@ pub unsafe fn downcast_ref_from_object_in_place<T: 
RefFromAnyUnchecked>(
     }
 }
 
-macro_rules! impl_downcast_ref_from_any {
-    ($($t:ty),* $(,)?) => {
-        $(unsafe impl DowncastRefFromAny for $t {
-            #[inline(always)]
-            unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> 
{
-                downcast_ref_from_any_in_place::<Self>(cell)
-            }
-        })*
-    };
-}
-
-// An `&Any` must own what it points at, as an `Any` does: a view's cell may
-// instead borrow a C string, byte array, `DLTensor` or rvalue slot, whose
-// `&Any` would clone into an owner of a borrowed resource. Those are refused;
-// an `Any` or container cell never holds them.
-unsafe impl DowncastRefFromAny for Any {
-    #[inline(always)]
-    unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> {
-        let index = cell.type_index;
-        let borrowed = index == TypeIndex::kTVMFFIRawStr as i32
-            || index == TypeIndex::kTVMFFIByteArrayPtr as i32
-            || index == TypeIndex::kTVMFFIDLTensorPtr as i32
-            || index == TypeIndex::kTVMFFIObjectRValueRef as i32;
-        if borrowed {
-            None
-        } else {
-            Some(Any::ref_from_any_unchecked(cell))
-        }
+//-----------------------------------------------------
+// AnyCompatible impls
+//-----------------------------------------------------
+unsafe impl AnyCompatible for bool {
+    unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
+        data.type_index = TypeIndex::kTVMFFIBool as i32;
+        data.small_str_len = 0;
+        data.data_union.v_int64 = *src as i64;
+    }
+    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
+        data.type_index == TypeIndex::kTVMFFIBool as i32
     }
-}
-
-impl_downcast_ref_from_any!(
-    crate::String,
-    crate::string::Bytes,
-    i64,
-    f64,
-    crate::big_int::BigInt,
-    tvm_ffi_sys::dlpack::DLDataType,
-    tvm_ffi_sys::dlpack::DLDevice,
-);
 
-mod container_element_ops {
-    use super::{Any, AnyCompatible, AnyView, TVMFFIAny};
+    unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
+        data.data_union.v_int64 != 0
+    }
 
-    pub trait Ops: Sized {
-        const CONTAINER_IS_ANY: bool = false;
+    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
+        data.type_index = TypeIndex::kTVMFFIBool as i32;
+        data.small_str_len = 0;
+        data.data_union.v_int64 = src as i64;
+    }
 
-        unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny);
-        unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny);
-        unsafe fn container_check_any_strict(data: &TVMFFIAny) -> bool;
-        unsafe fn container_match_constraints(data: &TVMFFIAny) -> bool;
-        unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny) 
-> Self;
-        unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self;
-        unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()>;
-        fn container_get_mismatch_type_info(data: &TVMFFIAny) -> String;
-        fn container_type_str() -> String;
-        fn container_type_schema() -> String;
+    unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
+        data.data_union.v_int64 != 0
     }
 
-    impl<T: AnyCompatible> Ops for T {
-        #[inline]
-        unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny) 
{
-            <T as AnyCompatible>::copy_to_any_view(src, data)
+    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
+        if data.type_index == TypeIndex::kTVMFFIBool as i32
+            || data.type_index == TypeIndex::kTVMFFIInt as i32
+        {
+            unsafe { Ok(data.data_union.v_int64 != 0) }
+        } else {
+            Err(())
         }
+    }
 
-        #[inline]
-        unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny) {
-            <T as AnyCompatible>::move_to_any(src, data)
-        }
+    fn type_str() -> String {
+        "bool".to_string()
+    }
+}
 
-        #[inline]
-        unsafe fn container_check_any_strict(data: &TVMFFIAny) -> bool {
-            <T as AnyCompatible>::check_any_strict(data)
-        }
-        #[inline]
-        unsafe fn container_match_constraints(data: &TVMFFIAny) -> bool {
-            <T as AnyCompatible>::match_constraints(data)
-        }
+macro_rules! impl_any_compatible_for_int {
+    ($($int_type:ty),* $(,)?) => {
+        $(
+            unsafe impl AnyCompatible for $int_type {
+                fn type_str() -> String {
+                    "int".to_string()
+                }
 
-        #[inline]
-        unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny) 
-> Self {
-            <T as AnyCompatible>::copy_from_any_view_after_check(data)
-        }
-
-        #[inline]
-        unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
-            <T as AnyCompatible>::move_from_any_after_check(data)
-        }
-
-        #[inline]
-        unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
-            <T as AnyCompatible>::try_cast_from_any_view(data)
-        }
-
-        #[inline]
-        fn container_get_mismatch_type_info(data: &TVMFFIAny) -> String {
-            <T as AnyCompatible>::get_mismatch_type_info(data)
-        }
-
-        #[inline]
-        fn container_type_str() -> String {
-            <T as AnyCompatible>::type_str()
-        }
-
-        #[inline]
-        fn container_type_schema() -> String {
-            <T as AnyCompatible>::type_schema()
-        }
-    }
-
-    impl Ops for Any {
-        const CONTAINER_IS_ANY: bool = true;
-
-        #[inline]
-        unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny) 
{
-            *data = *src.as_raw_ffi_any();
-        }
-
-        #[inline]
-        unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny) {
-            *data = Any::into_raw_ffi_any(src);
-        }
-
-        #[inline]
-        unsafe fn container_check_any_strict(_data: &TVMFFIAny) -> bool {
-            true
-        }
-        #[inline]
-        unsafe fn container_match_constraints(_data: &TVMFFIAny) -> bool {
-            true
-        }
+                unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
+                    data.type_index = TypeIndex::kTVMFFIInt as i32;
+                    data.small_str_len = 0;
+                    data.data_union.v_int64 = *src as i64;
+                }
 
-        #[inline]
-        unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny) 
-> Self {
-            Any::from(AnyView::from_raw_ffi_any(*data))
-        }
+                unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
+                    data.type_index == TypeIndex::kTVMFFIInt as i32
+                }
 
-        #[inline]
-        unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
-            Any::from_raw_ffi_any(std::mem::replace(data, TVMFFIAny::new()))
-        }
+                unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
+                    <$int_type>::try_from(data.data_union.v_int64).is_ok()
+                }
 
-        #[inline]
-        unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
-            Ok(Any::from(AnyView::from_raw_ffi_any(*data)))
-        }
+                unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> 
Self {
+                    data.data_union.v_int64 as $int_type
+                }
 
-        #[inline]
-        fn container_get_mismatch_type_info(_data: &TVMFFIAny) -> String {
-            "Any".to_string()
-        }
+                unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
+                    data.type_index = TypeIndex::kTVMFFIInt as i32;
+                    data.small_str_len = 0;
+                    data.data_union.v_int64 = src as i64;
+                }
 
-        #[inline]
-        fn container_type_str() -> String {
-            "Any".to_string()
-        }
+                unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
+                    data.data_union.v_int64 as $int_type
+                }
 
-        #[inline]
-        fn container_type_schema() -> String {
-            <Any as super::TypeSchema>::type_schema()
-        }
-    }
+                unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
+                    if data.type_index == TypeIndex::kTVMFFIInt as i32 ||
+                       data.type_index == TypeIndex::kTVMFFIBool as i32 {
+                        Ok(data.data_union.v_int64 as $int_type)
+                    } else {
+                        Err(())
+                    }
+                }
+            }
+        )*
+    };
 }
 
-unsafe impl AnyCompatible for bool {
-    unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
-        data.type_index = TypeIndex::kTVMFFIBool as i32;
-        data.small_str_len = 0;
-        data.data_union.v_int64 = *src as i64;
-    }
-    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
-        data.type_index == TypeIndex::kTVMFFIBool as i32
-    }
-
-    unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
-        data.data_union.v_int64 != 0
-    }
-
-    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
-        data.type_index = TypeIndex::kTVMFFIBool as i32;
-        data.small_str_len = 0;
-        data.data_union.v_int64 = src as i64;
-    }
-
-    unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
-        data.data_union.v_int64 != 0
-    }
-
-    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
-        if data.type_index == TypeIndex::kTVMFFIBool as i32
-            || data.type_index == TypeIndex::kTVMFFIInt as i32
-        {
-            unsafe { Ok(data.data_union.v_int64 != 0) }
-        } else {
-            Err(())
-        }
-    }
-
-    fn type_str() -> String {
-        "bool".to_string()
-    }
-}
+// Implement AnyCompatible for all integer types
+impl_any_compatible_for_int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, 
usize);
 
-macro_rules! impl_any_compatible_for_int {
-    ($($int_type:ty),* $(,)?) => {
+macro_rules! impl_any_compatible_for_float {
+    ($($float_type:ty),* $(,)?) => {
         $(
-            unsafe impl AnyCompatible for $int_type {
+            unsafe impl AnyCompatible for $float_type {
                 fn type_str() -> String {
-                    "int".to_string()
+                    "float".to_string()
                 }
 
                 unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
-                    data.type_index = TypeIndex::kTVMFFIInt as i32;
+                    data.type_index = TypeIndex::kTVMFFIFloat as i32;
                     data.small_str_len = 0;
-                    data.data_union.v_int64 = *src as i64;
+                    data.data_union.v_float64 = *src as f64;
                 }
 
                 unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
-                    data.type_index == TypeIndex::kTVMFFIInt as i32
+                    data.type_index == TypeIndex::kTVMFFIFloat as i32
                 }
 
                 unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
-                    <$int_type>::try_from(data.data_union.v_int64).is_ok()
+                    let value = data.data_union.v_float64;
+                    ((value as $float_type) as f64).to_bits() == 
value.to_bits()
                 }
 
                 unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> 
Self {
-                    data.data_union.v_int64 as $int_type
+                    data.data_union.v_float64 as $float_type
                 }
 
                 unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
-                    data.type_index = TypeIndex::kTVMFFIInt as i32;
+                    data.type_index = TypeIndex::kTVMFFIFloat as i32;
                     data.small_str_len = 0;
-                    data.data_union.v_int64 = src as i64;
+                    data.data_union.v_float64 = src as f64;
                 }
 
                 unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
-                    data.data_union.v_int64 as $int_type
+                    data.data_union.v_float64 as $float_type
                 }
 
                 unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
-                    if data.type_index == TypeIndex::kTVMFFIInt as i32 ||
+                    if data.type_index == TypeIndex::kTVMFFIFloat as i32 {
+                        Ok(data.data_union.v_float64 as $float_type)
+                    } else if data.type_index == TypeIndex::kTVMFFIInt as i32 
||
                        data.type_index == TypeIndex::kTVMFFIBool as i32 {
-                        Ok(data.data_union.v_int64 as $int_type)
+                        Ok(data.data_union.v_int64 as $float_type)
                     } else {
                         Err(())
                     }
@@ -784,8 +692,8 @@ macro_rules! impl_any_compatible_for_int {
     };
 }
 
-// Implement AnyCompatible for all integer types
-impl_any_compatible_for_int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, 
usize);
+// Implement AnyCompatible for all float types
+impl_any_compatible_for_float!(f32, f64);
 
 // Implement AnyCompatible for `Option<T>`
 unsafe impl<T: AnyCompatible> AnyCompatible for Option<T> {
@@ -869,6 +777,45 @@ unsafe impl<T: AnyCompatible> AnyCompatible for Option<T> {
     }
 }
 
+// Special rule: we convert () to None
+// This allows us to effectively pass () around as null value
+// note that this is indeed a bit relaxation of the type
+// but it is necessary for us to enable void/none interoperability
+unsafe impl AnyCompatible for () {
+    fn type_str() -> String {
+        // make it consistent with c++ representation
+        "None".to_string()
+    }
+
+    unsafe fn copy_to_any_view(_src: &Self, data: &mut TVMFFIAny) {
+        data.type_index = TypeIndex::kTVMFFINone as i32;
+        data.small_str_len = 0;
+        data.data_union.v_int64 = 0;
+    }
+
+    unsafe fn move_to_any(_src: Self, data: &mut TVMFFIAny) {
+        data.type_index = TypeIndex::kTVMFFINone as i32;
+        data.small_str_len = 0;
+        data.data_union.v_int64 = 0;
+    }
+
+    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
+        data.type_index == TypeIndex::kTVMFFINone as i32
+    }
+
+    unsafe fn copy_from_any_view_after_check(_data: &TVMFFIAny) -> Self {}
+
+    unsafe fn move_from_any_after_check(_data: &mut TVMFFIAny) -> Self {}
+
+    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
+        if data.type_index == TypeIndex::kTVMFFINone as i32 {
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+}
+
 unsafe impl AnyCompatible for *mut core::ffi::c_void {
     unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
         data.type_index = TypeIndex::kTVMFFIOpaquePtr as i32;
@@ -908,96 +855,201 @@ unsafe impl AnyCompatible for *mut core::ffi::c_void {
     }
 }
 
-macro_rules! impl_any_compatible_for_float {
-    ($($float_type:ty),* $(,)?) => {
-        $(
-            unsafe impl AnyCompatible for $float_type {
-                fn type_str() -> String {
-                    "float".to_string()
-                }
+mod container_element_ops {
+    use super::{Any, AnyCompatible, AnyView, TVMFFIAny};
 
-                unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
-                    data.type_index = TypeIndex::kTVMFFIFloat as i32;
-                    data.small_str_len = 0;
-                    data.data_union.v_float64 = *src as f64;
-                }
+    pub trait Ops: Sized {
+        const CONTAINER_IS_ANY: bool = false;
 
-                unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
-                    data.type_index == TypeIndex::kTVMFFIFloat as i32
-                }
+        unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny);
+        unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny);
+        unsafe fn container_check_any_strict(data: &TVMFFIAny) -> bool;
+        unsafe fn container_match_constraints(data: &TVMFFIAny) -> bool;
+        unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny) 
-> Self;
+        unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self;
+        unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()>;
+        fn container_get_mismatch_type_info(data: &TVMFFIAny) -> String;
+        fn container_type_str() -> String;
+        fn container_type_schema() -> String;
+    }
 
-                unsafe fn match_constraints(data: &TVMFFIAny) -> bool {
-                    let value = data.data_union.v_float64;
-                    ((value as $float_type) as f64).to_bits() == 
value.to_bits()
-                }
+    impl<T: AnyCompatible> Ops for T {
+        #[inline(always)]
+        unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny) 
{
+            <T as AnyCompatible>::copy_to_any_view(src, data)
+        }
 
-                unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> 
Self {
-                    data.data_union.v_float64 as $float_type
-                }
+        #[inline(always)]
+        unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny) {
+            <T as AnyCompatible>::move_to_any(src, data)
+        }
 
-                unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
-                    data.type_index = TypeIndex::kTVMFFIFloat as i32;
-                    data.small_str_len = 0;
-                    data.data_union.v_float64 = src as f64;
-                }
+        #[inline(always)]
+        unsafe fn container_check_any_strict(data: &TVMFFIAny) -> bool {
+            <T as AnyCompatible>::check_any_strict(data)
+        }
+        #[inline(always)]
+        unsafe fn container_match_constraints(data: &TVMFFIAny) -> bool {
+            <T as AnyCompatible>::match_constraints(data)
+        }
 
-                unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
-                    data.data_union.v_float64 as $float_type
-                }
+        #[inline(always)]
+        unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny) 
-> Self {
+            <T as AnyCompatible>::copy_from_any_view_after_check(data)
+        }
 
-                unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
-                    if data.type_index == TypeIndex::kTVMFFIFloat as i32 {
-                        Ok(data.data_union.v_float64 as $float_type)
-                    } else if data.type_index == TypeIndex::kTVMFFIInt as i32 
||
-                       data.type_index == TypeIndex::kTVMFFIBool as i32 {
-                        Ok(data.data_union.v_int64 as $float_type)
-                    } else {
-                        Err(())
-                    }
-                }
-            }
-        )*
-    };
-}
+        #[inline(always)]
+        unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
+            <T as AnyCompatible>::move_from_any_after_check(data)
+        }
 
-// Implement AnyCompatible for all float types
-impl_any_compatible_for_float!(f32, f64);
+        #[inline(always)]
+        unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
+            <T as AnyCompatible>::try_cast_from_any_view(data)
+        }
 
-// Special rule: we convert () to None
-// This allows us to effectively pass () around as null value
-// note that this is indeed a bit relaxation of the type
-// but it is necessary for us to enable void/none interoperability
-unsafe impl AnyCompatible for () {
-    fn type_str() -> String {
-        // make it consistent with c++ representation
-        "None".to_string()
-    }
+        #[inline(always)]
+        fn container_get_mismatch_type_info(data: &TVMFFIAny) -> String {
+            <T as AnyCompatible>::get_mismatch_type_info(data)
+        }
 
-    unsafe fn copy_to_any_view(_src: &Self, data: &mut TVMFFIAny) {
-        data.type_index = TypeIndex::kTVMFFINone as i32;
-        data.small_str_len = 0;
-        data.data_union.v_int64 = 0;
+        #[inline(always)]
+        fn container_type_str() -> String {
+            <T as AnyCompatible>::type_str()
+        }
+
+        #[inline(always)]
+        fn container_type_schema() -> String {
+            <T as AnyCompatible>::type_schema()
+        }
     }
 
-    unsafe fn move_to_any(_src: Self, data: &mut TVMFFIAny) {
-        data.type_index = TypeIndex::kTVMFFINone as i32;
-        data.small_str_len = 0;
-        data.data_union.v_int64 = 0;
+    impl Ops for Any {
+        const CONTAINER_IS_ANY: bool = true;
+
+        #[inline(always)]
+        unsafe fn container_copy_to_any_view(src: &Self, data: &mut TVMFFIAny) 
{
+            *data = *src.as_raw_ffi_any();
+        }
+
+        #[inline(always)]
+        unsafe fn container_move_to_any(src: Self, data: &mut TVMFFIAny) {
+            *data = Any::into_raw_ffi_any(src);
+        }
+
+        #[inline(always)]
+        unsafe fn container_check_any_strict(_data: &TVMFFIAny) -> bool {
+            true
+        }
+        #[inline(always)]
+        unsafe fn container_match_constraints(_data: &TVMFFIAny) -> bool {
+            true
+        }
+
+        #[inline(always)]
+        unsafe fn container_copy_from_any_view_after_check(data: &TVMFFIAny) 
-> Self {
+            Any::from(AnyView::from_raw_ffi_any(*data))
+        }
+
+        #[inline(always)]
+        unsafe fn container_move_from_any_after_check(data: &mut TVMFFIAny) -> 
Self {
+            Any::from_raw_ffi_any(std::mem::replace(data, TVMFFIAny::new()))
+        }
+
+        #[inline(always)]
+        unsafe fn container_try_cast_from_any_view(data: &TVMFFIAny) -> 
Result<Self, ()> {
+            Ok(Any::from(AnyView::from_raw_ffi_any(*data)))
+        }
+
+        #[inline(always)]
+        fn container_get_mismatch_type_info(_data: &TVMFFIAny) -> String {
+            "Any".to_string()
+        }
+
+        #[inline(always)]
+        fn container_type_str() -> String {
+            "Any".to_string()
+        }
+
+        #[inline(always)]
+        fn container_type_schema() -> String {
+            <Any as super::TypeSchema>::type_schema()
+        }
     }
+}
 
-    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
-        data.type_index == TypeIndex::kTVMFFINone as i32
+/// The type key of the runtime type index `type_index`.
+pub(crate) fn runtime_type_key(type_index: i32) -> String {
+    unsafe {
+        let info = TVMFFIGetTypeInfo(type_index);
+        assert!(!info.is_null(), "TVMFFIGetTypeInfo returned null");
+        (*info).type_key.as_str().to_string()
     }
+}
 
-    unsafe fn copy_from_any_view_after_check(_data: &TVMFFIAny) -> Self {}
+/// Writes a type schema, `{"type":"<origin>"}` or, with type arguments,
+/// `{"type":"<origin>","args":[...]}`.
+pub(crate) fn type_schema(origin: &str, args: &[String]) -> String {
+    if args.is_empty() {
+        format!(r#"{{"type":"{origin}"}}"#)
+    } else {
+        format!(r#"{{"type":"{origin}","args":[{}]}}"#, args.join(","))
+    }
+}
 
-    unsafe fn move_from_any_after_check(_data: &mut TVMFFIAny) -> Self {}
+/// The metadata recorded for a function or field of type schema
+/// `type_schema`: `{"type_schema":<type_schema as a JSON string>}`.
+pub(crate) fn type_schema_metadata(type_schema: &str) -> String {
+    format!(r#"{{"type_schema":{}}}"#, escape_string_json(type_schema))
+}
 
-    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
-        if data.type_index == TypeIndex::kTVMFFINone as i32 {
-            Ok(())
-        } else {
-            Err(())
+/// `text` as a JSON string literal, escaped so that the metadata matches the
+/// C++ runtime's byte for byte.
+fn escape_string_json(text: &str) -> String {
+    let mut out = String::with_capacity(text.len() + 2);
+    out.push('"');
+    for ch in text.chars() {
+        match ch {
+            '"' => out.push_str("\\\""),
+            '\\' => out.push_str("\\\\"),
+            '/' => out.push_str("\\/"),
+            '\u{8}' => out.push_str("\\b"),
+            '\u{c}' => out.push_str("\\f"),
+            '\n' => out.push_str("\\n"),
+            '\r' => out.push_str("\\r"),
+            '\t' => out.push_str("\\t"),
+            ch if (ch as u32) < 0x20 || ch == '\u{7f}' => {
+                out.push_str(&format!("\\u{:04x}", ch as u32))
+            }
+            ch => out.push(ch),
         }
     }
+    out.push('"');
+    out
+}
+
+/// The type schema of a function parameter or return type.
+///
+/// This is [`AnyCompatible::type_schema`] for a type that converts through
+/// [`Any`], and `{"type":"Any"}` for `Any` and `AnyView` themselves.
+pub(crate) trait TypeSchema {
+    fn type_schema() -> String;
+}
+
+impl<T: AnyCompatible> TypeSchema for T {
+    fn type_schema() -> String {
+        <T as AnyCompatible>::type_schema()
+    }
+}
+
+impl TypeSchema for Any {
+    fn type_schema() -> String {
+        type_schema("Any", &[])
+    }
+}
+
+impl TypeSchema for AnyView<'_> {
+    fn type_schema() -> String {
+        type_schema("Any", &[])
+    }
 }
diff --git a/rust/tvm-ffi/tests/test_big_int.rs 
b/rust/tvm-ffi/tests/test_big_int.rs
index c5c1ae8a..e782cbe6 100644
--- a/rust/tvm-ffi/tests/test_big_int.rs
+++ b/rust/tvm-ffi/tests/test_big_int.rs
@@ -484,7 +484,7 @@ fn test_big_int_containers() {
         assert!(map.contains_key(value).unwrap());
         assert_eq!(map.get(value).unwrap(), Some(i as i64));
     }
-    assert_eq!(map.get(&BigInt::from(1u128 << 64)).unwrap(), None);
+    assert_eq!(map.get(BigInt::from(1u128 << 64)).unwrap(), None);
 }
 
 #[test]
diff --git a/rust/tvm-ffi/tests/test_function.rs 
b/rust/tvm-ffi/tests/test_function.rs
index 42d453c2..e47c5277 100644
--- a/rust/tvm-ffi/tests/test_function.rs
+++ b/rust/tvm-ffi/tests/test_function.rs
@@ -85,7 +85,7 @@ fn test_function_call_tuple() {
     let result = sum1.call_tuple((1,)).unwrap();
     assert_eq!(i32::try_from(result).unwrap(), 1 + offset);
     // test pass by reference
-    let result = sum1.call_tuple_with_len::<1, _>((&1,)).unwrap();
+    let result = sum1.call_tuple((&1,)).unwrap();
     assert_eq!(i32::try_from(result).unwrap(), 1 + offset);
     let typed_fn = |x: &i32| -> Result<i32> { 
sum1.call_tuple((x,))?.try_into() };
     let result = typed_fn(&1);
diff --git a/rust/tvm-ffi/tests/test_function_metadata.rs 
b/rust/tvm-ffi/tests/test_function_metadata.rs
index e45863e4..753bc986 100644
--- a/rust/tvm-ffi/tests/test_function_metadata.rs
+++ b/rust/tvm-ffi/tests/test_function_metadata.rs
@@ -53,7 +53,7 @@ fn global_metadata(name: &str) -> String {
     assert_eq!(unsafe { tvm_ffi_sys::TVMFFITestingDummyTarget() }, 0);
     Function::get_global("ffi.GetGlobalFuncMetadata")
         .unwrap()
-        .call_tuple_with_len::<1, _>((String::from(name),))
+        .call_tuple((String::from(name),))
         .unwrap()
         .try_into()
         .unwrap()
@@ -137,7 +137,7 @@ fn test_exports_without_metadata() {
     // SAFETY: rust_add takes two arguments and does not use the handle.
     let add =
         unsafe { Function::from_extern_c(std::ptr::null_mut(), 
__tvm_ffi_rust_add, None).unwrap() };
-    let sum = add.call_tuple_with_len::<2, _>((1i64, 2i64)).unwrap();
+    let sum = add.call_tuple((1i64, 2i64)).unwrap();
     assert_eq!(i64::try_from(sum).unwrap(), 3);
     without_export_metadata::__tvm_ffi__metadata_rust_schema_id_int();
     without_export_metadata::__tvm_ffi__metadata_rust_packed();
@@ -287,6 +287,6 @@ fn test_global_untyped_metadata_matches_cpp() {
 fn test_global_typed_registration_calls_the_function() {
     register_globals();
     let add = Function::get_global("testing.rust.add").unwrap();
-    let sum = add.call_tuple_with_len::<2, _>((1i64, 2i64)).unwrap();
+    let sum = add.call_tuple((1i64, 2i64)).unwrap();
     assert_eq!(i64::try_from(sum).unwrap(), 3);
 }
diff --git a/rust/tvm-ffi/tests/test_map.rs b/rust/tvm-ffi/tests/test_map.rs
index 475e5f7c..7b51c0e0 100644
--- a/rust/tvm-ffi/tests/test_map.rs
+++ b/rust/tvm-ffi/tests/test_map.rs
@@ -45,13 +45,13 @@ fn test_map_basic_lookup() {
     assert_eq!(map.len(), 3);
     assert!(!map.is_empty());
 
-    assert_eq!(map.get(&1).unwrap(), Some(10));
-    assert_eq!(map.get(&2).unwrap(), Some(20));
-    assert_eq!(map.get(&3).unwrap(), Some(30));
-    assert_eq!(map.get(&99).unwrap(), None);
+    assert_eq!(map.get(1).unwrap(), Some(10));
+    assert_eq!(map.get(2).unwrap(), Some(20));
+    assert_eq!(map.get(3).unwrap(), Some(30));
+    assert_eq!(map.get(99).unwrap(), None);
 
-    assert!(map.contains_key(&1).unwrap());
-    assert!(!map.contains_key(&99).unwrap());
+    assert!(map.contains_key(1).unwrap());
+    assert!(!map.contains_key(99).unwrap());
 }
 
 #[test]
@@ -59,7 +59,7 @@ fn test_map_empty() {
     let map: Map<i64, i64> = Map::new();
     assert_eq!(map.len(), 0);
     assert!(map.is_empty());
-    assert_eq!(map.get(&1).unwrap(), None);
+    assert_eq!(map.get(1).unwrap(), None);
     // All three iterators must handle the empty (remaining == 0) case.
     assert_eq!(map.iter().count(), 0);
     assert_eq!(map.keys().count(), 0);
@@ -72,8 +72,8 @@ fn test_map_empty() {
 fn test_map_from_iter_duplicate_keys_last_wins() {
     let map: Map<i64, i64> = [(1i64, 10i64), (1, 20), (2, 
30)].into_iter().collect();
     assert_eq!(map.len(), 2);
-    assert_eq!(map.get(&1).unwrap(), Some(20));
-    assert_eq!(map.get(&2).unwrap(), Some(30));
+    assert_eq!(map.get(1).unwrap(), Some(20));
+    assert_eq!(map.get(2).unwrap(), Some(30));
 }
 
 #[test]
@@ -109,10 +109,10 @@ fn test_map_string_keys() {
     .collect();
 
     assert_eq!(map.len(), 3);
-    assert_eq!(map.get(&String::from("a")).unwrap(), Some(1));
-    assert_eq!(map.get(&String::from("c")).unwrap(), Some(3));
-    assert_eq!(map.get(&String::from("z")).unwrap(), None);
-    assert!(map.contains_key(&String::from("b")).unwrap());
+    assert_eq!(map.get("a").unwrap(), Some(1));
+    assert_eq!(map.get("c").unwrap(), Some(3));
+    assert_eq!(map.get("z").unwrap(), None);
+    assert!(map.contains_key("b").unwrap());
 }
 
 #[test]
@@ -124,8 +124,8 @@ fn test_map_any_roundtrip() {
 
     let back: Map<i64, i64> = Map::try_from(any).expect("Any -> Map failed");
     assert_eq!(back.len(), 2);
-    assert_eq!(back.get(&1).unwrap(), Some(10));
-    assert_eq!(back.get(&2).unwrap(), Some(20));
+    assert_eq!(back.get(1).unwrap(), Some(10));
+    assert_eq!(back.get(2).unwrap(), Some(20));
 }
 
 #[test]
@@ -144,23 +144,22 @@ fn test_map_with_any_values() {
     .collect();
 
     assert_eq!(map.len(), 3);
-    assert!(map.contains_key(&String::from("number")).unwrap());
-    assert!(!map.contains_key(&String::from("missing")).unwrap());
+    assert!(map.contains_key("number").unwrap());
+    assert!(!map.contains_key("missing").unwrap());
     assert_eq!(format!("{map:?}"), "Map<String, Any>[3]");
     assert_eq!(AnyView::from(&array).debug_strong_count().unwrap(), 2);
     assert_eq!(
-        
i64::try_from(map.get(&String::from("number")).unwrap().unwrap()).unwrap(),
+        i64::try_from(map.get("number").unwrap().unwrap()).unwrap(),
         7
     );
     assert_eq!(
-        String::try_from(map.get(&String::from("text")).unwrap().unwrap())
+        String::try_from(map.get("text").unwrap().unwrap())
             .unwrap()
             .as_str(),
         "value"
     );
 
-    let fetched_array =
-        
Array::<i64>::try_from(map.get(&String::from("array")).unwrap().unwrap()).unwrap();
+    let fetched_array = 
Array::<i64>::try_from(map.get("array").unwrap().unwrap()).unwrap();
     assert_eq!(
         fetched_array.iter().copied().collect::<Vec<_>>(),
         vec![1, 2, 3]
@@ -229,7 +228,7 @@ fn test_map_shares_underlying_object() {
     // Cloning shares the same underlying MapObj rather than copying entries.
     let clone = map.clone();
     assert_eq!(clone.len(), 1);
-    assert_eq!(clone.get(&1).unwrap(), Some(10));
+    assert_eq!(clone.get(1).unwrap(), Some(10));
 }
 
 #[test]
@@ -245,10 +244,7 @@ fn test_map_object_values_and_refcount() {
     assert_eq!(AnyView::from(&t).debug_strong_count().unwrap(), base + 1);
 
     // The value round-trips correctly and `get` hands back a fresh handle.
-    let got = map
-        .get(&String::from("x"))
-        .unwrap()
-        .expect("key should be present");
+    let got = map.get("x").unwrap().expect("key should be present");
     assert_eq!(get_val(&got), 7.0);
     assert_eq!(got.ndim(), 2);
     assert_eq!(AnyView::from(&t).debug_strong_count().unwrap(), base + 2);
@@ -274,7 +270,7 @@ fn test_map_object_values_and_refcount() {
 fn test_map_as_function_argument() {
     let map: Map<i64, i64> = [(1i64, 10i64), (2, 20), (3, 
30)].into_iter().collect();
 
-    // Exercises the `ArgIntoRef`/`call_tuple` path, by value and by reference.
+    // Exercises the `call_tuple` path, by value and by reference.
     let map_size = Function::get_global("ffi.MapSize").unwrap();
     let n_by_value: i64 = map_size
         .call_tuple((map.clone(),))
@@ -303,12 +299,12 @@ fn test_map_get_missing_with_object_values() {
         .into_iter()
         .collect();
 
-    let present = map.get(&String::from("x")).unwrap();
+    let present = map.get("x").unwrap();
     assert!(present.is_some());
     assert_eq!(get_val(&present.unwrap()), 7.0);
 
     // Absent key returns `None`, never an object miscast as a tensor.
-    assert!(map.get(&String::from("missing")).unwrap().is_none());
+    assert!(map.get("missing").unwrap().is_none());
 }
 
 /// Views a map behind an `Any` as `Map<K, V>` while bypassing the strict
@@ -334,7 +330,7 @@ fn mistyped_value_map() -> Map<i64, String> {
 #[test]
 fn test_map_get_type_mismatch_is_err() {
     let map = mistyped_value_map();
-    assert!(map.get(&1).is_err());
+    assert!(map.get(1).is_err());
 }
 
 /// Iterators panic on a value type mismatch rather than silently truncating
@@ -357,7 +353,7 @@ fn test_map_get_mistyped_key_debug_asserts() {
     // keys do not match `K = String`.
     let real: Map<i64, i64> = [(1i64, 10i64)].into_iter().collect();
     let map: Map<String, i64> = view_map_unchecked(Any::from(real));
-    let _ = map.get(&String::from("anything"));
+    let _ = map.get("anything");
 }
 
 /// `try_from` rejects a map whose values can neither strictly match nor cast
@@ -377,8 +373,8 @@ fn test_map_any_cast_slow_path_converts() {
     let converted: Map<f64, f64> =
         Map::try_from(Any::from(real.clone())).expect("i64 entries cast to 
f64");
     assert_eq!(converted.len(), 2);
-    assert_eq!(converted.get(&1.0).unwrap(), Some(10.0));
-    assert_eq!(converted.get(&2.0).unwrap(), Some(20.0));
+    assert_eq!(converted.get(1.0).unwrap(), Some(10.0));
+    assert_eq!(converted.get(2.0).unwrap(), Some(20.0));
     // Each map owns its own object: nothing is shared between them.
     assert_eq!(AnyView::from(&real).debug_strong_count(), Some(1));
     assert_eq!(AnyView::from(&converted).debug_strong_count(), Some(1));
@@ -392,6 +388,6 @@ fn test_map_from_dict_copies() {
         .unwrap();
     let map = Map::<String, i64>::try_from(dict.clone()).unwrap();
     assert_eq!(map.len(), 2);
-    assert_eq!(map.get(&String::from("b")).unwrap(), Some(2));
+    assert_eq!(map.get("b").unwrap(), Some(2));
     assert_eq!(dict.debug_strong_count(), Some(1));
 }
diff --git a/rust/tvm-ffi/tests/test_match_any.rs 
b/rust/tvm-ffi/tests/test_match_any.rs
index 8999cd80..96943f3d 100644
--- a/rust/tvm-ffi/tests/test_match_any.rs
+++ b/rust/tvm-ffi/tests/test_match_any.rs
@@ -307,7 +307,7 @@ fn type_alias_matcher_keeps_strict_compatibility() {
 
     let module: Module = Function::get_global("ffi.SystemLib")
         .unwrap()
-        .call_tuple_with_len::<0, _>(())
+        .call_tuple(())
         .unwrap()
         .try_into()
         .unwrap();
diff --git a/rust/tvm-ffi/tests/test_module.rs 
b/rust/tvm-ffi/tests/test_module.rs
index 449be9ec..1096e17c 100644
--- a/rust/tvm-ffi/tests/test_module.rs
+++ b/rust/tvm-ffi/tests/test_module.rs
@@ -55,13 +55,13 @@ fn testing_module() -> Module {
 fn importing_module() -> Module {
     let module: Module = Function::get_global("ffi.SystemLib")
         .unwrap()
-        .call_tuple_with_len::<1, 
_>((String::from("testing.rust.test_module."),))
+        .call_tuple((String::from("testing.rust.test_module."),))
         .unwrap()
         .try_into()
         .unwrap();
     Function::get_global("ffi.ModuleImportModule")
         .unwrap()
-        .call_tuple_with_len::<2, _>((&module, &testing_module()))
+        .call_tuple((&module, &testing_module()))
         .unwrap();
     module
 }
diff --git a/rust/tvm-ffi/tests/test_rs_object.rs 
b/rust/tvm-ffi/tests/test_rs_object.rs
index 9f68e23e..3bffb1e9 100644
--- a/rust/tvm-ffi/tests/test_rs_object.rs
+++ b/rust/tvm-ffi/tests/test_rs_object.rs
@@ -371,6 +371,22 @@ fn test_constructors_deref_and_upcast() {
     assert!(ObjectRef::from(&root).same_as(&root));
 }
 
+#[test]
+fn test_into_arg_into_owned_moves_or_clones() {
+    fn keep(value: impl IntoArg<ObjectRef>) -> ObjectRef {
+        value.into_owned()
+    }
+    let pair = point(1, "a");
+    let count = || AnyView::from(&pair).debug_strong_count().unwrap();
+    let base = count();
+    let cloned = keep(&pair);
+    assert!(cloned.same_as(&pair));
+    assert_eq!(count(), base + 1);
+    let moved = keep(pair.clone());
+    assert!(moved.same_as(&pair));
+    assert_eq!(count(), base + 2);
+}
+
 #[test]
 fn test_containers_upcast_without_a_copy() {
     fn keep_array(value: impl Into<Array<RsPoint>>) -> Array<RsPoint> {
@@ -379,8 +395,8 @@ fn test_containers_upcast_without_a_copy() {
     fn keep_map(value: impl Into<Map<tvm_ffi::String, RsPoint>>) -> 
Map<tvm_ffi::String, RsPoint> {
         value.into()
     }
-    fn len(value: impl IntoObjectRef<Array<RsPoint>>) -> usize {
-        value.into_object_ref().borrow().len()
+    fn len(value: impl IntoArg<Array<RsPoint>>) -> usize {
+        value.into_arg().borrow().len()
     }
 
     let (a, b) = (point(1, "a"), point(2, "b"));
@@ -403,7 +419,7 @@ fn test_containers_upcast_without_a_copy() {
     let map3: Map<tvm_ffi::String, RsPoint3> = [("p".into(), point3(1, 
3))].into();
     let upcast: Map<tvm_ffi::String, RsPoint> = map3.clone().upcast();
     assert!(upcast.same_as(&map3));
-    assert_eq!(upcast.get(&"p".into()).unwrap().unwrap().x, 1);
+    assert_eq!(upcast.get("p").unwrap().unwrap().x, 1);
     let roots: &Map<tvm_ffi::String, ObjectRef> = map3.upcast_ref();
     assert!(roots.same_as(&map3));
 }
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 61b7ea95..ae84991e 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -358,7 +358,7 @@ fn 
user_driven_mutator_controls_default_recursion_and_in_place_opt_in() {
         .and_then(Map::<i64, i64>::try_from)
         .unwrap();
     assert_eq!(map_pointer(&mutated_map), source_map_pointer);
-    assert_eq!(mutated_map.get(&1).unwrap(), Some(11));
+    assert_eq!(mutated_map.get(1).unwrap(), Some(11));
 
     let dict = call_global("ffi.Dict", &[Any::from(1i64), Any::from(10i64)]);
     let dict_pointer = any_object_pointer(&dict);
@@ -1295,7 +1295,7 @@ fn unique_map_reuses_nested_unique_value_storage() {
     let mapped = structural_map(source, &mut IncrementIntegers, 
WalkOrder::PostOrder)
         .and_then(Map::<i64, Array<i64>>::try_from)
         .unwrap();
-    let mapped_child = mapped.get(&1).unwrap().unwrap();
+    let mapped_child = mapped.get(1).unwrap().unwrap();
 
     assert_eq!(map_pointer(&mapped), source_pointer);
     assert_eq!(array_pointer(&mapped_child), child_pointer);
@@ -1319,8 +1319,8 @@ fn shared_map_and_dict_copy_only_when_a_value_changes() {
         .and_then(Map::<i64, i64>::try_from)
         .unwrap();
     assert_ne!(map_pointer(&mapped), source_pointer);
-    assert_eq!(source.get(&1).unwrap(), Some(10));
-    assert_eq!(mapped.get(&1).unwrap(), Some(11));
+    assert_eq!(source.get(1).unwrap(), Some(10));
+    assert_eq!(mapped.get(1).unwrap(), Some(11));
 
     let dict = call_global("ffi.Dict", &[Any::from(1i64), Any::from(10i64)]);
     let dict_pointer = any_object_pointer(&dict);
@@ -1353,8 +1353,8 @@ fn 
shared_map_callback_error_preserves_source_and_reports_object_context() {
         Err(error) => error,
     };
 
-    assert_eq!(source.get(&1).unwrap(), Some(10));
-    assert_eq!(source.get(&2).unwrap(), Some(20));
+    assert_eq!(source.get(1).unwrap(), Some(10));
+    assert_eq!(source.get(2).unwrap(), Some(20));
     assert_eq!(error.message(), "map mapper failed");
     assert!(error.backtrace().contains("origin"));
     assert!(error.backtrace().contains("object `ffi.Map`"));
@@ -1938,8 +1938,8 @@ fn map_keys_are_anchors_and_object_leaves_are_preserved() 
{
     .and_then(Map::<FfiString, i64>::try_from)
     .unwrap();
     assert_eq!(key_callbacks, 0);
-    assert_eq!(mapped.get(&FfiString::from("a")).unwrap(), Some(2));
-    assert_eq!(mapped.get(&FfiString::from("b")).unwrap(), Some(3));
+    assert_eq!(mapped.get(FfiString::from("a")).unwrap(), Some(2));
+    assert_eq!(mapped.get(FfiString::from("b")).unwrap(), Some(3));
 
     let string = FfiString::from("leaf");
     let heterogeneous = Function::get_global("ffi.Array")
diff --git a/tests/python/test_stubgen_rust.py 
b/tests/python/test_stubgen_rust.py
index c42c1da6..edfbbecc 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -778,8 +778,8 @@ impl ExprObj {
 
 impl Expr {
     /// Lossless complete-field allocation.
-    pub fn new(span: impl Into<Option<Span>>, ty: impl IntoObjectRef<Type>) -> 
Self {
-        let obj = ExprObj::new(span.into(), ty.into_owned_object_ref());
+    pub fn new(span: impl Into<Option<Span>>, ty: impl IntoArg<Type>) -> Self {
+        let obj = ExprObj::new(span.into(), ty.into_owned());
         Self { data_: ObjectArc::new(obj) }
     }
 }"""
@@ -826,16 +826,11 @@ impl Add {
     /// Lossless complete-field allocation.
     pub fn new(
         span: impl Into<Option<Span>>,
-        ty: impl IntoObjectRef<PrimType>,
-        a: impl IntoObjectRef<PrimExpr>,
-        b: impl IntoObjectRef<PrimExpr>,
+        ty: impl IntoArg<PrimType>,
+        a: impl IntoArg<PrimExpr>,
+        b: impl IntoArg<PrimExpr>,
     ) -> Self {
-        let obj = AddObj::new(
-            span.into(),
-            ty.into_owned_object_ref(),
-            a.into_owned_object_ref(),
-            b.into_owned_object_ref(),
-        );
+        let obj = AddObj::new(span.into(), ty.into_owned(), a.into_owned(), 
b.into_owned());
         Self { data_: ObjectArc::new(obj) }
     }
 }
@@ -850,7 +845,7 @@ def test_render_complete_expr_golden() -> None:
     RUST.add_directive(imports, "nullable", "ir.Expr.span", 1)
     text, imports = _render(_expr(), imports)
     assert text == EXPR_EXPECTED
-    assert _uses(imports) == {"tvm_ffi::Object", "tvm_ffi::IntoObjectRef", 
"tvm_ffi::ObjectArc"}
+    assert _uses(imports) == {"tvm_ffi::Object", "tvm_ffi::IntoArg", 
"tvm_ffi::ObjectArc"}
 
 
 def test_render_complete_add_golden() -> None:
@@ -871,7 +866,7 @@ def test_render_complete_add_golden() -> None:
     assert text == ADD_EXPECTED
     assert _uses(imports) == {
         "std::ops::Deref",
-        "tvm_ffi::IntoObjectRef",
+        "tvm_ffi::IntoArg",
         "tvm_ffi::ObjectArc",
         "crate::ir::ExprObj",
         "crate::ir::Expr",
@@ -1192,25 +1187,20 @@ def test_custom_new_renames_the_wrapper_allocator() -> 
None:
     assert (
         "impl Add {\n    /// Lossless complete-field allocation.\n"
         "    pub fn from_fields(\n"
-        "        span: impl IntoObjectRef<Span>,\n"
-        "        ty: impl IntoObjectRef<Type>,\n"
-        "        a: impl IntoObjectRef<Expr>,\n"
-        "        b: impl IntoObjectRef<Expr>,\n"
+        "        span: impl IntoArg<Span>,\n"
+        "        ty: impl IntoArg<Type>,\n"
+        "        a: impl IntoArg<Expr>,\n"
+        "        b: impl IntoArg<Expr>,\n"
         "    ) -> Self {\n"
-        "        let obj = AddObj::new(\n"
-        "            span.into_owned_object_ref(),\n"
-        "            ty.into_owned_object_ref(),\n"
-        "            a.into_owned_object_ref(),\n"
-        "            b.into_owned_object_ref(),\n"
-        "        );\n"
+        "        let obj = AddObj::new(span.into_owned(), ty.into_owned(), 
a.into_owned(), b.into_owned());\n"
         "        Self { data_: ObjectArc::new(obj) }\n"
         "    }\n}"
     ) in text
     assert "    pub fn new(" not in text
 
 
-def test_wrapper_allocator_takes_object_references_as_into_object_ref() -> 
None:
-    """An object reference is `impl IntoObjectRef`, a value `impl Into`, a 
scalar itself.
+def test_wrapper_allocator_takes_object_references_as_into_arg() -> None:
+    """An object reference is `impl IntoArg`, a value `impl Into`, a scalar 
itself.
 
     A `field` directive narrowing an object reference keeps it one; `nullable` 
makes it an
     `Option`, a value. `Shape` is a container, taken as `impl Into` like 
`Array`.
@@ -1247,12 +1237,12 @@ def 
test_wrapper_allocator_takes_object_references_as_into_object_ref() -> None:
     text, imports = _render(info, imports)
     assert (
         "    pub fn new(\n"
-        "        tensor: impl IntoObjectRef<Tensor>,\n"
-        "        owner: impl IntoObjectRef<ObjectRef>,\n"
-        "        callback: impl IntoObjectRef<Function>,\n"
-        "        module: impl IntoObjectRef<Module>,\n"
-        "        var: impl IntoObjectRef<Var>,\n"
-        "        buffer: impl IntoObjectRef<BufferVar>,\n"
+        "        tensor: impl IntoArg<Tensor>,\n"
+        "        owner: impl IntoArg<ObjectRef>,\n"
+        "        callback: impl IntoArg<Function>,\n"
+        "        module: impl IntoArg<Module>,\n"
+        "        var: impl IntoArg<Var>,\n"
+        "        buffer: impl IntoArg<BufferVar>,\n"
         "        span: impl Into<Option<Span>>,\n"
         "        name: impl Into<String>,\n"
         "        items: impl Into<Array<Any>>,\n"
@@ -1265,12 +1255,12 @@ def 
test_wrapper_allocator_takes_object_references_as_into_object_ref() -> None:
         "        count: i32,\n"
         "    ) -> Self {\n"
         "        let obj = NodeObj::new(\n"
-        "            tensor.into_owned_object_ref(),\n"
-        "            owner.into_owned_object_ref(),\n"
-        "            callback.into_owned_object_ref(),\n"
-        "            module.into_owned_object_ref(),\n"
-        "            var.into_owned_object_ref(),\n"
-        "            buffer.into_owned_object_ref(),\n"
+        "            tensor.into_owned(),\n"
+        "            owner.into_owned(),\n"
+        "            callback.into_owned(),\n"
+        "            module.into_owned(),\n"
+        "            var.into_owned(),\n"
+        "            buffer.into_owned(),\n"
         "            span.into(),\n"
         "            name.into(),\n"
         "            items.into(),\n"
@@ -1283,10 +1273,10 @@ def 
test_wrapper_allocator_takes_object_references_as_into_object_ref() -> None:
         "            count,\n"
         "        );\n"
     ) in text, text
-    assert "tvm_ffi::IntoObjectRef" in _uses(imports)
+    assert "tvm_ffi::IntoArg" in _uses(imports)
 
 
-def 
test_wrapper_allocator_without_object_references_does_not_import_into_object_ref()
 -> None:
+def 
test_wrapper_allocator_without_object_references_does_not_import_into_arg() -> 
None:
     info = _info(
         "demo.Pair",
         (_field("name", "str", 24, 16), _field("count", "int", 40, 8)),
@@ -1295,8 +1285,8 @@ def 
test_wrapper_allocator_without_object_references_does_not_import_into_object
     )
     text, imports = _render(info)
     assert "    pub fn new(name: impl Into<String>, count: i64) -> Self {" in 
text
-    assert "IntoObjectRef" not in text
-    assert "tvm_ffi::IntoObjectRef" not in _uses(imports)
+    assert "IntoArg" not in text
+    assert "tvm_ffi::IntoArg" not in _uses(imports)
 
 
 def test_allocator_body_wraps_like_rustfmt() -> None:

Reply via email to