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 919672c4 [Rust] Unify dispatcher input conversion and raw base hooks 
(#917)
919672c4 is described below

commit 919672c41b3e445ed1c133aa0ff584ed45f91f99
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Nov 4 09:58:49 2026 -0500

    [Rust] Unify dispatcher input conversion and raw base hooks (#917)
    
    Centralize dispatcher conversion, typed callback matching, callback
    prefix adaptation, and mixed tuple chaining in the shared dispatch
    module. Reuse this conversion at structural walk, map, visit, and mutate
    entry points while preserving recursive dispatcher identity and scoped
    mutable reborrows.
    
    Move default raw operations into the visitor and mutator base contracts.
    Document supported inputs and conversion markers, and replace IntoWalker
    and IntoMapper with IntoDispatcher.
---
 rust/tvm-ffi/src/extra/dispatch.rs               | 285 +++++++++++++++
 rust/tvm-ffi/src/extra/marker.rs                 |  25 --
 rust/tvm-ffi/src/extra/mod.rs                    |   1 -
 rust/tvm-ffi/src/extra/structural_map.rs         | 406 +--------------------
 rust/tvm-ffi/src/extra/structural_mutate.rs      | 125 ++++---
 rust/tvm-ffi/src/extra/structural_visit.rs       | 153 ++++----
 rust/tvm-ffi/src/extra/structural_walk.rs        | 439 +++--------------------
 rust/tvm-ffi/src/lib.rs                          |  22 +-
 rust/tvm-ffi/tests/test_dispatch.rs              |  86 +++++
 rust/tvm-ffi/tests/test_structural_mutate.rs     |  20 +-
 rust/tvm-ffi/tests/test_structural_visit.rs      |   2 +-
 rust/tvm-ffi/tests/test_structural_visit_base.rs |  77 ++--
 12 files changed, 659 insertions(+), 982 deletions(-)

diff --git a/rust/tvm-ffi/src/extra/dispatch.rs 
b/rust/tvm-ffi/src/extra/dispatch.rs
index 3fc0a1b1..a8c6e110 100644
--- a/rust/tvm-ffi/src/extra/dispatch.rs
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -65,3 +65,288 @@ impl<'source, 'value> 
DispatchRecovery<crate::match_any_internal::ViewSource<'so
 {
     type Recovery = ();
 }
+
+use super::structural_common::impl_callback_chain_tuple_arities;
+use std::{borrow::BorrowMut, marker::PhantomData};
+
+/// Inferred selectors for dispatcher normalization.
+///
+/// Entry points infer one marker from the input. Callback markers select 
argument
+/// matching and a leading argument count; tuple markers contain each link's 
marker.
+/// Normalization stores the input once, before traversal, without cloning it.
+pub mod marker {
+    use std::marker::PhantomData;
+    /// Store an owned dispatcher with the exact target argument and result 
types.
+    pub enum FromOwned {}
+    /// Store a mutable borrow of an exact-signature dispatcher.
+    pub enum FromBorrowed {}
+    /// Match a callback's first argument using `Shape`, then pass its first 
`N`
+    /// arguments. `N` ranges from one through four, including the matched 
value.
+    pub struct FromCallback<const N: usize, Shape>(PhantomData<fn() -> Shape>);
+    /// Try tuple links in order. `Markers` is their marker tuple, including 
nested
+    /// tuples; `Args` selects how arguments are reborrowed between links.
+    pub struct FromTuple<Markers, Args = CopyArgs>(PhantomData<fn() -> 
(Markers, Args)>);
+    /// Replay copyable observer arguments; this does not copy dispatcher 
storage.
+    pub enum CopyArgs {}
+    /// Sequentially reborrow a mutable second argument for each tuple link.
+    pub enum MutArg {}
+    /// Match and convert the first `AnyView` to the FFI value type `T`.
+    pub struct Value<T>(PhantomData<fn() -> T>);
+    /// Match a borrowed object node `&T` without retaining it, unlike the
+    /// by-value conversion selected by [`Value<T>`].
+    pub struct BorrowedObject<T>(PhantomData<fn() -> T>);
+    /// Pass the first `AnyView` without a runtime type filter.
+    pub enum AnyView {}
+    /// Match a mutation capability for `T`, retaining its scoped permission.
+    pub struct Mutation<T = crate::Any>(PhantomData<fn() -> T>);
+}
+
+/// Convert an input to a reusable [`Dispatcher`].
+///
+/// `Args` is the complete target argument tuple, `Ret` its complete result, 
and
+/// `M` the inferred input-form marker. `Target` is the actual dispatcher type;
+/// `Output` owns or borrows it and provides `BorrowMut<Target>`. Owned `D` and
+/// borrowed `&mut D` inputs both preserve target `D`; callbacks and tuples 
use an
+/// adapter as their target. This keeps recursive view types tied to the 
dispatcher.
+///
+/// Inputs may be owned dispatchers, mutable dispatcher borrows, callbacks, or
+/// nested tuples mixing these forms. Dispatchers require the exact argument 
and
+/// result signature. Callbacks may accept a leading prefix of one to four
+/// arguments; structural callbacks match an FFI value, an object-node borrow, 
or
+/// `AnyView`; mutation callbacks match a typed `MutationView`. Callable values
+/// and their shared or mutable borrows are stored as supplied, subject to 
their `FnMut` implementation. Only an outer `None` tries
+/// the next tuple link; a matched error or inner `None` stops the chain.
+///
+/// Supply the target argument and result types when calling the conversion 
directly;
+/// the input-form marker remains inferred.
+///
+/// ```
+/// use tvm_ffi::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
+/// let callbacks = (|value: i64| Any::from(value + 1), |_value: AnyView<'_>| 
Any::new());
+/// let mut dispatcher = IntoDispatcher::<(AnyView<'_>,), Option<Result<Any>>, 
_>::into_dispatcher(callbacks);
+/// let result = 
dispatcher.dispatch_tuple((AnyView::from(&41_i64),)).unwrap()?;
+/// assert_eq!(result.try_as::<i64>(), Some(42));
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub trait IntoDispatcher<Args, Ret, M> {
+    /// Actual dispatcher type, independent of its owned or borrowed storage.
+    type Target;
+    /// Storage normalized once at entry, retaining the target's identity.
+    type Output: BorrowMut<Self::Target>;
+    fn into_dispatcher(self) -> Self::Output;
+}
+
+/// Normalized callback or tuple storage; direct dispatchers retain their own 
type.
+#[doc(hidden)]
+pub struct DispatchAdapter<F, M> {
+    input: F,
+    marker: PhantomData<fn() -> M>,
+}
+
+impl<D, Args, Ret> IntoDispatcher<Args, Ret, marker::FromOwned> for D
+where
+    for<'s> &'s mut D: Dispatcher<Args, Ret>,
+{
+    type Target = D;
+    type Output = D;
+    #[inline(always)]
+    fn into_dispatcher(self) -> Self::Output {
+        self
+    }
+}
+
+impl<'d, D, Args, Ret> IntoDispatcher<Args, Ret, marker::FromBorrowed> for &'d 
mut D
+where
+    for<'s> &'s mut D: Dispatcher<Args, Ret>,
+{
+    type Target = D;
+    type Output = &'d mut D;
+    #[inline(always)]
+    fn into_dispatcher(self) -> Self::Output {
+        self
+    }
+}
+
+impl<F, Args, Ret, const N: usize, Shape> IntoDispatcher<Args, Ret, 
marker::FromCallback<N, Shape>>
+    for F
+where
+    F: DispatchLink<Args, Ret, marker::FromCallback<N, Shape>>,
+{
+    type Target = DispatchAdapter<F, marker::FromCallback<N, Shape>>;
+    type Output = Self::Target;
+    #[inline(always)]
+    fn into_dispatcher(self) -> Self::Output {
+        DispatchAdapter {
+            input: self,
+            marker: PhantomData,
+        }
+    }
+}
+
+impl<F, Args, Ret, Markers, Reborrow>
+    IntoDispatcher<Args, Ret, marker::FromTuple<Markers, Reborrow>> for F
+where
+    F: DispatchLink<Args, Ret, marker::FromTuple<Markers, Reborrow>>,
+{
+    type Target = DispatchAdapter<F, marker::FromTuple<Markers, Reborrow>>;
+    type Output = Self::Target;
+    #[inline(always)]
+    fn into_dispatcher(self) -> Self::Output {
+        DispatchAdapter {
+            input: self,
+            marker: PhantomData,
+        }
+    }
+}
+
+/// Implementation boundary shared by callback and tuple adapters.
+#[doc(hidden)]
+pub trait DispatchLink<Args, Ret, M> {
+    fn dispatch_link(&mut self, args: Args) -> Ret;
+}
+
+impl<F, Args, Ret, M> Dispatcher<Args, Ret> for &mut DispatchAdapter<F, M>
+where
+    F: DispatchLink<Args, Ret, M>,
+{
+    #[inline(always)]
+    fn dispatch_tuple(self, args: Args) -> Ret {
+        self.input.dispatch_link(args)
+    }
+}
+
+impl<D, Args, Ret> DispatchLink<Args, Ret, marker::FromOwned> for D
+where
+    for<'s> &'s mut D: Dispatcher<Args, Ret>,
+{
+    #[inline(always)]
+    fn dispatch_link(&mut self, args: Args) -> Ret {
+        self.dispatch_tuple(args)
+    }
+}
+impl<D, Args, Ret> DispatchLink<Args, Ret, marker::FromBorrowed> for &mut D
+where
+    for<'s> &'s mut D: Dispatcher<Args, Ret>,
+{
+    #[inline(always)]
+    fn dispatch_link(&mut self, args: Args) -> Ret {
+        (&mut **self).dispatch_tuple(args)
+    }
+}
+
+/// Operation-specific callback result conversion; matching and chaining are 
shared.
+#[doc(hidden)]
+pub trait CallbackResult<R> {
+    fn callback_result(self) -> R;
+}
+
+macro_rules! callback_prefix {
+    ($n:literal; ($($A:ident:$a:ident),*); ($($B:ident:$b:ident),*)) => {
+        impl<'v, F, T, O, R, $($A,)*>
+            DispatchLink<(AnyView<'v>, $($A,)*), Option<R>, 
marker::FromCallback<$n, marker::Value<T>>> for F
+        where F: FnMut(T, $($B,)*) -> O, T: crate::type_traits::AnyCompatible,
+              O: CallbackResult<R>,
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, (value, $($a,)*): (AnyView<'v>, 
$($A,)*)) -> Option<R> {
+                $(let _ = &$a;)*
+                value.try_as::<T>().map(|typed| self(typed, 
$($b,)*).callback_result())
+            }
+        }
+        impl<'v, F, T, O, R, $($A,)*>
+            DispatchLink<(AnyView<'v>, $($A,)*), Option<R>, 
marker::FromCallback<$n, marker::BorrowedObject<T>>> for F
+        where F: for<'node> FnMut(&'node T, $($B,)*) -> O, T: 
crate::ObjectCore,
+              O: CallbackResult<R>,
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, (value, $($a,)*): (AnyView<'v>, 
$($A,)*)) -> Option<R> {
+                $(let _ = &$a;)*
+                value.downcast_ref::<T>().map(|node| self(node, 
$($b,)*).callback_result())
+            }
+        }
+        impl<'v, F, O, R, $($A,)*>
+            DispatchLink<(AnyView<'v>, $($A,)*), Option<R>, 
marker::FromCallback<$n, marker::AnyView>> for F
+        where F: FnMut(AnyView<'v>, $($B,)*) -> O, O: CallbackResult<R>,
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, (value, $($a,)*): (AnyView<'v>, 
$($A,)*)) -> Option<R> {
+                $(let _ = &$a;)*
+                Some(self(value, $($b,)*).callback_result())
+            }
+        }
+    };
+}
+callback_prefix!(1; (); ());
+callback_prefix!(1; (A1:a1); ());
+callback_prefix!(2; (A1:a1); (A1:a1));
+callback_prefix!(1; (A1:a1, A2:a2); ());
+callback_prefix!(2; (A1:a1, A2:a2); (A1:a1));
+callback_prefix!(3; (A1:a1, A2:a2); (A1:a1, A2:a2));
+callback_prefix!(1; (A1:a1, A2:a2, A3:a3); ());
+callback_prefix!(2; (A1:a1, A2:a2, A3:a3); (A1:a1));
+callback_prefix!(3; (A1:a1, A2:a2, A3:a3); (A1:a1, A2:a2));
+callback_prefix!(4; (A1:a1, A2:a2, A3:a3); (A1:a1, A2:a2, A3:a3));
+
+macro_rules! mutation_callback_prefix {
+    ($n:literal; ($($A:ident:$a:ident),*); ($($B:ident:$b:ident),*)) => {
+        impl<'v, F, T, O, R, $($A,)*>
+            DispatchLink<(crate::MutationView<'v>, $($A,)*), Option<R>, 
marker::FromCallback<$n, marker::Mutation<T>>> for F
+        where F: FnMut(crate::MutationView<'v, T>, $($B,)*) -> O,
+              T: crate::type_traits::ContainerElement, O: CallbackResult<R>,
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, (value, $($a,)*): 
(crate::MutationView<'v>, $($A,)*)) -> Option<R> {
+                $(let _ = &$a;)*
+                value.try_cast::<T>().ok().map(|typed| self(typed, 
$($b,)*).callback_result())
+            }
+        }
+    };
+}
+mutation_callback_prefix!(1; (); ());
+mutation_callback_prefix!(1; (A1:a1); ());
+mutation_callback_prefix!(2; (A1:a1); (A1:a1));
+mutation_callback_prefix!(1; (A1:a1, A2:a2); ());
+mutation_callback_prefix!(2; (A1:a1, A2:a2); (A1:a1));
+mutation_callback_prefix!(3; (A1:a1, A2:a2); (A1:a1, A2:a2));
+mutation_callback_prefix!(1; (A1:a1, A2:a2, A3:a3); ());
+mutation_callback_prefix!(2; (A1:a1, A2:a2, A3:a3); (A1:a1));
+mutation_callback_prefix!(3; (A1:a1, A2:a2, A3:a3); (A1:a1, A2:a2));
+mutation_callback_prefix!(4; (A1:a1, A2:a2, A3:a3); (A1:a1, A2:a2, A3:a3));
+
+// Concrete tuple storage bounds keep trait inference from recursively 
inventing
+// unknown adapter receiver layers. Tuple arity is independent of callback 
arity.
+macro_rules! dispatch_tuple_links {
+    ($(($F:ident, $M:ident, $idx:tt)),+) => {
+        impl<Args: Copy, R, $($F, $M,)+>
+            DispatchLink<Args, Option<R>, marker::FromTuple<($($M,)+)>> for 
($($F,)+)
+        where $($F: DispatchLink<Args, Option<R>, $M>,)+
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, args: Args) -> Option<R> {
+                $(if let Some(result) = self.$idx.dispatch_link(args) { return 
Some(result); })+
+                None
+            }
+        }
+        impl<'v, 'c, C, R, $($F, $M,)+>
+            DispatchLink<(AnyView<'v>, &'c mut C), Option<R>, 
marker::FromTuple<($($M,)+), marker::MutArg>> for ($($F,)+)
+        where $(for<'borrow> $F: DispatchLink<(AnyView<'v>, &'borrow mut C), 
Option<R>, $M>,)+
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, (value, context): (AnyView<'v>, &'c 
mut C)) -> Option<R> {
+                $(if let Some(result) = self.$idx.dispatch_link((value, &mut 
*context)) { return Some(result); })+
+                None
+            }
+        }
+        impl<'v, 'c, C, R, $($F, $M,)+>
+            DispatchLink<(crate::MutationView<'v>, &'c mut C), Option<R>, 
marker::FromTuple<($($M,)+), marker::MutArg>> for ($($F,)+)
+        where $(for<'value, 'borrow> $F: 
DispatchLink<(crate::MutationView<'value>, &'borrow mut C), Option<R>, $M>,)+
+        {
+            #[inline(always)]
+            fn dispatch_link(&mut self, (mut value, context): 
(crate::MutationView<'v>, &'c mut C)) -> Option<R> {
+                $(if let Some(result) = 
self.$idx.dispatch_link((value.reborrow(), &mut *context)) { return 
Some(result); })+
+                None
+            }
+        }
+    };
+}
+impl_callback_chain_tuple_arities!(dispatch_tuple_links);
diff --git a/rust/tvm-ffi/src/extra/marker.rs b/rust/tvm-ffi/src/extra/marker.rs
deleted file mode 100644
index 89e0138a..00000000
--- a/rust/tvm-ffi/src/extra/marker.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-//! Compile-time ownership selectors for dispatcher entry points.
-
-/// Selects an entry point that takes ownership of its dispatcher.
-pub enum Owned {}
-
-/// Selects an entry point that mutably borrows its dispatcher.
-pub enum Borrowed {}
diff --git a/rust/tvm-ffi/src/extra/mod.rs b/rust/tvm-ffi/src/extra/mod.rs
index 911e9f09..9b3484f3 100644
--- a/rust/tvm-ffi/src/extra/mod.rs
+++ b/rust/tvm-ffi/src/extra/mod.rs
@@ -18,7 +18,6 @@
  */
 pub mod cold_init_lazy_lock;
 pub mod dispatch;
-pub mod marker;
 pub mod module;
 mod structural_common;
 pub mod structural_map;
diff --git a/rust/tvm-ffi/src/extra/structural_map.rs 
b/rust/tvm-ffi/src/extra/structural_map.rs
index 98a3bf9e..02c541ab 100644
--- a/rust/tvm-ffi/src/extra/structural_map.rs
+++ b/rust/tvm-ffi/src/extra/structural_map.rs
@@ -19,405 +19,25 @@
 
 //! Ordered replacement callbacks and adapters for structural mapping.
 
-use super::structural_common::{impl_callback_chain_tuple_arities, 
raw_tvm_ffi_any_same_as};
+use super::structural_common::raw_tvm_ffi_any_same_as;
 use super::structural_mutate::{
-    with_mutation_error_context, IntoMutateResult, MutationView, 
StructuralMutateEngine,
-    StructuralMutatorView,
+    IntoMutateResult, MutationView, StructuralMutateEngine, 
StructuralMutatorView,
+    with_mutation_error_context,
 };
 use super::structural_visit::DefRegionKind;
 use super::structural_walk::WalkOrder;
 use super::unchanged::is_unchanged;
-use crate::{Any, AnyView, Dispatcher, ObjectCore, Result};
-use std::marker::PhantomData;
+use crate::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
+use std::borrow::BorrowMut;
 
 // 
-----------------------------------------------------------------------------
 // Map adapter to Dispatcher.
 // 
-----------------------------------------------------------------------------
 
-/// Conversion into the mapper consumed by [`structural_map`].
-#[diagnostic::on_unimplemented(
-    message = "unsupported structural-map callback shape",
-    label = "this value cannot be used as a structural mapper",
-    note = "pass `&mut` a matching `Dispatcher`, a supported closure or 
callback tuple"
-)]
-pub trait IntoMapper<Marker> {
-    type Mapper;
-    fn into_mapper(self) -> Self::Mapper;
-    #[doc(hidden)]
-    fn map_root(self, root: Any, order: WalkOrder) -> Result<Any>;
-}
-
-#[doc(hidden)]
-pub enum ByMapDispatch {}
-
-impl<'a, V> IntoMapper<ByMapDispatch> for &'a mut V
-where
-    for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>,
-{
-    type Mapper = &'a mut V;
-
-    #[inline(always)]
-    fn into_mapper(self) -> Self::Mapper {
-        self
-    }
-
-    #[inline(always)]
-    fn map_root(self, root: Any, order: WalkOrder) -> Result<Any> {
-        run_mapper::<V>(root, self, order)
-    }
-}
-
-/// One typed callback in a structural-map tuple.
-///
-/// Links use first-match order and may receive an owned FFI value, borrowed
-/// object node, or `AnyView<'_>`, optionally followed by [`DefRegionKind`].
-pub trait MapChainLink<Marker>: sealed_map::SealedMapLink<Marker> {
-    #[doc(hidden)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>>;
-}
-
-mod sealed_map {
-    use super::{Any, AnyView, DefRegionKind, Dispatcher, IntoMutateResult, 
ObjectCore, Result};
-
-    pub trait SealedMapLink<Marker> {}
-
-    impl<F, T, O> SealedMapLink<super::ByMapOwned<T>> for F
-    where
-        F: FnMut(T) -> O,
-        O: IntoMutateResult,
-    {
-    }
-
-    impl<F, T, O> SealedMapLink<super::ByMapOwnedKind<T>> for F
-    where
-        F: FnMut(T, DefRegionKind) -> O,
-        O: IntoMutateResult,
-    {
-    }
-
-    impl<F, N: ObjectCore, O> SealedMapLink<super::ByMapNode<N>> for F
-    where
-        F: for<'a> FnMut(&'a N) -> O,
-        O: IntoMutateResult,
-    {
-    }
-
-    impl<F, N: ObjectCore, O> SealedMapLink<super::ByMapNodeKind<N>> for F
-    where
-        F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
-        O: IntoMutateResult,
-    {
-    }
-
-    impl<F, O> SealedMapLink<super::ByMapCatchAll> for F
-    where
-        F: for<'a> FnMut(AnyView<'a>) -> O,
-        O: IntoMutateResult,
-    {
-    }
-
-    impl<F, O> SealedMapLink<super::ByMapCatchAllKind> for F
-    where
-        F: for<'a> FnMut(AnyView<'a>, DefRegionKind) -> O,
-        O: IntoMutateResult,
-    {
-    }
-
-    impl<V> SealedMapLink<super::ByMapDispatchLink> for &mut V where
-        for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>
-    {
-    }
-}
-
-#[doc(hidden)]
-pub struct ByMapOwned<T>(PhantomData<T>);
-
-impl<F, T, O> MapChainLink<ByMapOwned<T>> for F
-where
-    F: FnMut(T) -> O,
-    T: crate::type_traits::AnyCompatible,
-    O: IntoMutateResult,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        _def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        value
-            .try_as::<T>()
-            .map(|typed| self(typed).into_mutate_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByMapOwnedKind<T>(PhantomData<T>);
-
-impl<F, T, O> MapChainLink<ByMapOwnedKind<T>> for F
-where
-    F: FnMut(T, DefRegionKind) -> O,
-    T: crate::type_traits::AnyCompatible,
-    O: IntoMutateResult,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        value
-            .try_as::<T>()
-            .map(|typed| self(typed, def_region_kind).into_mutate_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByMapNode<N>(PhantomData<N>);
-
-impl<F, N, O> MapChainLink<ByMapNode<N>> for F
-where
-    F: for<'a> FnMut(&'a N) -> O,
-    N: ObjectCore,
-    O: IntoMutateResult,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        _def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        value
-            .downcast_ref::<N>()
-            .map(|node| self(node).into_mutate_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByMapNodeKind<N>(PhantomData<N>);
-
-impl<F, N, O> MapChainLink<ByMapNodeKind<N>> for F
-where
-    F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
-    N: ObjectCore,
-    O: IntoMutateResult,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        value
-            .downcast_ref::<N>()
-            .map(|node| self(node, def_region_kind).into_mutate_result())
-    }
-}
-
-#[doc(hidden)]
-pub enum ByMapCatchAll {}
-
-impl<F, O> MapChainLink<ByMapCatchAll> for F
-where
-    F: for<'a> FnMut(AnyView<'a>) -> O,
-    O: IntoMutateResult,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        _def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        Some(self(value).into_mutate_result())
-    }
-}
-
-#[doc(hidden)]
-pub enum ByMapCatchAllKind {}
-
-impl<F, O> MapChainLink<ByMapCatchAllKind> for F
-where
-    F: for<'a> FnMut(AnyView<'a>, DefRegionKind) -> O,
-    O: IntoMutateResult,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        Some(self(value, def_region_kind).into_mutate_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByMapChainLink<Markers>(PhantomData<fn(Markers)>);
-
-#[doc(hidden)]
-pub enum ByMapDispatchLink {}
-
-impl<V> MapChainLink<ByMapDispatchLink> for &mut V
-where
-    for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>,
-{
-    #[inline(always)]
-    fn try_map(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<Any>> {
-        self.dispatch_tuple((value, def_region_kind))
-    }
-}
-
-/// Adapter from a [`MapChainLink`] to [`Dispatcher`].
-#[doc(hidden)]
-pub struct MapChain<Link, Marker> {
-    link: Link,
-    marker: PhantomData<fn(Marker)>,
-}
-
-impl<Link, Marker> MapChain<Link, Marker> {
+impl<O: IntoMutateResult> super::dispatch::CallbackResult<Result<Any>> for O {
     #[inline(always)]
-    fn new(link: Link) -> Self {
-        MapChain {
-            link,
-            marker: PhantomData,
-        }
-    }
-}
-
-impl<'receiver, 'value, Link, Marker>
-    Dispatcher<(AnyView<'value>, DefRegionKind), Option<Result<Any>>>
-    for &'receiver mut MapChain<Link, Marker>
-where
-    Link: MapChainLink<Marker>,
-{
-    #[inline(always)]
-    fn dispatch_tuple(
-        self,
-        (value, def_region_kind): (AnyView<'value>, DefRegionKind),
-    ) -> Option<Result<Any>> {
-        self.link.try_map(value, def_region_kind)
-    }
-}
-
-macro_rules! impl_map_chain_link {
-    ($(($F:ident, $M:ident, $idx:tt)),+) => {
-        impl<$($F, $M,)+> sealed_map::SealedMapLink<ByMapChainLink<($($M,)+)>> 
for ($($F,)+)
-        where
-            $($F: MapChainLink<$M>,)+
-        {
-        }
-
-        impl<$($F, $M,)+> MapChainLink<ByMapChainLink<($($M,)+)>> for ($($F,)+)
-        where
-            $($F: MapChainLink<$M>,)+
-        {
-            #[inline(always)]
-            fn try_map(
-                &mut self,
-                value: AnyView<'_>,
-                def_region_kind: DefRegionKind,
-            ) -> Option<Result<Any>> {
-                $(
-                    if let Some(result) = self.$idx.try_map(value, 
def_region_kind) {
-                        return Some(result);
-                    }
-                )+
-                None
-            }
-        }
-
-        impl<$($F, $M,)+> IntoMapper<($($M,)+)> for ($($F,)+)
-        where
-            $($F: MapChainLink<$M>,)+
-        {
-            type Mapper = MapChain<($($F,)+), ByMapChainLink<($($M,)+)>>;
-
-            #[inline(always)]
-            fn into_mapper(self) -> Self::Mapper {
-                MapChain::new(self)
-            }
-
-            #[inline(always)]
-            fn map_root(self, root: Any, order: WalkOrder) -> Result<Any> {
-                run_mapper::<Self::Mapper>(root, &mut self.into_mapper(), 
order)
-            }
-        }
-    };
-}
-
-impl_callback_chain_tuple_arities!(impl_map_chain_link);
-
-macro_rules! impl_bare_map_link {
-    ($(($marker:ident, $($fn_args:ty),+)),+ $(,)?) => {
-        $(
-            impl<F, T, O> IntoMapper<$marker<T>> for F
-            where
-                F: FnMut($($fn_args),+) -> O,
-                Self: MapChainLink<$marker<T>>,
-                O: IntoMutateResult,
-            {
-                type Mapper = MapChain<F, $marker<T>>;
-
-                #[inline(always)]
-                fn into_mapper(self) -> Self::Mapper {
-                    MapChain::new(self)
-                }
-
-                #[inline(always)]
-                fn map_root(self, root: Any, order: WalkOrder) -> Result<Any> {
-                    run_mapper::<Self::Mapper>(root, &mut self.into_mapper(), 
order)
-                }
-            }
-        )+
-    };
-}
-
-impl_bare_map_link!(
-    (ByMapOwned, T),
-    (ByMapOwnedKind, T, DefRegionKind),
-    (ByMapNode, &T),
-    (ByMapNodeKind, &T, DefRegionKind),
-);
-
-impl<F, O> IntoMapper<ByMapCatchAll> for F
-where
-    F: for<'a> FnMut(AnyView<'a>) -> O,
-    O: IntoMutateResult,
-{
-    type Mapper = MapChain<F, ByMapCatchAll>;
-
-    #[inline(always)]
-    fn into_mapper(self) -> Self::Mapper {
-        MapChain::new(self)
-    }
-
-    #[inline(always)]
-    fn map_root(self, root: Any, order: WalkOrder) -> Result<Any> {
-        run_mapper::<Self::Mapper>(root, &mut self.into_mapper(), order)
-    }
-}
-
-impl<F, O> IntoMapper<ByMapCatchAllKind> for F
-where
-    F: for<'a> FnMut(AnyView<'a>, DefRegionKind) -> O,
-    O: IntoMutateResult,
-{
-    type Mapper = MapChain<F, ByMapCatchAllKind>;
-
-    #[inline(always)]
-    fn into_mapper(self) -> Self::Mapper {
-        MapChain::new(self)
-    }
-
-    #[inline(always)]
-    fn map_root(self, root: Any, order: WalkOrder) -> Result<Any> {
-        run_mapper::<Self::Mapper>(root, &mut self.into_mapper(), order)
+    fn callback_result(self) -> Result<Any> {
+        self.into_mutate_result()
     }
 }
 
@@ -547,7 +167,7 @@ where
 /// * `root` - Owned value to transform, convertible into an [`Any`]. Clone it
 ///   first to preserve the original through copy-on-write behavior.
 /// * `mapper` - Mutable dispatcher, closure, or first-match callback tuple
-///   implementing [`IntoMapper`]. Callbacks return replacements; unmatched
+///   implementing [`IntoDispatcher`]. Callbacks return replacements; unmatched
 ///   values retain their value while default traversal maps their children.
 /// * `order` - [`WalkOrder::PreOrder`] applies the callback before traversing 
the
 ///   replacement's children; [`WalkOrder::PostOrder`] maps children before
@@ -567,10 +187,12 @@ where
 /// assert_eq!(root.iter().collect::<Vec<_>>(), vec![1, 2, 3]);
 /// # Ok::<(), tvm_ffi::Error>(())
 /// ```
-pub fn structural_map<R, M, H>(root: R, mapper: H, order: WalkOrder) -> 
Result<Any>
+pub fn structural_map<R, M, H, D, O>(root: R, mapper: H, order: WalkOrder) -> 
Result<Any>
 where
     R: Into<Any>,
-    H: IntoMapper<M>,
+    O: BorrowMut<D>,
+    for<'v> H: IntoDispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>, M, Target = D, Output = O>,
+    for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>,
 {
-    mapper.map_root(root.into(), order)
+    run_mapper(root.into(), mapper.into_dispatcher().borrow_mut(), order)
 }
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 8317dbfc..4d77a081 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -44,12 +44,13 @@
 // Local Perf note comments explain individual implementation choices.
 
 use std::cell::{RefCell, UnsafeCell};
-use std::collections::{hash_map::Entry, HashMap};
+use std::collections::{HashMap, hash_map::Entry};
 use std::ffi::c_void;
 use std::marker::PhantomData;
 
 use super::cold_init_lazy_lock::ColdInitLazyLock;
-use super::marker::{Borrowed, Owned};
+use super::dispatch::IntoDispatcher;
+use std::borrow::BorrowMut;
 use std::ops::ControlFlow;
 use std::ptr::NonNull;
 use std::rc::Rc;
@@ -57,7 +58,7 @@ use std::rc::Rc;
 pub use super::structural_map::*;
 
 use super::dispatch::Dispatcher;
-use crate::any::{is_plain_inline, Any, AnyView};
+use crate::any::{Any, AnyView, is_plain_inline};
 use crate::error::{Error, Result, TYPE_ERROR};
 use crate::function::Function;
 use crate::object::{self, Object, ObjectArc, ObjectCore};
@@ -78,7 +79,7 @@ use super::structural_common::{
     with_structural_field_error_context, with_visit_error_context,
 };
 use super::structural_visit::{
-    field_def_region, for_each_field_info, free_var_child_region, 
DefRegionKind,
+    DefRegionKind, field_def_region, for_each_field_info, 
free_var_child_region,
 };
 use super::unchanged::is_unchanged;
 pub use super::unchanged::{Unchanged, UnchangedOr};
@@ -146,7 +147,7 @@ impl StructuralMutatorObj {
 /// to retain the runtime. Hooks may access the runtime and borrowed mutation
 /// state only synchronously, on the calling thread.
 ///
-/// `default_mutate` returns one owning ABI replacement/Unchanged/Error and 
recurses
+/// `default_mutate_raw` returns one owning ABI replacement/Unchanged/Error 
and recurses
 /// only through the installed vtable, preserving the input's mutation 
permission.
 /// No whole-base mutable reference may span recursion. Failures use the result
 /// carrier; panics must not cross native callbacks.
@@ -164,7 +165,7 @@ pub unsafe trait StructuralMutatorBase: Sized {
     /// matching installed vtable.
     /// The input and runtime remain borrowed for this synchronous call. The
     /// caller has published the current exclusive dispatcher reborrow.
-    unsafe fn default_mutate(runtime: *mut Self, value: MutationView<'_>) -> 
TVMFFIAny;
+    unsafe fn default_mutate_raw(runtime: *mut Self, value: MutationView<'_>) 
-> TVMFFIAny;
 }
 
 // SAFETY: this prefix exactly matches the ordinary native mutator. Its mutable
@@ -191,7 +192,7 @@ unsafe impl StructuralMutatorBase for StructuralMutatorObj {
     }
 
     #[inline(always)]
-    unsafe fn default_mutate(runtime: *mut Self, value: MutationView<'_>) -> 
TVMFFIAny {
+    unsafe fn default_mutate_raw(runtime: *mut Self, value: MutationView<'_>) 
-> TVMFFIAny {
         // This implementation is entered only by the ordinary Rust engine, so
         // reflection can access its remap directly. Prefix-only callers use
         // `default_mutate_raw`, which dispatches remaps through the native 
vtable.
@@ -201,7 +202,7 @@ unsafe impl StructuralMutatorBase for StructuralMutatorObj {
 
 /// Scoped Rust mutator runtime with a statically selected native base.
 ///
-/// Enter through [`IntoMutator::mutate_root`]. The engine owns the complete 
base,
+/// Enter through [`StructuralMutateEngine::mutate_root`]. The engine owns the 
complete base,
 /// dispatcher reborrow handle and identity remaps. The dispatcher stays in the
 /// outer call; callbacks receive only a scoped [`StructuralMutatorView`].
 #[repr(C)]
@@ -463,6 +464,19 @@ impl<'a> From<&'a mut Any> for MutationView<'a> {
 }
 
 impl<'a, T> MutationView<'a, T> {
+    // A chain may lend its capability to one link at a time. The shorter 
lifetime
+    // prevents a matched link from retaining a borrow across the next link.
+    #[inline(always)]
+    pub(crate) fn reborrow(&mut self) -> MutationView<'_, T> {
+        MutationView {
+            value: self.value,
+            mode: self.mode,
+            _scope: PhantomData,
+            _type: PhantomData,
+            _not_send_sync: PhantomData,
+        }
+    }
+
     /// Permission carried independently of temporary owning aliases.
     #[inline(always)]
     pub fn inplace_mode(&self) -> InplaceMode {
@@ -705,35 +719,30 @@ impl<D, Base: StructuralMutatorBase> 
StructuralMutatorView<'_, D, Base> {
     }
 }
 
-/// Conversion into a dispatcher-driven mutation. Closures belong to 
structural_map.
-pub trait IntoMutator<Marker, Base = StructuralMutatorObj> {
-    #[doc(hidden)]
-    fn mutate_root(self, root: Any) -> Result<Any>;
-}
-
-impl<D, Base: StructuralMutatorBase> IntoMutator<Owned, Base> for D
-where
-    for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
-        (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-        Option<Result<Any>>,
-    >,
-{
-    #[inline(always)]
-    fn mutate_root(mut self, root: Any) -> Result<Any> {
-        StructuralMutateEngine::<Base>::run_mutator::<D, true>(root, &mut self)
-    }
-}
-
-impl<D, Base: StructuralMutatorBase> IntoMutator<Borrowed, Base> for &mut D
-where
-    for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
-        (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-        Option<Result<Any>>,
-    >,
-{
+impl<Base: StructuralMutatorBase> StructuralMutateEngine<Base> {
+    /// Normalize the input once and traverse using this engine's native base.
+    /// Named dispatchers keep their actual recursive view type. Callback and
+    /// tuple targets are their aggregate adapters; closures taking their own
+    /// recursive view type remain unsupported.
     #[inline(always)]
-    fn mutate_root(self, root: Any) -> Result<Any> {
-        StructuralMutateEngine::<Base>::run_mutator::<D, true>(root, self)
+    pub fn mutate_root<R, H, M, D, O>(root: R, dispatcher: H) -> Result<Any>
+    where
+        R: Into<Any>,
+        O: BorrowMut<D>,
+        for<'v, 'c, 'r> H: IntoDispatcher<
+                (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
+                Option<Result<Any>>,
+                M,
+                Target = D,
+                Output = O,
+            >,
+        for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+                (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
+                Option<Result<Any>>,
+            >,
+    {
+        let mut storage = dispatcher.into_dispatcher();
+        Self::run_mutator::<D, true>(root.into(), storage.borrow_mut())
     }
 }
 
@@ -1056,9 +1065,9 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
     fn vtable<D, const CALLBACK_CONTEXT: bool>() -> &'static 
StructuralMutatorVTable
     where
         for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
-            (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-            Option<Result<Any>>,
-        >,
+                (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
+                Option<Result<Any>>,
+            >,
     {
         &StructuralMutatorVTable {
             mutate: Self::rust_vtable_mutate::<D, false, CALLBACK_CONTEXT>,
@@ -1081,9 +1090,9 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
     ) -> TVMFFIAny
     where
         for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
-            (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-            Option<Result<Any>>,
-        >,
+                (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
+                Option<Result<Any>>,
+            >,
     {
         let context = (*Self::from_handle(runtime)).dispatcher;
         let raw = *value.as_raw_ffi_any();
@@ -1134,7 +1143,7 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
         mode: InplaceMode,
     ) -> TVMFFIAny {
         (*Self::from_handle(runtime)).dispatcher = context;
-        Base::default_mutate(
+        <Base as StructuralMutatorBase>::default_mutate_raw(
             runtime.cast(),
             MutationView::new(AnyView::from_raw_ffi_any(raw), mode),
         )
@@ -1216,9 +1225,9 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
     ) -> Result<Any>
     where
         for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
-            (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-            Option<Result<Any>>,
-        >,
+                (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
+                Option<Result<Any>>,
+            >,
     {
         let mut base = Base::new()?;
         let type_index = Base::type_index();
@@ -1812,7 +1821,7 @@ impl StructuralMutatorObj {
 ///
 /// * `root` - Owned value to mutate, convertible into an [`Any`]. A uniquely
 ///   owned container may be reused in place; clone first to preserve the 
original.
-/// * `mutator` - Owned or mutably borrowed dispatcher implementing 
[`IntoMutator`].
+/// * `dispatcher` - Owned or mutably borrowed dispatcher implementing 
[`IntoDispatcher`].
 ///   Matching handlers return replacements and control recursion through
 ///   [`StructuralMutatorView`]; unmatched values use default mutation. For 
closure
 ///   callbacks with automatic recursion, use [`structural_map`].
@@ -1846,9 +1855,27 @@ impl StructuralMutatorObj {
 /// # Ok(())
 /// # }
 /// ```
-pub fn structural_mutate<R, M>(root: R, mutator: impl IntoMutator<M>) -> 
Result<Any>
+pub fn structural_mutate<R, H, M, D, O>(root: R, dispatcher: H) -> Result<Any>
 where
     R: Into<Any>,
+    O: BorrowMut<D>,
+    for<'v, 'c, 'r> H: IntoDispatcher<
+            (
+                MutationView<'v>,
+                &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
+            ),
+            Option<Result<Any>>,
+            M,
+            Target = D,
+            Output = O,
+        >,
+    for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+            (
+                MutationView<'v>,
+                &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
+            ),
+            Option<Result<Any>>,
+        >,
 {
-    mutator.mutate_root(root.into())
+    StructuralMutateEngine::mutate_root(root, dispatcher)
 }
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs 
b/rust/tvm-ffi/src/extra/structural_visit.rs
index db7e3ed9..afde0d76 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -70,7 +70,8 @@
 use std::cell::UnsafeCell;
 use std::marker::PhantomData;
 
-use super::marker::{Borrowed, Owned};
+use super::dispatch::IntoDispatcher;
+use std::borrow::BorrowMut;
 use std::ops::ControlFlow;
 use std::os::raw::c_void;
 use std::rc::Rc;
@@ -167,7 +168,7 @@ impl StructuralVisitorObj {
 /// to retain the runtime. Hooks may access the runtime and borrowed traversal
 /// state only synchronously, on the calling thread.
 ///
-/// `default_visit` returns one owning ABI None/Error/VisitInterrupt and 
recurses only
+/// `default_visit_raw` returns one owning ABI None/Error/VisitInterrupt and 
recurses only
 /// through the installed vtable, without a whole-base mutable reference 
spanning
 /// recursion. Failures use that result carrier; panics must not cross native 
callbacks.
 pub unsafe trait StructuralVisitorBase: Sized {
@@ -178,12 +179,15 @@ pub unsafe trait StructuralVisitorBase: Sized {
     fn type_index() -> i32;
 
     /// Descend from a borrowed value without dispatching that value again.
+    /// Return one owning ABI None, Error, or VisitInterrupt value.
+    /// Children reenter the installed vtable. The ordinary implementation 
accesses
+    /// only the visitor prefix, so specialized bases may reuse it.
     ///
     /// # Safety
     /// `runtime` is this live initialized base with a matching installed 
vtable.
     /// The input and runtime remain borrowed for this synchronous call. The
     /// caller has published the current exclusive dispatcher reborrow.
-    unsafe fn default_visit(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny;
+    unsafe fn default_visit_raw(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny;
 }
 
 // SAFETY: this prefix exactly matches the ordinary native visitor. Its mutable
@@ -211,30 +215,20 @@ unsafe impl StructuralVisitorBase for 
StructuralVisitorObj {
     }
 
     #[inline(always)]
-    unsafe fn default_visit(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny {
-        Self::default_visit_raw(runtime, value)
+    unsafe fn default_visit_raw(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny {
+        Self::visit_children_raw(runtime, *value.as_raw_ffi_any())
     }
 }
 
 /// Scoped Rust visitor runtime with a statically selected native base.
 ///
-/// Enter through [`IntoVisitor::visit_root`]. The engine owns the complete 
base
+/// Enter through [`StructuralVisitEngine::visit_root`]. The engine owns the 
complete base
 /// and the dispatcher reborrow handle, while the dispatcher stays in the outer
 /// call. Engines and their owning handles are never exposed to callbacks.
 ///
-/// A downstream layer selects its base in a thin entry point; its handlers use
-/// `StructuralVisitorView<'_, D, Base>` for recursion and base-state 
inspection:
-///
-/// ```
-/// use tvm_ffi::{AnyView, IntoVisitor, Result, StructuralVisitorBase, 
VisitInterrupt};
-///
-/// fn visit_with_base<'a, Base: StructuralVisitorBase, Marker>(
-///     root: impl Into<AnyView<'a>>,
-///     visitor: impl IntoVisitor<Marker, Base>,
-/// ) -> Result<Option<VisitInterrupt>> {
-///     visitor.visit_root(root.into())
-/// }
-/// ```
+/// Select a custom base with `StructuralVisitEngine::<Base>::visit_root(root, 
input)`.
+/// Handlers use `StructuralVisitorView<'_, Target, Base>` for recursion and
+/// base-state inspection; normalization preserves the actual target's 
identity.
 #[repr(C)]
 pub struct StructuralVisitEngine<Base = StructuralVisitorObj> {
     base: Base,
@@ -411,45 +405,48 @@ impl<D, Base: StructuralVisitorBase> 
StructuralVisitorView<'_, D, Base> {
     }
 }
 
-/// Conversion into a dispatcher-driven visitor. Closures belong to 
structural_walk.
-pub trait IntoVisitor<Marker, Base = StructuralVisitorObj> {
-    #[doc(hidden)]
-    fn visit_root(self, root: AnyView<'_>) -> Result<Option<VisitInterrupt>>;
-}
-
-impl<D, Base: StructuralVisitorBase> IntoVisitor<Owned, Base> for D
-where
-    for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
-        (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
-        Option<Result<Option<VisitInterrupt>>>,
-    >,
-{
+impl<Base: StructuralVisitorBase> StructuralVisitEngine<Base> {
+    /// Normalize the input once and traverse using this engine's native base.
+    /// Named dispatchers keep their actual recursive view type. Callback and
+    /// tuple targets are their aggregate adapters; closures taking their own
+    /// recursive view type remain unsupported.
     #[inline(always)]
-    fn visit_root(mut self, root: AnyView<'_>) -> 
Result<Option<VisitInterrupt>> {
+    pub fn visit_root<'input, R, H, M, D, O>(
+        root: R,
+        dispatcher: H,
+    ) -> Result<Option<VisitInterrupt>>
+    where
+        R: Into<AnyView<'input>>,
+        O: BorrowMut<D>,
+        for<'v, 'c, 'r> H: IntoDispatcher<
+                (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+                Option<Result<Option<VisitInterrupt>>>,
+                M,
+                Target = D,
+                Output = O,
+            >,
+        for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+                (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+                Option<Result<Option<VisitInterrupt>>>,
+            >,
+    {
+        let mut storage = dispatcher.into_dispatcher();
+        let root = root.into();
         visit_result_from_any(unsafe {
-            
Any::from_raw_ffi_any(StructuralVisitEngine::<Base>::run_visitor_raw::<D>(
+            Any::from_raw_ffi_any(Self::run_visitor_raw::<D>(
                 *root.as_raw_ffi_any(),
-                &mut self,
+                storage.borrow_mut(),
             ))
         })
     }
 }
 
-impl<D, Base: StructuralVisitorBase> IntoVisitor<Borrowed, Base> for &mut D
-where
-    for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
-        (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
-        Option<Result<Option<VisitInterrupt>>>,
-    >,
+impl super::dispatch::CallbackResult<Result<Option<VisitInterrupt>>>
+    for Result<Option<VisitInterrupt>>
 {
     #[inline(always)]
-    fn visit_root(self, root: AnyView<'_>) -> Result<Option<VisitInterrupt>> {
-        visit_result_from_any(unsafe {
-            
Any::from_raw_ffi_any(StructuralVisitEngine::<Base>::run_visitor_raw::<D>(
-                *root.as_raw_ffi_any(),
-                self,
-            ))
-        })
+    fn callback_result(self) -> Result<Option<VisitInterrupt>> {
+        self
     }
 }
 
@@ -659,20 +656,6 @@ unsafe fn visit_field_level<B>(
 // value. Forward it exactly once or import it into Any before any operation
 // that can unwind or discard it, including destruction of an observer.
 impl StructuralVisitorObj {
-    /// Ordinary registered-hook or reflected-field descent through a visitor 
prefix.
-    /// Children reenter the installed vtable, including a specialized base's 
vtable.
-    /// Returns one owning None, Error or VisitInterrupt ABI value.
-    ///
-    /// # Safety
-    /// The prefix and input must remain valid for synchronous descent. The 
caller
-    /// must publish the current dispatcher reborrow and uphold the native-hook
-    /// lifetime, layout and ownership contract of [`StructuralVisitorBase`]. 
This
-    /// function accesses only the prefix, never a Rust engine's dispatcher 
slot.
-    #[inline(always)]
-    pub unsafe fn default_visit_raw(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny {
-        Self::visit_children_raw(runtime, *value.as_raw_ffi_any())
-    }
-
     /// Consume owning `result` from descent, adding borrowed `value` context 
on Error.
     /// Internal callers supply valid tokens and keep the input live during 
annotation;
     /// nonerrors transfer unchanged, and Error ownership is guarded through 
formatting.
@@ -1010,9 +993,9 @@ impl<Base: StructuralVisitorBase> 
StructuralVisitEngine<Base> {
     fn vtable<D>() -> &'static StructuralVisitorVTable
     where
         for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
-            (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
-            Option<Result<Option<VisitInterrupt>>>,
-        >,
+                (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+                Option<Result<Option<VisitInterrupt>>>,
+            >,
     {
         &StructuralVisitorVTable {
             visit: Self::rust_vtable_visit_raw::<D>,
@@ -1032,9 +1015,9 @@ impl<Base: StructuralVisitorBase> 
StructuralVisitEngine<Base> {
     ) -> TVMFFIAny
     where
         for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
-            (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
-            Option<Result<Option<VisitInterrupt>>>,
-        >,
+                (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+                Option<Result<Option<VisitInterrupt>>>,
+            >,
     {
         let context = (*Self::from_handle(runtime)).dispatcher;
         let raw = *value.as_raw_ffi_any();
@@ -1091,7 +1074,7 @@ impl<Base: StructuralVisitorBase> 
StructuralVisitEngine<Base> {
         // the handle-only scanner never creates or uses a competing D 
reference.
         (*Self::from_handle(runtime)).dispatcher = context;
         StructuralVisitorObj::with_visit_context_raw(
-            Base::default_visit(runtime.cast(), 
AnyView::from_raw_ffi_any(raw)),
+            Base::default_visit_raw(runtime.cast(), 
AnyView::from_raw_ffi_any(raw)),
             raw,
         )
     }
@@ -1103,9 +1086,9 @@ impl<Base: StructuralVisitorBase> 
StructuralVisitEngine<Base> {
     pub(super) fn run_visitor_raw<D>(root: TVMFFIAny, dispatch: &mut D) -> 
TVMFFIAny
     where
         for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
-            (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
-            Option<Result<Option<VisitInterrupt>>>,
-        >,
+                (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+                Option<Result<Option<VisitInterrupt>>>,
+            >,
     {
         let base = match Base::new() {
             Ok(base) => base,
@@ -1156,7 +1139,7 @@ impl<Base: StructuralVisitorBase> 
StructuralVisitEngine<Base> {
 /// # Arguments
 ///
 /// * `root` - Value borrowed for traversal, convertible into an [`AnyView`].
-/// * `visitor` - Owned or mutably borrowed dispatcher implementing 
[`IntoVisitor`].
+/// * `dispatcher` - Owned or mutably borrowed dispatcher implementing 
[`IntoDispatcher`].
 ///   Matching handlers control recursion through [`StructuralVisitorView`];
 ///   unmatched values use default child traversal.
 ///
@@ -1192,14 +1175,32 @@ impl<Base: StructuralVisitorBase> 
StructuralVisitEngine<Base> {
 /// # Ok(())
 /// # }
 /// ```
-pub fn structural_visit<'input, R, M>(
+pub fn structural_visit<'input, R, H, M, D, O>(
     root: R,
-    visitor: impl IntoVisitor<M>,
+    dispatcher: H,
 ) -> Result<Option<VisitInterrupt>>
 where
     R: Into<AnyView<'input>>,
+    O: BorrowMut<D>,
+    for<'v, 'c, 'r> H: IntoDispatcher<
+            (
+                AnyView<'v>,
+                &'c mut StructuralVisitorView<'r, D, StructuralVisitorObj>,
+            ),
+            Option<Result<Option<VisitInterrupt>>>,
+            M,
+            Target = D,
+            Output = O,
+        >,
+    for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+            (
+                AnyView<'v>,
+                &'c mut StructuralVisitorView<'r, D, StructuralVisitorObj>,
+            ),
+            Option<Result<Option<VisitInterrupt>>>,
+        >,
 {
-    visitor.visit_root(root.into())
+    StructuralVisitEngine::visit_root(root, dispatcher)
 }
 
 #[inline(always)]
diff --git a/rust/tvm-ffi/src/extra/structural_walk.rs 
b/rust/tvm-ffi/src/extra/structural_walk.rs
index 08d0283c..a12a50a0 100644
--- a/rust/tvm-ffi/src/extra/structural_walk.rs
+++ b/rust/tvm-ffi/src/extra/structural_walk.rs
@@ -22,13 +22,12 @@
 //! Callbacks report failures with [`Result`] and must not panic. A panic 
reaching
 //! the non-unwinding native callback boundary aborts the process.
 
-use super::structural_common::impl_callback_chain_tuple_arities;
 use super::structural_visit::{
-    visit_result_from_any, DefRegionKind, StructuralVisitEngine, 
StructuralVisitorView,
-    VisitInterrupt,
+    DefRegionKind, StructuralVisitEngine, StructuralVisitorView, 
VisitInterrupt,
+    visit_result_from_any,
 };
-use crate::{Any, AnyView, Dispatcher, ObjectCore, Result};
-use std::marker::PhantomData;
+use crate::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
+use std::borrow::BorrowMut;
 
 // 
-----------------------------------------------------------------------------
 // Key data structures and callback results.
@@ -90,378 +89,13 @@ pub enum WalkOrder {
 // Walk adapter to Dispatcher.
 // 
-----------------------------------------------------------------------------
 
-// Borrowed traversal adapters forward this concrete tuple without making
-// unknown-Args trait solving recurse through mutable reference layers.
-impl<'receiver, 'inner, 'value, D, Ret> Dispatcher<(AnyView<'value>, 
DefRegionKind), Ret>
-    for &'receiver mut &'inner mut D
-where
-    &'receiver mut D: Dispatcher<(AnyView<'value>, DefRegionKind), Ret>,
-{
-    #[inline(always)]
-    fn dispatch_tuple(self, args: (AnyView<'value>, DefRegionKind)) -> Ret {
-        <&'receiver mut D as Dispatcher<(AnyView<'value>, DefRegionKind), 
Ret>>::dispatch_tuple(
-            &mut **self,
-            args,
-        )
-    }
-}
-
-#[doc(hidden)]
-pub enum ByWalkDispatch {}
-
-impl<'a, V> IntoWalker<ByWalkDispatch> for &'a mut V
-where
-    for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>,
-{
-    #[inline(always)]
-    fn walk_root(self, root: AnyView<'_>, order: WalkOrder) -> 
Result<Option<VisitInterrupt>> {
-        run_walker::<Self>(root, self, order)
-    }
-}
-
-/// Conversion into the walker argument of [`structural_walk`].
-///
-/// Accepts a mutable [`Dispatcher`], a typed callback or a nested callback
-/// tuple.
-/// `Marker` distinguishes the supported callback shapes.
-#[diagnostic::on_unimplemented(
-    message = "`{Self}` is not a supported `structural_walk` walker",
-    note = "accepted walkers: `&mut V` with the walk `Dispatcher` signature; a 
closure over `AnyView<'_>`, \
-            an FFI value type `T`, or `&N` of an object node type (`N: 
ObjectCore`, e.g. \
-            `&Object`), optionally with a trailing `DefRegionKind` argument; 
or a tuple of \
-            up to 12 such links (tuples nest, so `(a, (b, c))` chains more)",
-    note = "closure arguments need explicit type annotations; ObjectRef 
wrappers like `String` \
-            or `Array<T>` are FFI value types — take them by value, not by 
reference"
-)]
-pub trait IntoWalker<Marker> {
-    #[doc(hidden)]
-    fn walk_root(self, root: AnyView<'_>, order: WalkOrder) -> 
Result<Option<VisitInterrupt>>;
-}
-
-impl<F, O> IntoWalker<ByCatchAllLink> for F
-where
-    F: for<'a> FnMut(AnyView<'a>) -> O,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn walk_root(self, root: AnyView<'_>, order: WalkOrder) -> 
Result<Option<VisitInterrupt>> {
-        run_walker::<ChainWalker<F, ByCatchAllLink>>(root, 
ChainWalker::new(self), order)
-    }
-}
-
-impl<F, O> IntoWalker<ByCatchAllKindLink> for F
-where
-    F: for<'a> FnMut(AnyView<'a>, DefRegionKind) -> O,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn walk_root(self, root: AnyView<'_>, order: WalkOrder) -> 
Result<Option<VisitInterrupt>> {
-        run_walker::<ChainWalker<F, ByCatchAllKindLink>>(root, 
ChainWalker::new(self), order)
-    }
-}
-
-/// One link in a first-match [`structural_walk`] callback chain.
-///
-/// Supported links are typed values, borrowed object nodes, `AnyView<'_>`,
-/// and mutable [`Dispatcher`] implementations, optionally followed by
-/// [`DefRegionKind`]. Tuples hold up to 12 links and may be nested.
-pub trait WalkChainLink<Marker>: sealed::SealedLink<Marker> {
-    /// Run this link if `value` matches its argument type; `None` hands the
-    /// value to the next link.
-    #[doc(hidden)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>>;
-}
-
-mod sealed {
-    use super::{
-        AnyView, DefRegionKind, Dispatcher, IntoWalkResult, ObjectCore, 
Result, WalkResult,
-    };
-
-    pub trait SealedLink<Marker> {}
-
-    impl<F, T, O> SealedLink<super::ByOwnedLink<T>> for F
-    where
-        F: FnMut(T) -> O,
-        O: IntoWalkResult,
-    {
-    }
-    impl<F, T, O> SealedLink<super::ByOwnedKindLink<T>> for F
-    where
-        F: FnMut(T, DefRegionKind) -> O,
-        O: IntoWalkResult,
-    {
-    }
-    impl<F, N: ObjectCore, O> SealedLink<super::ByNodeLink<N>> for F
-    where
-        F: for<'a> FnMut(&'a N) -> O,
-        O: IntoWalkResult,
-    {
-    }
-    impl<F, N: ObjectCore, O> SealedLink<super::ByNodeKindLink<N>> for F
-    where
-        F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
-        O: IntoWalkResult,
-    {
-    }
-    impl<F, O> SealedLink<super::ByCatchAllLink> for F
-    where
-        F: for<'a> FnMut(AnyView<'a>) -> O,
-        O: IntoWalkResult,
-    {
-    }
-    impl<F, O> SealedLink<super::ByCatchAllKindLink> for F
-    where
-        F: for<'a> FnMut(AnyView<'a>, DefRegionKind) -> O,
-        O: IntoWalkResult,
-    {
-    }
-    impl<V> SealedLink<super::ByWalkDispatchLink> for &mut V where
-        for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>
-    {
-    }
-}
-
-#[doc(hidden)]
-pub struct ByOwnedLink<T>(PhantomData<T>);
-
-impl<F, T, O> WalkChainLink<ByOwnedLink<T>> for F
-where
-    F: FnMut(T) -> O,
-    T: crate::type_traits::AnyCompatible,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        _def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        value
-            .try_as::<T>()
-            .map(|typed| self(typed).into_walk_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByOwnedKindLink<T>(PhantomData<T>);
-
-impl<F, T, O> WalkChainLink<ByOwnedKindLink<T>> for F
-where
-    F: FnMut(T, DefRegionKind) -> O,
-    T: crate::type_traits::AnyCompatible,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        value
-            .try_as::<T>()
-            .map(|typed| self(typed, def_region_kind).into_walk_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByNodeLink<N>(PhantomData<N>);
-
-impl<F, N, O> WalkChainLink<ByNodeLink<N>> for F
-where
-    F: for<'a> FnMut(&'a N) -> O,
-    N: ObjectCore,
-    O: IntoWalkResult,
-{
+impl<O: IntoWalkResult> super::dispatch::CallbackResult<Result<WalkResult>> 
for O {
     #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        _def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        value
-            .downcast_ref::<N>()
-            .map(|node| self(node).into_walk_result())
+    fn callback_result(self) -> Result<WalkResult> {
+        self.into_walk_result()
     }
 }
 
-#[doc(hidden)]
-pub struct ByNodeKindLink<N>(PhantomData<N>);
-
-impl<F, N, O> WalkChainLink<ByNodeKindLink<N>> for F
-where
-    F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
-    N: ObjectCore,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        value
-            .downcast_ref::<N>()
-            .map(|node| self(node, def_region_kind).into_walk_result())
-    }
-}
-
-#[doc(hidden)]
-pub enum ByCatchAllLink {}
-
-impl<F, O> WalkChainLink<ByCatchAllLink> for F
-where
-    F: for<'a> FnMut(AnyView<'a>) -> O,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        _def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        Some(self(value).into_walk_result())
-    }
-}
-
-#[doc(hidden)]
-pub enum ByCatchAllKindLink {}
-
-impl<F, O> WalkChainLink<ByCatchAllKindLink> for F
-where
-    F: for<'a> FnMut(AnyView<'a>, DefRegionKind) -> O,
-    O: IntoWalkResult,
-{
-    #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        Some(self(value, def_region_kind).into_walk_result())
-    }
-}
-
-#[doc(hidden)]
-pub struct ByChainLink<Markers>(PhantomData<fn(Markers)>);
-
-#[doc(hidden)]
-pub enum ByWalkDispatchLink {}
-
-impl<V> WalkChainLink<ByWalkDispatchLink> for &mut V
-where
-    for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>,
-{
-    #[inline(always)]
-    fn try_call(
-        &mut self,
-        value: AnyView<'_>,
-        def_region_kind: DefRegionKind,
-    ) -> Option<Result<WalkResult>> {
-        self.dispatch_tuple((value, def_region_kind))
-    }
-}
-
-/// Adapter from a [`WalkChainLink`] to the native traversal callback.
-#[doc(hidden)]
-pub struct ChainWalker<Link, Marker> {
-    link: Link,
-    marker: PhantomData<fn(Marker)>,
-}
-
-impl<Link, Marker> ChainWalker<Link, Marker> {
-    #[inline(always)]
-    fn new(link: Link) -> Self {
-        ChainWalker {
-            link,
-            marker: PhantomData,
-        }
-    }
-}
-
-impl<'s, 'v, Link, Marker> Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>
-    for &'s mut ChainWalker<Link, Marker>
-where
-    Link: WalkChainLink<Marker>,
-{
-    #[inline(always)]
-    fn dispatch_tuple(
-        self,
-        (value, def_region_kind): (AnyView<'v>, DefRegionKind),
-    ) -> Option<Result<WalkResult>> {
-        self.link.try_call(value, def_region_kind)
-    }
-}
-
-macro_rules! impl_chain_link {
-    ($(($F:ident, $M:ident, $idx:tt)),+) => {
-        impl<$($F, $M,)+> sealed::SealedLink<ByChainLink<($($M,)+)>> for 
($($F,)+)
-        where
-            $($F: WalkChainLink<$M>,)+
-        {
-        }
-
-        impl<$($F, $M,)+> WalkChainLink<ByChainLink<($($M,)+)>> for ($($F,)+)
-        where
-            $($F: WalkChainLink<$M>,)+
-        {
-            #[inline(always)]
-            fn try_call(
-                &mut self,
-                value: AnyView<'_>,
-                def_region_kind: DefRegionKind,
-            ) -> Option<Result<WalkResult>> {
-                $(
-                    if let Some(result) = self.$idx.try_call(value, 
def_region_kind) {
-                        return Some(result);
-                    }
-                )+
-                None
-            }
-        }
-
-        impl<$($F, $M,)+> IntoWalker<($($M,)+)> for ($($F,)+)
-        where
-            $($F: WalkChainLink<$M>,)+
-        {
-            #[inline(always)]
-            fn walk_root(self, root: AnyView<'_>, order: WalkOrder) -> 
Result<Option<VisitInterrupt>> {
-                run_walker::<ChainWalker<($($F,)+), 
ByChainLink<($($M,)+)>>>(root, ChainWalker::new(self), order)
-            }
-        }
-    };
-}
-
-impl_callback_chain_tuple_arities!(impl_chain_link);
-
-macro_rules! impl_bare_link_walker {
-    ($(($marker:ident, $($fn_args:ty),+)),+ $(,)?) => {
-        $(
-            impl<F, T, O> IntoWalker<$marker<T>> for F
-            where
-                F: FnMut($($fn_args),+) -> O,
-                Self: WalkChainLink<$marker<T>>,
-                O: IntoWalkResult,
-            {
-                #[inline(always)]
-                fn walk_root(self, root: AnyView<'_>, order: WalkOrder) -> 
Result<Option<VisitInterrupt>> {
-                    run_walker::<ChainWalker<F, $marker<T>>>(root, 
ChainWalker::new(self), order)
-                }
-            }
-        )+
-    };
-}
-
-impl_bare_link_walker!(
-    (ByOwnedLink, T),
-    (ByOwnedKindLink, T, DefRegionKind),
-    (ByNodeLink, &T),
-    (ByNodeKindLink, &T, DefRegionKind),
-);
-
 // 
-----------------------------------------------------------------------------
 // Implementation of walk.
 // 
-----------------------------------------------------------------------------
@@ -469,36 +103,36 @@ impl_bare_link_walker!(
 // A walk is an ordinary visitor handler: the visitor core owns all child
 // recursion, native hooks and region transitions. This adapter only chooses
 // when the observer runs and translates its control result.
-struct WalkVisitor<V, const PRE_ORDER: bool> {
-    observer: V,
+struct WalkVisitor<'a, D, const PRE_ORDER: bool> {
+    observer: &'a mut D,
 }
 
-impl<'s, 'v, 'c, 'r, V, const PRE_ORDER: bool>
+impl<'s, 'v, 'c, 'r, 'a, D, const PRE_ORDER: bool>
     Dispatcher<
         (
             AnyView<'v>,
-            &'c mut StructuralVisitorView<'r, WalkVisitor<V, PRE_ORDER>>,
+            &'c mut StructuralVisitorView<'r, WalkVisitor<'a, D, PRE_ORDER>>,
         ),
         Option<Result<Option<VisitInterrupt>>>,
-    > for &'s mut WalkVisitor<V, PRE_ORDER>
+    > for &'s mut WalkVisitor<'a, D, PRE_ORDER>
 where
-    for<'a, 'b> &'a mut V: Dispatcher<(AnyView<'b>, DefRegionKind), 
Option<Result<WalkResult>>>,
+    for<'d, 'b> &'d mut D: Dispatcher<(AnyView<'b>, DefRegionKind), 
Option<Result<WalkResult>>>,
 {
     #[inline(always)]
     fn dispatch_tuple(
         self,
         (value, visitor): (
             AnyView<'v>,
-            &'c mut StructuralVisitorView<'r, WalkVisitor<V, PRE_ORDER>>,
+            &'c mut StructuralVisitorView<'r, WalkVisitor<'a, D, PRE_ORDER>>,
         ),
     ) -> Option<Result<Option<VisitInterrupt>>> {
         Some(self.visit(value, visitor))
     }
 }
 
-impl<V, const PRE_ORDER: bool> WalkVisitor<V, PRE_ORDER>
+impl<'a, D, const PRE_ORDER: bool> WalkVisitor<'a, D, PRE_ORDER>
 where
-    for<'s, 'v> &'s mut V: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>,
+    for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>,
 {
     #[inline(always)]
     fn visit(
@@ -513,7 +147,7 @@ where
                 return Ok(Some(interrupt));
             }
         }
-        match (&mut self.observer)
+        match (&mut *self.observer)
             .dispatch_tuple((value, kind))
             .unwrap_or_else(|| Ok(WalkResult::Advance))?
         {
@@ -536,7 +170,11 @@ where
 }
 
 #[inline(always)]
-fn run_walker<D>(root: AnyView<'_>, observer: D, order: WalkOrder) -> 
Result<Option<VisitInterrupt>>
+fn run_walker<D>(
+    root: AnyView<'_>,
+    observer: &mut D,
+    order: WalkOrder,
+) -> Result<Option<VisitInterrupt>>
 where
     for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>,
 {
@@ -546,16 +184,20 @@ where
     let result = match order {
         WalkOrder::PreOrder => {
             let mut visitor = WalkVisitor::<D, true> { observer };
-            let raw =
-                StructuralVisitEngine::run_visitor_raw::<WalkVisitor<D, 
true>>(root, &mut visitor);
+            let raw = StructuralVisitEngine::run_visitor_raw::<WalkVisitor<'_, 
D, true>>(
+                root,
+                &mut visitor,
+            );
             let result = unsafe { Any::from_raw_ffi_any(raw) };
             drop(visitor);
             result
         }
         WalkOrder::PostOrder => {
             let mut visitor = WalkVisitor::<D, false> { observer };
-            let raw =
-                StructuralVisitEngine::run_visitor_raw::<WalkVisitor<D, 
false>>(root, &mut visitor);
+            let raw = StructuralVisitEngine::run_visitor_raw::<WalkVisitor<'_, 
D, false>>(
+                root,
+                &mut visitor,
+            );
             let result = unsafe { Any::from_raw_ffi_any(raw) };
             drop(visitor);
             result
@@ -567,9 +209,9 @@ where
 /// Walk `root` with an observer, the Rust analog of C++
 /// `StructuralWalk<order>(root, callbacks...)`.
 ///
-/// `walker` is anything implementing [`IntoWalker`]: a `&mut` reference to a
+/// `walker` is anything implementing [`IntoDispatcher`]: a `&mut` reference 
to a
 /// stateful [`Dispatcher`] walker (declared with `#[dispatch]`), a bare 
closure
-/// in any [`WalkChainLink`] shape (catch-all `AnyView<'_>`, typed, or node,
+/// in any [`crate::IntoDispatcher`] callback shape (catch-all `AnyView<'_>`, 
typed, or node,
 /// with an optional trailing [`DefRegionKind`]), or a tuple of such
 /// callbacks tried in order — the C++ callback overloads and variadic
 /// chain.
@@ -598,7 +240,7 @@ where
 ///
 /// * `root` - Value borrowed for traversal, convertible into an [`AnyView`].
 /// * `walker` - Mutable dispatcher, closure, or first-match callback tuple
-///   implementing [`IntoWalker`]. Each callback observes a value and returns a
+///   implementing [`IntoDispatcher`]. Each callback observes a value and 
returns a
 ///   result convertible through [`IntoWalkResult`] to control traversal.
 /// * `order` - [`WalkOrder::PreOrder`] observes each value before its 
children;
 ///   [`WalkOrder::PostOrder`] observes it after its children. Skipping 
children
@@ -620,14 +262,23 @@ where
 /// assert_eq!(integers, vec![1, 2, 3]);
 /// # Ok::<(), tvm_ffi::Error>(())
 /// ```
-pub fn structural_walk<'input, R, M, H>(
+pub fn structural_walk<'input, R, M, H, D, O>(
     root: R,
     walker: H,
     order: WalkOrder,
 ) -> Result<Option<VisitInterrupt>>
 where
-    H: IntoWalker<M>,
+    for<'v> H: IntoDispatcher<
+            (AnyView<'v>, DefRegionKind),
+            Option<Result<WalkResult>>,
+            M,
+            Target = D,
+            Output = O,
+        >,
+    for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<WalkResult>>>,
     R: Into<AnyView<'input>>,
+    O: BorrowMut<D>,
 {
-    walker.walk_root(root.into(), order)
+    let mut storage = walker.into_dispatcher();
+    run_walker(root.into(), storage.borrow_mut(), order)
 }
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index efecae42..6f022fdd 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -47,25 +47,23 @@ pub use crate::collections::shape::Shape;
 pub use crate::collections::tensor::{CPUNDAlloc, NDAllocator, Tensor, 
TensorView};
 pub use crate::device::{current_stream, with_stream};
 pub use crate::dtype::DLDataTypeExt;
-pub use crate::error::{Error, ErrorKind, Result};
 pub use crate::error::{
     ATTRIBUTE_ERROR, INDEX_ERROR, INTERNAL_ERROR, KEY_ERROR, OVERFLOW_ERROR, 
RUNTIME_ERROR,
     TYPE_ERROR, VALUE_ERROR, ZERO_DIVISION_ERROR,
 };
-pub use crate::extra::dispatch::Dispatcher;
+pub use crate::error::{Error, ErrorKind, Result};
+pub use crate::extra::dispatch::{Dispatcher, IntoDispatcher};
 pub use crate::extra::module::Module;
-pub use crate::extra::structural_map::{structural_map, IntoMapper, 
MapChainLink};
+pub use crate::extra::structural_map::structural_map;
 pub use crate::extra::structural_mutate::{
-    structural_mutate, InplaceMode, IntoMutator, MutationView, 
StructuralMutateEngine,
-    StructuralMutatorBase, StructuralMutatorObj, StructuralMutatorView, 
StructuralVarRemap,
+    InplaceMode, MutationView, StructuralMutateEngine, StructuralMutatorBase, 
StructuralMutatorObj,
+    StructuralMutatorView, StructuralVarRemap, structural_mutate,
 };
 pub use crate::extra::structural_visit::{
-    structural_visit, DefRegionKind, IntoVisitor, StructuralVisitEngine, 
StructuralVisitorBase,
-    StructuralVisitorObj, StructuralVisitorView, VisitInterrupt, 
VisitInterruptObj,
-};
-pub use crate::extra::structural_walk::{
-    structural_walk, IntoWalkResult, IntoWalker, WalkChainLink, WalkOrder, 
WalkResult,
+    DefRegionKind, StructuralVisitEngine, StructuralVisitorBase, 
StructuralVisitorObj,
+    StructuralVisitorView, VisitInterrupt, VisitInterruptObj, structural_visit,
 };
+pub use crate::extra::structural_walk::{IntoWalkResult, WalkOrder, WalkResult, 
structural_walk};
 pub use crate::extra::unchanged::{Unchanged, UnchangedOr};
 pub use crate::function::Function;
 pub use crate::object::ObjectRefCast;
@@ -74,8 +72,8 @@ pub use crate::object::{
 };
 pub use crate::optional::{Optional, OptionalCompatible};
 pub use crate::reflection::{
-    get_type_attr, FieldFlags, FieldGetter, ObjectDef, SEqHashKind, 
ShallowCopy, TypeAttrColumn,
-    UnsafeInit,
+    FieldFlags, FieldGetter, ObjectDef, SEqHashKind, ShallowCopy, 
TypeAttrColumn, UnsafeInit,
+    get_type_attr,
 };
 pub use crate::rvalue_ref::RValueRef;
 pub use crate::static_init::run_static_init_blocks;
diff --git a/rust/tvm-ffi/tests/test_dispatch.rs 
b/rust/tvm-ffi/tests/test_dispatch.rs
new file mode 100644
index 00000000..179c26ea
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_dispatch.rs
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use tvm_ffi::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
+
+#[test]
+fn into_dispatcher_supports_owned_borrowed_and_callbacks() -> Result<()> {
+    type Outcome = Option<Result<Any>>;
+    struct Add {
+        calls: usize,
+    }
+    impl<'v> Dispatcher<(AnyView<'v>, i64), Outcome> for &mut Add {
+        fn dispatch_tuple(self, (value, offset): (AnyView<'v>, i64)) -> 
Outcome {
+            value.try_as::<i64>().map(|value| {
+                self.calls += 1;
+                Ok(Any::from(value + offset))
+            })
+        }
+    }
+
+    // An owned dispatcher keeps its exact argument and result signature.
+    let mut owned =
+        IntoDispatcher::<(AnyView<'_>, i64), Outcome, _>::into_dispatcher(Add 
{ calls: 0 });
+    assert_eq!(
+        owned
+            .dispatch_tuple((AnyView::from(&40_i64), 2))
+            .unwrap()?
+            .try_as::<i64>(),
+        Some(42)
+    );
+
+    // A borrowed dispatcher updates the original instance.
+    let mut source = Add { calls: 0 };
+    {
+        let borrowed =
+            IntoDispatcher::<(AnyView<'_>, i64), Outcome, 
_>::into_dispatcher(&mut source);
+        assert_eq!(
+            borrowed
+                .dispatch_tuple((AnyView::from(&40_i64), 3))
+                .unwrap()?
+                .try_as::<i64>(),
+            Some(43)
+        );
+    }
+    assert_eq!(source.calls, 1);
+
+    // Typed misses fall through; matched callbacks receive only their prefix.
+    let callbacks = (
+        |_: bool| Any::from(-1_i64),
+        |value: i64, offset: i64| Any::from(value + offset),
+        |_: AnyView<'_>| -> Any { panic!("a matched callback must stop the 
tuple") },
+    );
+    let mut tuple =
+        IntoDispatcher::<(AnyView<'_>, i64, &str), Outcome, 
_>::into_dispatcher(callbacks);
+    assert_eq!(
+        tuple
+            .dispatch_tuple((AnyView::from(&40_i64), 4, "unused"))
+            .unwrap()?
+            .try_as::<i64>(),
+        Some(44)
+    );
+    assert_eq!(
+        tuple
+            .dispatch_tuple((AnyView::from(&true), 4, "unused"))
+            .unwrap()?
+            .try_as::<i64>(),
+        Some(-1)
+    );
+    Ok(())
+}
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index cfb9cef3..c623cdbb 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -24,7 +24,7 @@ use tvm_ffi::function::FunctionObj;
 use tvm_ffi::object::ObjectRef;
 use tvm_ffi::{
     dispatch, structural_map, structural_mutate, structural_visit, 
structural_walk, Any, AnyView,
-    Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function, 
InplaceMode, IntoMapper, Map,
+    Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function, 
InplaceMode, IntoDispatcher, Map,
     MutationView, Object, ObjectArc, ObjectRefCore, Result, String as 
FfiString,
     StructuralMutatorView, StructuralVarRemap, StructuralVisitorView, 
TypeIndex, Unchanged,
     UnchangedOr, VisitInterrupt, WalkOrder, WalkResult, RUNTIME_ERROR,
@@ -307,7 +307,7 @@ fn 
unique_array_is_reused_while_shared_array_uses_copy_on_write() {
 fn user_driven_mutator_controls_default_recursion_and_in_place_opt_in() {
     let unique = Array::new(vec![1i64, 2]);
     let unique_pointer = array_pointer(&unique);
-    let mutated = structural_mutate::<Array<i64>, _>(unique, 
ManualIncrement::default())
+    let mutated = structural_mutate::<Array<i64>, _, _, _, _>(unique, 
ManualIncrement::default())
         .and_then(Array::<i64>::try_from)
         .unwrap();
     assert_eq!(array_pointer(&mutated), unique_pointer);
@@ -1553,7 +1553,7 @@ fn pre_order_mapping_preserves_inplace_permission() {
         let alias = (case == "shared").then(|| root.clone());
         let mut retained = None;
         let mut pointer = std::ptr::null();
-        let mut mapper = (|value: AnyView<'_>| {
+        let callback = |value: AnyView<'_>| {
             if let Some(integer) = value.try_as::<i64>() {
                 return Any::from(integer + 1);
             }
@@ -1565,8 +1565,12 @@ fn pre_order_mapping_preserves_inplace_permission() {
             };
             pointer = any_object_pointer(&mapped);
             mapped
-        })
-        .into_mapper();
+        };
+        let mut mapper = IntoDispatcher::<
+            (AnyView<'_>, DefRegionKind),
+            Option<Result<Any>>,
+            _,
+        >::into_dispatcher(callback);
         let output = structural_map(root, &mut mapper, 
WalkOrder::PreOrder).unwrap();
         let reuse = case == "unique";
         assert_eq!(any_object_pointer(&output) == pointer, reuse, "{case}");
@@ -1750,7 +1754,11 @@ fn post_order_unchanged_preserves_descendant_rewrites() {
                 },
             );
             let mapped = if borrowed {
-                let mut mapper = callbacks.into_mapper();
+                let mut mapper = IntoDispatcher::<
+                    (AnyView<'_>, DefRegionKind),
+                    Option<Result<Any>>,
+                    _,
+                >::into_dispatcher(callbacks);
                 structural_map(root, &mut mapper, WalkOrder::PostOrder)
             } else {
                 structural_map(root, callbacks, WalkOrder::PostOrder)
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs 
b/rust/tvm-ffi/tests/test_structural_visit.rs
index 6d43ba7c..f7833062 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -1125,7 +1125,7 @@ fn 
visitor_interrupt_propagates_through_default_children() {
     }
 
     let root = Array::new(vec![1i64, 2, 3]);
-    let outcome = structural_visit::<&Array<i64>, _>(&root, &mut 
InterruptingVisitor).unwrap();
+    let outcome = structural_visit::<&Array<i64>, _, _, _, _>(&root, &mut 
InterruptingVisitor).unwrap();
     let Some(interrupt) = outcome else {
         panic!("visitor traversal unexpectedly completed");
     };
diff --git a/rust/tvm-ffi/tests/test_structural_visit_base.rs 
b/rust/tvm-ffi/tests/test_structural_visit_base.rs
index d385dad9..2babc299 100644
--- a/rust/tvm-ffi/tests/test_structural_visit_base.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit_base.rs
@@ -24,9 +24,9 @@ use tvm_ffi::tvm_ffi_sys::{
     TVMFFITypeMetadata, TVMFFITypeRegisterMetadata,
 };
 use tvm_ffi::{
-    dispatch, get_type_attr, structural_visit, Any, AnyView, Array, 
DefRegionKind, Dispatcher,
-    Error, Function, IntoVisitor, Result, StructuralVisitEngine, 
StructuralVisitorBase,
-    StructuralVisitorObj, StructuralVisitorView, VisitInterrupt, RUNTIME_ERROR,
+    Any, AnyView, Array, DefRegionKind, Dispatcher, Error, Function, 
IntoDispatcher, RUNTIME_ERROR,
+    Result, StructuralVisitEngine, StructuralVisitorBase, StructuralVisitorObj,
+    StructuralVisitorView, VisitInterrupt, dispatch, get_type_attr, 
structural_visit,
 };
 
 thread_local! {
@@ -96,7 +96,7 @@ unsafe impl StructuralVisitorBase for CountingBase {
         *INDEX
     }
 
-    unsafe fn default_visit(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny {
+    unsafe fn default_visit_raw(runtime: *mut Self, value: AnyView<'_>) -> 
TVMFFIAny {
         assert_eq!(
             (*runtime.cast::<TVMFFIObject>()).type_index,
             Self::type_index()
@@ -130,11 +130,28 @@ unsafe impl StructuralVisitorBase for CountingBase {
 
 type View<'a, D> = StructuralVisitorView<'a, D, CountingBase>;
 
-fn visit<'a, M>(
-    root: impl Into<AnyView<'a>>,
-    visitor: impl IntoVisitor<M, CountingBase>,
-) -> Result<Option<VisitInterrupt>> {
-    visitor.visit_root(root.into())
+fn visit<'a, H, M, D, O>(root: impl Into<AnyView<'a>>, visitor: H) -> 
Result<Option<VisitInterrupt>>
+where
+    O: std::borrow::BorrowMut<D>,
+    for<'v, 'c, 'r> H: IntoDispatcher<
+            (
+                AnyView<'v>,
+                &'c mut StructuralVisitorView<'r, D, CountingBase>,
+            ),
+            Option<Result<Option<VisitInterrupt>>>,
+            M,
+            Target = D,
+            Output = O,
+        >,
+    for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
+            (
+                AnyView<'v>,
+                &'c mut StructuralVisitorView<'r, D, CountingBase>,
+            ),
+            Option<Result<Option<VisitInterrupt>>>,
+        >,
+{
+    StructuralVisitEngine::<CountingBase>::visit_root(root, visitor)
 }
 
 #[derive(Default)]
@@ -217,12 +234,16 @@ fn 
unmatched_scalar_uses_selected_default_and_owned_dispatcher() {
     DROPPED.with_borrow_mut(Vec::clear);
     assert!(visit(&10_i64, Empty).unwrap().is_none());
     DROPPED.with_borrow(|drops| assert_eq!(*drops, [3]));
-    assert!(structural_visit(&Array::new(vec![1_i64]), Empty)
-        .unwrap()
-        .is_none());
-    assert!(structural_visit(&Array::new(vec![1_i64]), &mut Empty)
-        .unwrap()
-        .is_none());
+    assert!(
+        structural_visit(&Array::new(vec![1_i64]), Empty)
+            .unwrap()
+            .is_none()
+    );
+    assert!(
+        structural_visit(&Array::new(vec![1_i64]), &mut Empty)
+            .unwrap()
+            .is_none()
+    );
 }
 
 #[test]
@@ -276,11 +297,13 @@ fn base_failure_and_owned_results_release_exactly_once() {
     }
     DROPPED.with_borrow_mut(Vec::clear);
     FAIL_INIT.set(true);
-    assert!(visit(&1_i64, Unmatched)
-        .err()
-        .unwrap()
-        .to_string()
-        .contains("initialization failed"));
+    assert!(
+        visit(&1_i64, Unmatched)
+            .err()
+            .unwrap()
+            .to_string()
+            .contains("initialization failed")
+    );
     FAIL_INIT.set(false);
     DROPPED.with_borrow(|drops| assert!(drops.is_empty()));
 
@@ -358,12 +381,14 @@ fn 
callback_outcomes_preserve_reborrows_and_owners_through_specialized_defaults(
         assert_eq!(visitor.seen, [10, 11, 99]);
         DROPPED.with_borrow(|drops| assert_eq!(*drops, [2]));
         if fail {
-            assert!(result
-                .as_ref()
-                .err()
-                .unwrap()
-                .to_string()
-                .contains("callback failed"));
+            assert!(
+                result
+                    .as_ref()
+                    .err()
+                    .unwrap()
+                    .to_string()
+                    .contains("callback failed")
+            );
         } else {
             assert_eq!(payload.debug_strong_count(), Some(2));
         }

Reply via email to