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 23eafe84 [FEAT][Rust] Preserve in-place mutation permissions through 
callbacks (#798)
23eafe84 is described below

commit 23eafe84f57a3bbf1356a7e0fc9288be9ecc495f
Author: Shushi Hong <[email protected]>
AuthorDate: Thu Sep 17 17:57:20 2026 -0400

    [FEAT][Rust] Preserve in-place mutation permissions through callbacks (#798)
    
    Rust mutation callbacks previously used borrowed default recursion,
    which could not reuse the original object's storage even when it was
    uniquely owned.
    
    This PR adds `MutateValue<T>` so callbacks can inspect a node, then pass
    the handle by value to default recursion. The handle does not increase
    the reference count. With `InplaceMode::Allow`, recursion can reuse
    uniquely owned objects; shared objects are copied. `Disallow` always
    prevents in-place modification.
    
    Rust's borrow checker prevents a node reference borrowed from the handle
    from remaining in use across its consumption.
    
    Both `#[dispatch(mutate)]` handlers and closure callbacks support this
    API, including helpers that preserve `UnchangedOr`. Callbacks now
    inspect their argument directly and pass it explicitly to default
    recursion; mutation contexts no longer expose `current()`.
---
 docs/guides/rust_lang_guide.md                    |  41 +-
 rust/tvm-ffi-macros/src/dispatch.rs               | 106 ++-
 rust/tvm-ffi/src/extra/dispatch.rs                |  12 +-
 rust/tvm-ffi/src/extra/mod.rs                     |   3 +
 rust/tvm-ffi/src/extra/structural_common.rs       |  22 +-
 rust/tvm-ffi/src/extra/structural_mutate.rs       | 775 ++++++++++++++++++----
 rust/tvm-ffi/src/extra/structural_visit.rs        | 132 ++--
 rust/tvm-ffi/src/extra/structural_visit/policy.rs |  25 +-
 rust/tvm-ffi/src/lib.rs                           |   7 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs      | 389 ++++++++++-
 rust/tvm-ffi/tests/test_structural_visit.rs       |   2 +-
 11 files changed, 1225 insertions(+), 289 deletions(-)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index 2bc2764c..e308d685 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -270,7 +270,7 @@ order with the first matching argument type winning, like 
the variadic C++
 `StructuralWalk(root, callbacks...)` chain. A flat tuple holds up to 12
 lambdas; a tuple is itself a link, so nest `(a, b, (c, d, ...))` to chain
 more — order stays the flattened left-to-right order. Unmatched values
-simply advance; a `&VisitValue` lambda acts as a catch-all and must come
+simply advance; a `&StructuralView` lambda acts as a catch-all and must come
 last, since links after an always-matching one never run. Each lambda may
 take a trailing `DefRegionKind` argument:
 
@@ -339,7 +339,7 @@ Callbacks receive `VisitContext`, use first-match dispatch, 
and own traversal
 of matched values. `VisitCallbacks` adds state shared by the callback chain:
 
 ```rust
-use tvm_ffi::{structural_visit, Array, VisitCallbacks, VisitContext, 
VisitValue};
+use tvm_ffi::{structural_visit, Array, VisitCallbacks, VisitContext, 
StructuralView};
 
 #[derive(Default)]
 struct Stats {
@@ -353,7 +353,7 @@ let mut visitor = VisitCallbacks::new(
         |value: i64, visitor: &mut VisitContext<'_, Stats>| {
             visitor.state_mut().total += value;
         },
-        |_value: &VisitValue, visitor: &mut VisitContext<'_, Stats>| {
+        |_value: &StructuralView, visitor: &mut VisitContext<'_, Stats>| {
             visitor.visit_children()
         },
     ),
@@ -377,7 +377,7 @@ unmatched values use default child traversal:
 ```rust
 use tvm_ffi::{
     dispatch, structural_visit, Array, DefRegionKind, Result, 
StructuralVisitor, VisitInterrupt,
-    VisitValue,
+    StructuralView,
 };
 
 #[derive(Default)]
@@ -390,7 +390,7 @@ struct Depth {
 impl Depth {
     fn visit_any(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         self.current += 1;
@@ -417,18 +417,21 @@ than silently walked through reflection — visit such a 
type's children
 explicitly from a `StructuralVisitor`, or skip it with a pre-order
 `WalkResult::Skip`.
 
+`StructuralView` is the shared borrowed callback value for visit, walk, map,
+and mutate; `VisitValue` and `MapValue` remain compatibility names.
+
 ### Structural Mapping and Mutation
 
 `structural_map` is the transforming counterpart to `structural_walk`. Put
 `#[dispatch(map)]` on an impl whose `map_*` methods return any value 
convertible
 into `Any`, directly or in `Result`. Methods are tested in source order, the
 first matching argument type wins, and an unmatched value is preserved. A
-method may take an optional trailing `DefRegionKind`; a `&MapValue` method is a
+method may take an optional trailing `DefRegionKind`; a `&StructuralView` 
method is a
 catch-all and should therefore come last:
 
 ```rust
 use tvm_ffi::{
-    dispatch, structural_map, Any, Array, DefRegionKind, MapValue, Result, 
WalkOrder,
+    dispatch, structural_map, Any, Array, DefRegionKind, StructuralView, 
Result, WalkOrder,
 };
 
 #[derive(Default)]
@@ -443,7 +446,7 @@ impl Increment {
         Ok(value + 1)
     }
 
-    fn map_other(&mut self, value: &MapValue) -> Any {
+    fn map_other(&mut self, value: &StructuralView) -> Any {
         value.to_owned()
     }
 }
@@ -462,7 +465,7 @@ A single typed closure and an ordered tuple of up to twelve 
closures are also
 accepted; a tuple is itself a link, so `(a, b, (c, d, ...))` nests beyond that
 without changing the flattened order. Tuple dispatch is first-match, not 
broadcast: later closures do not
 run after an earlier argument type matches. As with generated dispatch, a
-`&MapValue` catch-all belongs last. Numeric handlers claim the complete FFI
+`&StructuralView` catch-all belongs last. Numeric handlers claim the complete 
FFI
 `Int` or `Float` tag and then use Rust `as` conversion semantics; prefer `i64`
 or `f64` unless narrowing is deliberate.
 
@@ -476,7 +479,7 @@ there is final. `Array` and `List` elements are mapped in 
order. `Map` and
 The root is consumed. A uniquely owned built-in container may reuse its
 storage in place; passing `root.clone()` keeps the source shared and selects
 copy-on-write behavior. The engine rechecks uniqueness after a pre-order
-callback, so retaining an owning `MapValue::to_owned()` alias before the
+callback, so retaining an owning `StructuralView::to_owned()` alias before the
 container's children are mapped forces the non-in-place path. Reflected
 objects must provide `__ffi_shallow_copy__`; the copy is validated before
 fields are mapped and discarded if no structural field changes.
@@ -511,7 +514,7 @@ original value.
 
 ```rust
 use tvm_ffi::{
-    structural_mutate, Array, CallbackMutator, MapValue, MutateCallbacks,
+    structural_mutate, Array, CallbackMutator, MutateValue, MutateCallbacks,
 };
 
 #[derive(Default)]
@@ -526,8 +529,8 @@ let mut mutator = MutateCallbacks::new(
             mutator.state_mut().integers += 1;
             value + 1
         },
-        |_value: &MapValue, mutator: &mut CallbackMutator<Stats>| {
-            mutator.default_mutate()
+        |value: MutateValue<'_>, mutator: &mut CallbackMutator<Stats>| {
+            mutator.default_maybe_inplace_mutate(value)
         },
     ),
 );
@@ -541,6 +544,12 @@ assert_eq!(mutator.state().integers, 2);
 `maybe_inplace_mutate` preserves the reuse opportunity of an owned value.
 Closure callbacks are `Fn`; mutable data belongs in the callback state.
 
+Use `MutateValue<'_, T>` with `default_maybe_inplace_mutate` to forward its
+permission, or `default_mutate_with_mode` to restrict it. Borrow through 
`value`; mutation
+contexts do not expose `current()`, and copy-only default descent takes an
+explicit borrow (`default_mutate(value)`). Generated handlers may take
+`InplaceMode` after `&mut Mutator`; see `MutateValue` for ownership 
requirements.
+
 `#[dispatch(mutate)]` groups typed `mutate_*` callbacks. The dispatch object
 owns its mutable pass state, while `Mutator` supplies recursion and the current
 definition region. `mutator.mutate(self, child)` safely reborrows that dispatch
@@ -577,7 +586,7 @@ assert_eq!(increment.integers, 2);
 
 For a named custom recursion policy, implement `StructuralMutator` and pass
 `&mut` it to `structural_mutate`. `InplaceValue` is an engine-issued
-capability: callers cannot construct it from a read-only `MapValue`. Override
+capability: callers cannot construct it from a read-only `StructuralView`. 
Override
 `dispatch_maybe_inplace_mutate` to opt into default container reuse;
 `default_maybe_inplace_mutate` rechecks uniqueness before writing. Borrowed
 values can be re-entered with `mutate`, while owned values can use
@@ -585,7 +594,7 @@ values can be re-entered with `mutate`, while owned values 
can use
 
 ```rust
 use tvm_ffi::{
-    structural_mutate, Any, Array, DefRegionKind, InplaceValue, MapValue, 
Result,
+    structural_mutate, Any, Array, DefRegionKind, InplaceValue, 
StructuralView, Result,
     StructuralMutator,
 };
 
@@ -593,7 +602,7 @@ use tvm_ffi::{
 struct Increment;
 
 impl StructuralMutator for Increment {
-    fn dispatch_mutate(&mut self, value: &MapValue, kind: DefRegionKind) -> 
Result<Any> {
+    fn dispatch_mutate(&mut self, value: &StructuralView, kind: DefRegionKind) 
-> Result<Any> {
         match value.cast::<i64>() {
             Some(value) => Ok(Any::from(value + 1)),
             None => self.default_mutate(value, kind),
diff --git a/rust/tvm-ffi-macros/src/dispatch.rs 
b/rust/tvm-ffi-macros/src/dispatch.rs
index 85a16c86..feffa55b 100644
--- a/rust/tvm-ffi-macros/src/dispatch.rs
+++ b/rust/tvm-ffi-macros/src/dispatch.rs
@@ -71,13 +71,6 @@ impl DispatchMode {
         }
     }
 
-    fn value_type(self) -> &'static str {
-        match self {
-            Self::Walk | Self::Visit => "VisitValue",
-            Self::Map | Self::Mutate => "MapValue",
-        }
-    }
-
     fn result_is_optional(self) -> bool {
         match self {
             Self::Walk | Self::Map | Self::Mutate => true,
@@ -182,7 +175,7 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
                 let span = handler.method.span();
                 let handler_attrs = &handler.cfg_attrs;
                 let later_attrs = &later.cfg_attrs;
-                let value_type = mode.value_type();
+                let value_type = "StructuralView";
                 quote_spanned! {span=>
                     #(#[#impl_cfg_attrs])*
                     #(#[#handler_attrs])*
@@ -203,7 +196,7 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
                 #[allow(unreachable_code, unused_variables)]
                 fn dispatch_walk(
                     &mut self,
-                    value: &#tvm_ffi::extra::structural_visit::VisitValue,
+                    value: &#tvm_ffi::StructuralView,
                     def_region_kind: 
#tvm_ffi::extra::structural_visit::DefRegionKind,
                 ) -> 
Option<#tvm_ffi::extra::structural_visit::WalkCallbackResult> {
                     #(#links)*
@@ -219,7 +212,7 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
                 #[allow(unreachable_code, unused_variables)]
                 fn visit(
                     &mut self,
-                    value: &#tvm_ffi::extra::structural_visit::VisitValue,
+                    value: &#tvm_ffi::StructuralView,
                     def_region_kind: 
#tvm_ffi::extra::structural_visit::DefRegionKind,
                 ) -> #tvm_ffi::Result<
                     Option<#tvm_ffi::extra::structural_visit::VisitInterrupt>
@@ -237,7 +230,7 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
                 #[allow(unreachable_code, unused_variables)]
                 fn dispatch_map(
                     &mut self,
-                    value: &#tvm_ffi::extra::structural_mutate::MapValue,
+                    value: &#tvm_ffi::StructuralView,
                     def_region_kind: 
#tvm_ffi::extra::structural_visit::DefRegionKind,
                 ) -> Option<#tvm_ffi::extra::structural_mutate::MapResult> {
                     #(#links)*
@@ -253,9 +246,22 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
                 #[allow(unreachable_code, unused_variables)]
                 fn dispatch_mutate(
                     &mut self,
-                    value: &#tvm_ffi::extra::structural_mutate::MapValue,
+                    value: &#tvm_ffi::StructuralView,
                     mutator: &mut #tvm_ffi::extra::structural_mutate::Mutator,
                 ) -> Option<#tvm_ffi::extra::structural_mutate::MutateResult> {
+                    <Self as 
#tvm_ffi::extra::structural_mutate::MutateDispatch>::dispatch_mutate_value(
+                        self, #tvm_ffi::MutateValue::borrowed(value), mutator,
+                    )
+                }
+
+                #[inline(always)]
+                #[allow(unreachable_code, unused_variables, unused_mut)]
+                fn dispatch_mutate_value(
+                    &mut self,
+                    mut value: #tvm_ffi::MutateValue<'_>,
+                    mutator: &mut #tvm_ffi::Mutator,
+                ) -> Option<#tvm_ffi::extra::structural_mutate::MutateResult> {
+                    let inplace_mode = value.inplace_mode();
                     #(#links)*
                     None
                 }
@@ -283,6 +289,9 @@ fn expand_links(
             let method = &handler.method;
             let attrs = &handler.cfg_attrs;
             let trailing_arg = match mode {
+                DispatchMode::Mutate if handler.wants_inplace_mode => {
+                    quote!(, mutator, inplace_mode)
+                }
                 DispatchMode::Mutate if handler.wants_mutator => quote!(, 
mutator),
                 DispatchMode::Mutate => quote!(),
                 _ if handler.wants_def_region => quote!(, def_region_kind),
@@ -297,6 +306,11 @@ fn expand_links(
             };
             let invoke = match &handler.argument {
                 HandlerArgument::Value => {
+                    let value = if matches!(mode, DispatchMode::Mutate) {
+                        quote!(#value.as_value())
+                    } else {
+                        value.clone()
+                    };
                     let result = wrap_result(quote! {
                         #into_result(self.#method(#value #trailing_arg))
                     });
@@ -314,6 +328,17 @@ fn expand_links(
                         }
                     }
                 }
+                HandlerArgument::Capability(value_type) => {
+                    let result = wrap_result(quote! {
+                        #into_result(self.#method(typed #trailing_arg))
+                    });
+                    quote! {
+                        match #value.try_cast::<#value_type>() {
+                            Ok(typed) => return #result,
+                            Err(original) => #value = original,
+                        }
+                    }
+                }
                 HandlerArgument::Owned(value_type) => {
                     let result = wrap_result(quote! {
                         #into_result(self.#method(typed #trailing_arg))
@@ -340,6 +365,7 @@ struct Handler {
     argument: HandlerArgument,
     wants_def_region: bool,
     wants_mutator: bool,
+    wants_inplace_mode: bool,
     cfg_attrs: Vec<Meta>,
 }
 
@@ -347,6 +373,7 @@ enum HandlerArgument {
     Value,
     BorrowedNode(Type),
     Owned(Type),
+    Capability(Type),
 }
 
 fn parse_handler(method: &ImplItemMethod, mode: DispatchMode) -> 
syn::Result<Handler> {
@@ -360,10 +387,12 @@ fn parse_handler(method: &ImplItemMethod, mode: 
DispatchMode) -> syn::Result<Han
         }
         _ => false,
     };
-    let arity_is_expected = inputs.len() == 2 || inputs.len() == 3;
+    let arity_is_expected = inputs.len() == 2
+        || inputs.len() == 3
+        || (matches!(mode, DispatchMode::Mutate) && inputs.len() == 4);
     if !receiver_is_expected || !arity_is_expected {
         let message = if matches!(mode, DispatchMode::Mutate) {
-            "mutate handlers must take `&mut self`, a node, and optionally 
`&mut Mutator`"
+            "mutate handlers must take `&mut self`, a node, optionally `&mut 
Mutator`, then optionally `InplaceMode`"
                 .to_owned()
         } else {
             format!(
@@ -375,7 +404,17 @@ fn parse_handler(method: &ImplItemMethod, mode: 
DispatchMode) -> syn::Result<Han
         return Err(syn::Error::new_spanned(&method.sig, message));
     }
     let wants_def_region = !matches!(mode, DispatchMode::Mutate) && 
inputs.len() == 3;
-    let wants_mutator = matches!(mode, DispatchMode::Mutate) && inputs.len() 
== 3;
+    let wants_mutator = matches!(mode, DispatchMode::Mutate) && inputs.len() 
>= 3;
+    let wants_inplace_mode = matches!(mode, DispatchMode::Mutate) && 
inputs.len() == 4;
+    if wants_inplace_mode {
+        let Some(FnArg::Typed(arg)) = inputs.iter().nth(3) else {
+            unreachable!()
+        };
+        if !matches!(arg.ty.as_ref(), Type::Path(path) if 
path.path.segments.last().is_some_and(|s| s.ident == "InplaceMode" && 
matches!(s.arguments, PathArguments::None)))
+        {
+            return Err(syn::Error::new_spanned(&arg.ty, "expected 
`InplaceMode`"));
+        }
+    }
     if wants_mutator {
         let context_type = match inputs.iter().nth(2) {
             Some(FnArg::Typed(context)) => context.ty.as_ref(),
@@ -390,7 +429,7 @@ fn parse_handler(method: &ImplItemMethod, mode: 
DispatchMode) -> syn::Result<Han
     };
     let argument = match &value_type {
         Type::Reference(reference) if reference.mutability.is_none() => {
-            if is_dispatch_value(reference.elem.as_ref(), mode) {
+            if is_dispatch_value(reference.elem.as_ref()) {
                 HandlerArgument::Value
             } else {
                 HandlerArgument::BorrowedNode((*reference.elem).clone())
@@ -405,6 +444,28 @@ fn parse_handler(method: &ImplItemMethod, mode: 
DispatchMode) -> syn::Result<Han
                 ),
             ));
         }
+        Type::Path(path)
+            if matches!(mode, DispatchMode::Mutate)
+                && path
+                    .path
+                    .segments
+                    .last()
+                    .is_some_and(|s| s.ident == "MutateValue") =>
+        {
+            let segment = path.path.segments.last().unwrap();
+            let value_type = if let PathArguments::AngleBracketed(args) = 
&segment.arguments {
+                args.args.iter().find_map(|arg| match arg {
+                    syn::GenericArgument::Type(ty) => Some(ty.clone()),
+                    _ => None,
+                })
+            } else {
+                None
+            };
+            let tvm_ffi = get_tvm_ffi_crate();
+            HandlerArgument::Capability(
+                value_type.unwrap_or_else(|| syn::parse_quote!(#tvm_ffi::Any)),
+            )
+        }
         _ => HandlerArgument::Owned(value_type),
     };
     let cfg_attrs = presence_attrs(&method.attrs)?;
@@ -413,6 +474,7 @@ fn parse_handler(method: &ImplItemMethod, mode: 
DispatchMode) -> syn::Result<Han
         argument,
         wants_def_region,
         wants_mutator,
+        wants_inplace_mode,
         cfg_attrs,
     })
 }
@@ -496,12 +558,14 @@ fn presence_meta(meta: Meta) -> Option<Meta> {
     }
 }
 
-fn is_dispatch_value(value_type: &Type, mode: DispatchMode) -> bool {
+fn is_dispatch_value(value_type: &Type) -> bool {
     let Type::Path(path) = value_type else {
         return false;
     };
-    path.path
-        .segments
-        .last()
-        .is_some_and(|segment| segment.ident == mode.value_type())
+    path.path.segments.last().is_some_and(|segment| {
+        matches!(
+            segment.ident.to_string().as_str(),
+            "StructuralView" | "VisitValue" | "MapValue"
+        )
+    })
 }
diff --git a/rust/tvm-ffi/src/extra/dispatch.rs 
b/rust/tvm-ffi/src/extra/dispatch.rs
index 314965fa..af6f2a8e 100644
--- a/rust/tvm-ffi/src/extra/dispatch.rs
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -22,7 +22,7 @@
 use crate::error::Result;
 
 use super::structural_visit::{
-    DefRegionKind, IntoWalker, NativeVisit, VisitValue, WalkCallbackResult, 
WalkResult,
+    DefRegionKind, IntoWalker, NativeVisit, StructuralView, 
WalkCallbackResult, WalkResult,
 };
 
 /// Dispatch for typed `structural_walk` observer callbacks.
@@ -32,7 +32,7 @@ use super::structural_visit::{
 pub trait WalkDispatch: Sized {
     fn dispatch_walk(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult>;
 }
@@ -41,7 +41,7 @@ impl<V: WalkDispatch> WalkDispatch for &mut V {
     #[inline]
     fn dispatch_walk(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         (**self).dispatch_walk(value, def_region_kind)
@@ -65,7 +65,11 @@ pub struct DispatchWalker<V> {
 }
 
 impl<V: WalkDispatch> NativeVisit for DispatchWalker<V> {
-    fn visit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) -> 
Result<WalkResult> {
+    fn visit(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<WalkResult> {
         self.walker
             .dispatch_walk(value, def_region_kind)
             .unwrap_or(Ok(WalkResult::Advance))
diff --git a/rust/tvm-ffi/src/extra/mod.rs b/rust/tvm-ffi/src/extra/mod.rs
index 3abc1702..49963753 100644
--- a/rust/tvm-ffi/src/extra/mod.rs
+++ b/rust/tvm-ffi/src/extra/mod.rs
@@ -22,3 +22,6 @@ mod structural_common;
 pub mod structural_mutate;
 pub mod structural_visit;
 pub mod unchanged;
+
+/// Borrowed value shared by structural traversal and transformation callbacks.
+pub use structural_common::StructuralView;
diff --git a/rust/tvm-ffi/src/extra/structural_common.rs 
b/rust/tvm-ffi/src/extra/structural_common.rs
index be87a044..73242c84 100644
--- a/rust/tvm-ffi/src/extra/structural_common.rs
+++ b/rust/tvm-ffi/src/extra/structural_common.rs
@@ -64,15 +64,25 @@ macro_rules! impl_callback_chain_tuple_arities {
 
 pub(crate) use impl_callback_chain_tuple_arities;
 
-/// A borrowed value shared by structural visit and map callbacks.
+/// A borrowed value shared by structural visit, walk, map, and mutate 
callbacks.
 ///
-/// This type centralizes the audited unsafe operations used to cast FFI
-/// values and borrow object nodes. The public APIs expose it as `VisitValue`
-/// or `MapValue` according to the callback context.
+/// Use `&StructuralView` for an erased callback argument. [`Self::as_node`]
+/// borrows an object node, while [`Self::cast`] returns a typed value 
(acquiring
+/// ownership for object handles). This view does not grant in-place 
permission;
+/// consuming mutation callbacks use [`crate::MutateValue`] instead.
+/// `VisitValue` and `MapValue` remain compatibility names for this type.
 #[repr(transparent)]
-pub struct StructuralValue(TVMFFIAny);
+pub struct StructuralView(TVMFFIAny);
 
-impl StructuralValue {
+impl<'a> From<&'a StructuralView> for AnyView<'a> {
+    #[inline]
+    fn from(value: &'a StructuralView) -> Self {
+        // SAFETY: the view cannot outlive the borrowed structural value.
+        unsafe { AnyView::from_raw_ffi_any(value.raw()) }
+    }
+}
+
+impl StructuralView {
     #[inline]
     pub(crate) fn from_raw(raw: TVMFFIAny) -> Self {
         Self(raw)
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 96bcba4b..80998f0a 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -66,7 +66,10 @@ const FLAG_SEQ_HASH_IGNORE: i64 = 
kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
 const FLAG_SETTER_IS_FUNCTION: i64 = kTVMFFIFieldFlagBitSetterIsFunctionObj as 
i64;
 
 /// Borrowed value passed to structural map and mutation callbacks.
-pub use super::structural_common::StructuralValue as MapValue;
+pub use super::StructuralView;
+
+/// Compatibility name for [`StructuralView`].
+pub use super::StructuralView as MapValue;
 
 /// Result type produced by a structural-map callback.
 #[doc(hidden)]
@@ -106,14 +109,161 @@ impl<T: Into<Any>> IntoMapResult for Result<T> {
     }
 }
 
+/// Permission to attempt in-place structural mutation along an owned path.
+///
+/// `Allow` still requires unique ownership; it does not grant permission to
+/// modify a borrowed value. Use an owned value or an engine-issued
+/// [`InplaceValue`] with the mode-aware mutation helpers.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum InplaceMode {
+    /// Use ordinary mutation, including for uniquely owned values.
+    #[default]
+    Disallow,
+    /// Permit reuse when ownership and alias checks allow it.
+    Allow,
+}
+
+impl InplaceMode {
+    fn permit(self) -> Permit {
+        match self {
+            Self::Disallow => Permit::Copy,
+            Self::Allow => Permit::MaybeInPlace,
+        }
+    }
+
+    fn permit_if_unique(self, raw: TVMFFIAny) -> Permit {
+        if self == Self::Allow && object_is_unique(raw) {
+            Permit::MaybeInPlace
+        } else {
+            Permit::Copy
+        }
+    }
+}
+
+/// A callback value carrying the engine's in-place permission.
+///
+/// Unlike an owning typed argument, this handle does not increment the 
reference
+/// count. Borrow through it to inspect the node, then consume it in
+/// `default_maybe_inplace_mutate` to continue recursion. A surviving owning 
alias
+/// still forces copying. Node borrows come from this handle, not from the
+/// callback context, so the handle can safely be passed to another context.
+/// `T` selects the callback's matched FFI type; `Any` matches every value.
+///
+/// A node borrow cannot survive consumption of the handle:
+/// ```compile_fail
+/// use tvm_ffi::{CallbackMutator, MutateValue};
+/// fn invalid(value: MutateValue<'_>, ctx: &mut CallbackMutator) {
+///     let node = 
value.as_node::<tvm_ffi::collections::array::ArrayObj>().unwrap();
+///     ctx.default_maybe_inplace_mutate(value).unwrap();
+///     println!("{}", node.size);
+/// }
+/// ```
+///
+/// Moving the handle into a nested callback cannot bypass that borrow:
+/// ```compile_fail
+/// use std::cell::RefCell;
+/// use tvm_ffi::{structural_mutate, CallbackMutator, MutateValue};
+/// fn invalid(value: MutateValue<'_>) {
+///     let node = 
value.as_node::<tvm_ffi::collections::array::ArrayObj>().unwrap();
+///     let pending = RefCell::new(Some(value));
+///     structural_mutate(true, |_: bool, inner: &mut CallbackMutator| {
+///         
inner.default_maybe_inplace_mutate(pending.borrow_mut().take().unwrap())
+///     }).unwrap();
+///     println!("{}", node.size);
+/// }
+/// ```
+pub struct MutateValue<'a, T = Any> {
+    value: StructuralView,
+    mode: InplaceMode,
+    _scope: PhantomData<&'a StructuralView>,
+    _type: PhantomData<fn() -> T>,
+    _not_send_sync: PhantomData<Rc<()>>,
+}
+
+impl<'a> MutateValue<'a> {
+    fn new(value: &'a StructuralView, mode: InplaceMode) -> Self {
+        Self {
+            value: StructuralView::from_raw(value.raw()),
+            mode,
+            _scope: PhantomData,
+            _type: PhantomData,
+            _not_send_sync: PhantomData,
+        }
+    }
+
+    /// Wrap a borrowed value without granting in-place permission.
+    pub fn borrowed(value: &'a StructuralView) -> Self {
+        Self::new(value, InplaceMode::Disallow)
+    }
+}
+
+impl<'a, T> MutateValue<'a, T> {
+    /// Permission established before callback arguments acquire ownership.
+    pub fn inplace_mode(&self) -> InplaceMode {
+        self.mode
+    }
+
+    /// Borrow the current value. The borrow must end before default mutation.
+    pub fn as_value(&self) -> &StructuralView {
+        &self.value
+    }
+
+    /// Narrow the matched type without acquiring an owning reference.
+    pub fn try_cast<U: crate::type_traits::ContainerElement>(
+        self,
+    ) -> std::result::Result<MutateValue<'a, U>, Self> {
+        // SAFETY: the engine keeps the borrowed value live for this handle's
+        // lifetime. This only checks its type and does not acquire ownership.
+        if unsafe { U::container_check_any_strict(&self.value.raw()) } {
+            Ok(MutateValue {
+                value: self.value,
+                mode: self.mode,
+                _scope: PhantomData,
+                _type: PhantomData,
+                _not_send_sync: PhantomData,
+            })
+        } else {
+            Err(self)
+        }
+    }
+
+    /// Erase the matched type while preserving the engine-issued permission.
+    pub fn into_untyped(self) -> MutateValue<'a> {
+        MutateValue {
+            value: self.value,
+            mode: self.mode,
+            _scope: PhantomData,
+            _type: PhantomData,
+            _not_send_sync: PhantomData,
+        }
+    }
+
+    fn permit(&self, requested: InplaceMode) -> Permit {
+        if self.mode == InplaceMode::Disallow {
+            Permit::Copy
+        } else {
+            requested.permit_if_unique(self.value.raw())
+        }
+    }
+}
+
+impl<T> Deref for MutateValue<'_, T> {
+    type Target = StructuralView;
+    fn deref(&self) -> &Self::Target {
+        self.as_value()
+    }
+}
+
 /// State and recursive operations available to a callback-chain mutation.
 ///
 /// A matched callback owns mutation of its value. Recursive operations
 /// reborrow the mutator, so mutable state cannot remain borrowed across them.
+/// The context does not store a node: inspect the callback argument and pass
+/// it explicitly to default recursion.
 pub struct MutateContext<'a, State, Driver: ?Sized = dyn 
MutateContextDriver<State> + 'a> {
     driver: &'a mut Driver,
-    current: MapValue,
     def_region_kind: DefRegionKind,
+    inplace_mode: InplaceMode,
     _state: PhantomData<fn() -> State>,
     _not_send_sync: PhantomData<Rc<()>>,
 }
@@ -129,18 +279,21 @@ pub type CallbackMutator<'a, State = (), Driver = dyn 
MutateContextDriver<State>
 ///
 /// The dispatch object owns all pass state. Recursive operations take that
 /// object explicitly so Rust can safely reborrow the same `&mut self` for the
-/// child call.
+/// child call. Node access comes only from the callback argument; default
+/// recursion takes that value explicitly.
 pub struct Mutator {
-    current: MapValue,
     def_region_kind: DefRegionKind,
+    inplace_mode: InplaceMode,
     _not_send_sync: PhantomData<Rc<()>>,
 }
 
 impl Mutator {
-    /// Complete borrowed value active at this callback.
-    #[inline(always)]
-    pub fn current(&self) -> &MapValue {
-        &self.current
+    /// Engine-established permission for this callback's current value.
+    ///
+    /// This is not authority to modify a borrow: default in-place descent also
+    /// requires consuming a [`MutateValue`].
+    pub fn inplace_mode(&self) -> InplaceMode {
+        self.inplace_mode
     }
 
     /// Definition-region state active at the callback's current value.
@@ -202,13 +355,35 @@ impl Mutator {
         D: MutateDispatch,
         T: Into<Any>,
     {
-        StructuralMutator::maybe_inplace_mutate(dispatch, value, 
def_region_kind)
+        self.maybe_inplace_mutate_with_mode(dispatch, value, def_region_kind, 
InplaceMode::Allow)
     }
 
-    /// Apply default mutation to the callback's current value.
+    /// Mutate an owned child under an explicit region and in-place permission.
+    ///
+    /// `Disallow` keeps this input on the copy path even when uniquely owned.
+    #[inline(always)]
+    pub fn maybe_inplace_mutate_with_mode<D, T>(
+        &mut self,
+        dispatch: &mut D,
+        value: T,
+        def_region_kind: DefRegionKind,
+        mode: InplaceMode,
+    ) -> Result<Any>
+    where
+        D: MutateDispatch,
+        T: Into<Any>,
+    {
+        StructuralMutator::maybe_inplace_mutate_with_mode(dispatch, value, 
def_region_kind, mode)
+    }
+
+    /// Apply default copy-only mutation to an explicitly borrowed value.
     #[inline(always)]
-    pub fn default_mutate<D: MutateDispatch>(&mut self, dispatch: &mut D) -> 
Result<Any> {
-        StructuralMutator::default_mutate(dispatch, &self.current, 
self.def_region_kind)
+    pub fn default_mutate<D, T>(&mut self, dispatch: &mut D, value: &T) -> 
Result<Any>
+    where
+        D: MutateDispatch,
+        for<'x> AnyView<'x>: From<&'x T>,
+    {
+        StructuralMutator::default_mutate_value(dispatch, value, 
self.def_region_kind)
     }
 
     /// Mutate a borrowed child while preserving an unchanged result.
@@ -238,11 +413,66 @@ impl Mutator {
 
     /// Apply default mutation without materializing an unchanged original.
     #[inline]
-    pub fn default_mutate_result<D: MutateDispatch>(
+    pub fn default_mutate_result<D, T>(
         &mut self,
         dispatch: &mut D,
+        value: &T,
+    ) -> Result<UnchangedOr<Any>>
+    where
+        D: MutateDispatch,
+        for<'x> AnyView<'x>: From<&'x T>,
+    {
+        StructuralMutator::default_mutate_value_result(dispatch, value, 
self.def_region_kind)
+    }
+
+    /// Consume the handle for default descent with its existing permission.
+    #[inline]
+    pub fn default_maybe_inplace_mutate<D: MutateDispatch, T>(
+        &mut self,
+        dispatch: &mut D,
+        value: MutateValue<'_, T>,
+    ) -> Result<Any> {
+        let mode = value.inplace_mode();
+        self.default_mutate_with_mode(dispatch, value, mode)
+    }
+
+    /// Consume the handle for default descent, preserving permission and 
`Unchanged`.
+    #[inline]
+    pub fn default_maybe_inplace_mutate_result<D: MutateDispatch, T>(
+        &mut self,
+        dispatch: &mut D,
+        value: MutateValue<'_, T>,
+    ) -> Result<UnchangedOr<Any>> {
+        let mode = value.inplace_mode();
+        self.default_mutate_with_mode_result(dispatch, value, mode)
+    }
+
+    /// Continue default mutation after relinquishing the callback value's 
borrows.
+    ///
+    /// The requested mode can restrict, but cannot upgrade, the engine-issued
+    /// permission. Retained owning aliases force copying.
+    pub fn default_mutate_with_mode<D: MutateDispatch, T>(
+        &mut self,
+        dispatch: &mut D,
+        value: MutateValue<'_, T>,
+        mode: InplaceMode,
+    ) -> Result<Any> {
+        let raw = value.value.raw();
+        let permit = value.permit(mode);
+        user_default_mutate(dispatch, raw, self.def_region_kind, permit)
+            .and_then(|result| resolve_result(result, raw))
+    }
+
+    /// Consume the callback value for default descent, preserving `Unchanged`.
+    pub fn default_mutate_with_mode_result<D: MutateDispatch, T>(
+        &mut self,
+        dispatch: &mut D,
+        value: MutateValue<'_, T>,
+        mode: InplaceMode,
     ) -> Result<UnchangedOr<Any>> {
-        StructuralMutator::default_mutate_result(dispatch, &self.current, 
self.def_region_kind)
+        let permit = value.permit(mode);
+        user_default_mutate(dispatch, value.value.raw(), self.def_region_kind, 
permit)
+            .and_then(UnchangedOr::from_carrier)
     }
 
     /// Look up an invocation-local identity substitution.
@@ -250,7 +480,7 @@ impl Mutator {
     pub fn var_remap_get<D: MutateDispatch>(
         &mut self,
         dispatch: &mut D,
-        var: &MapValue,
+        var: &StructuralView,
     ) -> Result<Option<Any>> {
         StructuralMutator::var_remap_get(dispatch, var)
     }
@@ -260,7 +490,7 @@ impl Mutator {
     pub fn var_remap_set<D: MutateDispatch>(
         &mut self,
         dispatch: &mut D,
-        var: &MapValue,
+        var: &StructuralView,
         mutated_value: &Any,
     ) -> Result<()> {
         StructuralMutator::var_remap_set(dispatch, var, mutated_value)
@@ -272,20 +502,25 @@ impl Mutator {
 ///
 /// The dispatch macro keeps the concrete implementor visible to the compiler
 /// so recursive `mutate` calls can be inlined. This is not a user extension
-/// point.
+/// point. Borrowed inputs carry a lifetime; in-place entry requires an owned
+/// value or a consumed capability, never a bare ABI value plus permission.
 pub trait MutateContextDriver<State> {
     fn state(&self) -> &State;
     fn state_mut(&mut self) -> &mut State;
-    fn mutate_raw(
+    fn mutate_borrowed(&mut self, value: AnyView<'_>, kind: DefRegionKind) -> 
Result<Any>;
+    fn mutate_owned(&mut self, value: Any, kind: DefRegionKind, mode: 
InplaceMode) -> Result<Any>;
+    fn default_mutate_borrowed(&mut self, value: AnyView<'_>, kind: 
DefRegionKind) -> Result<Any>;
+    /// Consume the handle to exclude node borrows before default descent.
+    fn default_mutate_value(
         &mut self,
-        raw: TVMFFIAny,
-        def_region_kind: DefRegionKind,
-        permit: Permit,
-    ) -> Result<Any>;
-    fn default_mutate_raw(&mut self, raw: TVMFFIAny, def_region_kind: 
DefRegionKind)
-        -> Result<Any>;
-    fn var_remap_get_raw(&mut self, raw: TVMFFIAny) -> Result<Option<Any>>;
-    fn var_remap_set_raw(&mut self, raw: TVMFFIAny, mutated_value: &Any) -> 
Result<()>;
+        value: MutateValue<'_>,
+        kind: DefRegionKind,
+        _mode: InplaceMode,
+    ) -> Result<Any> {
+        self.default_mutate_borrowed(AnyView::from(value.as_value()), kind)
+    }
+    fn var_remap_get(&mut self, var: &StructuralView) -> Result<Option<Any>>;
+    fn var_remap_set(&mut self, var: &StructuralView, mutated_value: &Any) -> 
Result<()>;
 }
 
 impl<State, Driver> MutateContext<'_, State, Driver>
@@ -304,10 +539,12 @@ where
         self.driver.state_mut()
     }
 
-    /// Complete borrowed value active at this callback.
-    #[inline(always)]
-    pub fn current(&self) -> &MapValue {
-        &self.current
+    /// Engine-established permission for this callback's current value.
+    ///
+    /// This is not authority to modify a borrow: default in-place descent also
+    /// requires consuming a [`MutateValue`].
+    pub fn inplace_mode(&self) -> InplaceMode {
+        self.inplace_mode
     }
 
     /// Definition-region state active at the callback's current value.
@@ -340,7 +577,7 @@ where
     {
         let view = AnyView::from(value);
         self.driver
-            .mutate_raw(*view.as_raw_ffi_any(), def_region_kind, Permit::Copy)
+            .mutate_borrowed(view, def_region_kind)
             .and_then(|result| resolve_result(result, *view.as_raw_ffi_any()))
     }
 
@@ -358,24 +595,37 @@ where
         value: T,
         def_region_kind: DefRegionKind,
     ) -> Result<Any> {
-        let value = value.into();
-        let result = self.driver.mutate_raw(
-            *value.as_raw_ffi_any(),
-            def_region_kind,
-            Permit::MaybeInPlace,
-        )?;
-        Ok(if is_unchanged(&result) { value } else { result })
+        self.maybe_inplace_mutate_with_mode(value, def_region_kind, 
InplaceMode::Allow)
     }
 
-    /// Apply default mutation to the callback's current value.
+    /// Mutate an owned value under an explicit region and in-place permission.
     ///
-    /// This operation always uses the copy path because a callback may still
-    /// hold a shared borrow of the current value. It may be called repeatedly.
+    /// `Disallow` keeps this input on the copy path even when uniquely owned.
     #[inline(always)]
-    pub fn default_mutate(&mut self) -> Result<Any> {
+    pub fn maybe_inplace_mutate_with_mode<T: Into<Any>>(
+        &mut self,
+        value: T,
+        def_region_kind: DefRegionKind,
+        mode: InplaceMode,
+    ) -> Result<Any> {
         self.driver
-            .default_mutate_raw(self.current.raw(), self.def_region_kind)
-            .and_then(|result| resolve_result(result, self.current.raw()))
+            .mutate_owned(value.into(), def_region_kind, mode)
+    }
+
+    /// Apply default copy-only mutation to an explicitly borrowed value.
+    ///
+    /// This may be called repeatedly while holding node borrows. The value's
+    /// children re-enter callback dispatch, but the value itself does not.
+    #[inline(always)]
+    pub fn default_mutate<T>(&mut self, value: &T) -> Result<Any>
+    where
+        for<'x> AnyView<'x>: From<&'x T>,
+    {
+        let view = AnyView::from(value);
+        let raw = *view.as_raw_ffi_any();
+        self.driver
+            .default_mutate_borrowed(view, self.def_region_kind)
+            .and_then(|result| resolve_result(result, raw))
     }
 
     /// Mutate a borrowed child while preserving an unchanged result.
@@ -399,28 +649,76 @@ where
     {
         let view = AnyView::from(value);
         self.driver
-            .mutate_raw(*view.as_raw_ffi_any(), kind, Permit::Copy)
+            .mutate_borrowed(view, kind)
             .and_then(UnchangedOr::from_carrier)
     }
 
     /// Apply default mutation without materializing an unchanged original.
     #[inline]
-    pub fn default_mutate_result(&mut self) -> Result<UnchangedOr<Any>> {
+    pub fn default_mutate_result<T>(&mut self, value: &T) -> 
Result<UnchangedOr<Any>>
+    where
+        for<'x> AnyView<'x>: From<&'x T>,
+    {
+        let view = AnyView::from(value);
+        self.driver
+            .default_mutate_borrowed(view, self.def_region_kind)
+            .and_then(UnchangedOr::from_carrier)
+    }
+
+    /// Consume the handle for default descent with its existing permission.
+    #[inline]
+    pub fn default_maybe_inplace_mutate<T>(&mut self, value: MutateValue<'_, 
T>) -> Result<Any> {
+        let mode = value.inplace_mode();
+        self.default_mutate_with_mode(value, mode)
+    }
+
+    /// Consume the handle for default descent, preserving permission and 
`Unchanged`.
+    #[inline]
+    pub fn default_maybe_inplace_mutate_result<T>(
+        &mut self,
+        value: MutateValue<'_, T>,
+    ) -> Result<UnchangedOr<Any>> {
+        let mode = value.inplace_mode();
+        self.default_mutate_with_mode_result(value, mode)
+    }
+
+    /// Continue default mutation after relinquishing the callback value's 
borrows.
+    ///
+    /// The mode can restrict, but cannot upgrade, the handle's permission.
+    /// Retained owning aliases force copying; temporary handles can be dropped
+    /// before this call to preserve reuse of the current value.
+    pub fn default_mutate_with_mode<T>(
+        &mut self,
+        value: MutateValue<'_, T>,
+        mode: InplaceMode,
+    ) -> Result<Any> {
+        let raw = value.value.raw();
+        self.driver
+            .default_mutate_value(value.into_untyped(), self.def_region_kind, 
mode)
+            .and_then(|result| resolve_result(result, raw))
+    }
+
+    /// Consume the callback value for default descent, preserving `Unchanged`.
+    pub fn default_mutate_with_mode_result<T>(
+        &mut self,
+        value: MutateValue<'_, T>,
+        mode: InplaceMode,
+    ) -> Result<UnchangedOr<Any>> {
         self.driver
-            .default_mutate_raw(self.current.raw(), self.def_region_kind)
+            .default_mutate_value(value.into_untyped(), self.def_region_kind, 
mode)
             .and_then(UnchangedOr::from_carrier)
     }
 
     /// Look up an invocation-local identity substitution.
     #[inline(always)]
-    pub fn var_remap_get(&mut self, var: &MapValue) -> Result<Option<Any>> {
-        self.driver.var_remap_get_raw(var.raw())
+    pub fn var_remap_get(&mut self, var: &StructuralView) -> 
Result<Option<Any>> {
+        self.driver.var_remap_get(var)
     }
 
     /// Store an invocation-local identity substitution.
     #[inline(always)]
-    pub fn var_remap_set(&mut self, var: &MapValue, mutated_value: &Any) -> 
Result<()> {
-        self.driver.var_remap_set_raw(var.raw(), mutated_value)
+    pub fn var_remap_set(&mut self, var: &StructuralView, mutated_value: &Any) 
-> Result<()> {
+        self.driver.var_remap_set(var, mutated_value)
     }
 }
 
@@ -432,7 +730,7 @@ where
 /// remains available for closure callback chains with separate state.
 #[diagnostic::on_unimplemented(
     message = "`{Self}` is not a supported `structural_mutate` mutator",
-    note = "accepted mutators: `&mut U` where `U: StructuralMutator`; a 
generated `MutateDispatch`; an `Fn` callback over an FFI value type `T`, `&N` 
of an object node type, or `&MapValue`, followed by `&mut 
CallbackMutator<State>`; or a tuple of up to 12 such callbacks (tuples may 
nest)",
+    note = "accepted mutators: `&mut U` where `U: StructuralMutator`; a 
generated `MutateDispatch`; an `Fn` callback over an FFI value type `T`, `&N` 
of an object node type, or `&StructuralView`, or a consuming `MutateValue<T>`, 
followed by `&mut CallbackMutator<State>`; or a tuple of up to 12 such 
callbacks (tuples may nest)",
     note = "callback arguments need explicit type annotations; use 
`MutateCallbacks::new(state, callbacks)` for ordinary mutable callback state"
 )]
 pub trait IntoMutator<Marker> {
@@ -484,7 +782,7 @@ pub trait MutateChainLink<State, Marker>: 
mutate_sealed::SealedLink<State, Marke
     #[doc(hidden)]
     fn try_mutate(
         &self,
-        value: &MapValue,
+        value: &mut Option<MutateValue<'_>>,
         mutator: &mut MutateContext<'_, State>,
     ) -> Option<MutateResult>;
 }
@@ -495,9 +793,25 @@ pub trait MutateChainLink<State, Marker>: 
mutate_sealed::SealedLink<State, Marke
 /// behavior. A generated `#[dispatch(mutate)]` implementation tests
 /// `mutate_*` methods in source order and passes the same [`Mutator`] to the
 /// first match. The implementation owns its pass state and receives `&mut
-/// self`, while [`Mutator`] only controls recursion and the definition region.
+/// self`, while [`Mutator`] controls recursion, the definition region, and
+/// in-place permission. Handlers can consume [`MutateValue`] and take an
+/// optional trailing [`InplaceMode`] after `&mut Mutator`.
 pub trait MutateDispatch: Sized {
-    fn dispatch_mutate(&mut self, value: &MapValue, mutator: &mut Mutator) -> 
Option<MutateResult>;
+    fn dispatch_mutate(
+        &mut self,
+        value: &StructuralView,
+        mutator: &mut Mutator,
+    ) -> Option<MutateResult>;
+
+    /// Dispatch an engine-issued value. Existing borrowed dispatchers retain
+    /// their copy-only default recursion; generated dispatch supports 
consuming it.
+    fn dispatch_mutate_value(
+        &mut self,
+        value: MutateValue<'_>,
+        mutator: &mut Mutator,
+    ) -> Option<MutateResult> {
+        self.dispatch_mutate(value.as_value(), mutator)
+    }
 }
 
 impl<D: MutateDispatch> IntoMutator<ByMutateDispatch> for D {
@@ -508,10 +822,20 @@ impl<D: MutateDispatch> IntoMutator<ByMutateDispatch> for 
D {
 }
 
 mod mutate_sealed {
-    use super::{IntoMutateResult, MapValue, MutateContext, ObjectCore};
+    use super::{IntoMutateResult, MutateContext, MutateValue, ObjectCore, 
StructuralView};
 
     pub trait SealedLink<State, Marker> {}
 
+    impl<F, State, T, O> SealedLink<State, super::ByMutateValue<T>> for F
+    where
+        F: for<'value, 'mutator, 'driver> Fn(
+            MutateValue<'value, T>,
+            &'mutator mut MutateContext<'driver, State>,
+        ) -> O,
+        O: IntoMutateResult,
+    {
+    }
+
     impl<F, State, T, O> SealedLink<State, super::ByMutateOwned<T>> for F
     where
         F: for<'mutator, 'driver> Fn(T, &'mutator mut MutateContext<'driver, 
State>) -> O,
@@ -532,7 +856,7 @@ mod mutate_sealed {
     impl<F, State, O> SealedLink<State, super::ByMutateCatchAll> for F
     where
         F: for<'value, 'mutator, 'driver> Fn(
-            &'value MapValue,
+            &'value StructuralView,
             &'mutator mut MutateContext<'driver, State>,
         ) -> O,
         O: IntoMutateResult,
@@ -543,6 +867,38 @@ mod mutate_sealed {
 #[doc(hidden)]
 pub enum ByMutateDispatch {}
 
+#[doc(hidden)]
+pub struct ByMutateValue<T>(PhantomData<T>);
+
+impl<F, State, T, O> MutateChainLink<State, ByMutateValue<T>> for F
+where
+    F: for<'value, 'mutator, 'driver> Fn(
+        MutateValue<'value, T>,
+        &'mutator mut MutateContext<'driver, State>,
+    ) -> O,
+    T: crate::type_traits::ContainerElement,
+    O: IntoMutateResult,
+{
+    type Strategy = DynamicMutateCallbacks;
+    fn try_mutate(
+        &self,
+        value: &mut Option<MutateValue<'_>>,
+        mutator: &mut MutateContext<'_, State>,
+    ) -> Option<MutateResult> {
+        match value
+            .take()
+            .expect("unconsumed callback value")
+            .try_cast::<T>()
+        {
+            Ok(typed) => Some(self(typed, mutator).into_mutate_result()),
+            Err(original) => {
+                *value = Some(original);
+                None
+            }
+        }
+    }
+}
+
 #[doc(hidden)]
 pub struct ByMutateOwned<T>(PhantomData<T>);
 
@@ -556,10 +912,12 @@ where
 
     fn try_mutate(
         &self,
-        value: &MapValue,
+        value: &mut Option<MutateValue<'_>>,
         mutator: &mut MutateContext<'_, State>,
     ) -> Option<MutateResult> {
         value
+            .as_ref()
+            .expect("unconsumed callback value")
             .cast::<T>()
             .map(|typed| self(typed, mutator).into_mutate_result())
     }
@@ -581,10 +939,12 @@ where
 
     fn try_mutate(
         &self,
-        value: &MapValue,
+        value: &mut Option<MutateValue<'_>>,
         mutator: &mut MutateContext<'_, State>,
     ) -> Option<MutateResult> {
         value
+            .as_ref()
+            .expect("unconsumed callback value")
             .as_node::<N>()
             .map(|node| self(node, mutator).into_mutate_result())
     }
@@ -596,7 +956,7 @@ pub enum ByMutateCatchAll {}
 impl<F, State, O> MutateChainLink<State, ByMutateCatchAll> for F
 where
     F: for<'value, 'mutator, 'driver> Fn(
-        &'value MapValue,
+        &'value StructuralView,
         &'mutator mut MutateContext<'driver, State>,
     ) -> O,
     O: IntoMutateResult,
@@ -605,10 +965,19 @@ where
 
     fn try_mutate(
         &self,
-        value: &MapValue,
+        value: &mut Option<MutateValue<'_>>,
         mutator: &mut MutateContext<'_, State>,
     ) -> Option<MutateResult> {
-        Some(self(value, mutator).into_mutate_result())
+        Some(
+            self(
+                value
+                    .as_ref()
+                    .expect("unconsumed callback value")
+                    .as_value(),
+                mutator,
+            )
+            .into_mutate_result(),
+        )
     }
 }
 
@@ -633,7 +1002,7 @@ macro_rules! impl_mutate_chain_link {
 
             fn try_mutate(
                 &self,
-                value: &MapValue,
+                value: &mut Option<MutateValue<'_>>,
                 mutator: &mut MutateContext<'_, State>,
             ) -> Option<MutateResult> {
                 $(
@@ -745,7 +1114,7 @@ where
 pub trait MapDispatch: Sized {
     fn dispatch_map(
         &mut self,
-        value: &MapValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<MapResult>;
 }
@@ -754,7 +1123,7 @@ impl<V: MapDispatch> MapDispatch for &mut V {
     #[inline]
     fn dispatch_map(
         &mut self,
-        value: &MapValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<MapResult> {
         (**self).dispatch_map(value, def_region_kind)
@@ -787,14 +1156,18 @@ impl<'a, V: MapDispatch> IntoMapper<ByMapDispatch> for 
&'a mut V {
 /// One typed callback in a structural-map tuple.
 ///
 /// Links use first-match order and may receive an owned FFI value, borrowed
-/// object node, or `&MapValue`, optionally followed by [`DefRegionKind`].
+/// object node, or `&StructuralView`, optionally followed by 
[`DefRegionKind`].
 pub trait MapChainLink<Marker>: sealed_map::SealedMapLink<Marker> {
     #[doc(hidden)]
-    fn try_map(&mut self, value: &MapValue, def_region_kind: DefRegionKind) -> 
Option<MapResult>;
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MapResult>;
 }
 
 mod sealed_map {
-    use super::{DefRegionKind, IntoMapResult, MapDispatch, MapValue, 
ObjectCore};
+    use super::{DefRegionKind, IntoMapResult, MapDispatch, ObjectCore, 
StructuralView};
 
     pub trait SealedMapLink<Marker> {}
 
@@ -828,14 +1201,14 @@ mod sealed_map {
 
     impl<F, O> SealedMapLink<super::ByMapCatchAll> for F
     where
-        F: for<'a> FnMut(&'a MapValue) -> O,
+        F: for<'a> FnMut(&'a StructuralView) -> O,
         O: IntoMapResult,
     {
     }
 
     impl<F, O> SealedMapLink<super::ByMapCatchAllKind> for F
     where
-        F: for<'a> FnMut(&'a MapValue, DefRegionKind) -> O,
+        F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
         O: IntoMapResult,
     {
     }
@@ -853,7 +1226,11 @@ where
     O: IntoMapResult,
 {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, _def_region_kind: DefRegionKind) 
-> Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        _def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         value.cast::<T>().map(|typed| self(typed).into_map_result())
     }
 }
@@ -868,7 +1245,11 @@ where
     O: IntoMapResult,
 {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, def_region_kind: DefRegionKind) -> 
Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         value
             .cast::<T>()
             .map(|typed| self(typed, def_region_kind).into_map_result())
@@ -885,7 +1266,11 @@ where
     O: IntoMapResult,
 {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, _def_region_kind: DefRegionKind) 
-> Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        _def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         value
             .as_node::<N>()
             .map(|node| self(node).into_map_result())
@@ -902,7 +1287,11 @@ where
     O: IntoMapResult,
 {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, def_region_kind: DefRegionKind) -> 
Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         value
             .as_node::<N>()
             .map(|node| self(node, def_region_kind).into_map_result())
@@ -914,11 +1303,15 @@ pub enum ByMapCatchAll {}
 
 impl<F, O> MapChainLink<ByMapCatchAll> for F
 where
-    F: for<'a> FnMut(&'a MapValue) -> O,
+    F: for<'a> FnMut(&'a StructuralView) -> O,
     O: IntoMapResult,
 {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, _def_region_kind: DefRegionKind) 
-> Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        _def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         Some(self(value).into_map_result())
     }
 }
@@ -928,11 +1321,15 @@ pub enum ByMapCatchAllKind {}
 
 impl<F, O> MapChainLink<ByMapCatchAllKind> for F
 where
-    F: for<'a> FnMut(&'a MapValue, DefRegionKind) -> O,
+    F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
     O: IntoMapResult,
 {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, def_region_kind: DefRegionKind) -> 
Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         Some(self(value, def_region_kind).into_map_result())
     }
 }
@@ -945,7 +1342,11 @@ pub enum ByMapDispatchLink {}
 
 impl<V: MapDispatch> MapChainLink<ByMapDispatchLink> for &mut V {
     #[inline]
-    fn try_map(&mut self, value: &MapValue, def_region_kind: DefRegionKind) -> 
Option<MapResult> {
+    fn try_map(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MapResult> {
         self.dispatch_map(value, def_region_kind)
     }
 }
@@ -974,7 +1375,7 @@ where
     #[inline]
     fn dispatch_map(
         &mut self,
-        value: &MapValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<MapResult> {
         self.link.try_map(value, def_region_kind)
@@ -996,7 +1397,7 @@ macro_rules! impl_map_chain_link {
             #[inline]
             fn try_map(
                 &mut self,
-                value: &MapValue,
+                value: &StructuralView,
                 def_region_kind: DefRegionKind,
             ) -> Option<MapResult> {
                 $(
@@ -1053,7 +1454,7 @@ impl_bare_map_link!(
 
 impl<F, O> IntoMapper<ByMapCatchAll> for F
 where
-    F: for<'a> FnMut(&'a MapValue) -> O,
+    F: for<'a> FnMut(&'a StructuralView) -> O,
     O: IntoMapResult,
 {
     type Mapper = MapChain<F, ByMapCatchAll>;
@@ -1066,7 +1467,7 @@ where
 
 impl<F, O> IntoMapper<ByMapCatchAllKind> for F
 where
-    F: for<'a> FnMut(&'a MapValue, DefRegionKind) -> O,
+    F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
     O: IntoMapResult,
 {
     type Mapper = MapChain<F, ByMapCatchAllKind>;
@@ -1081,7 +1482,7 @@ where
 ///
 /// The engine issues it only when the current ownership path permits reuse.
 pub struct InplaceValue<'a> {
-    value: MapValue,
+    value: StructuralView,
     _scope: PhantomData<&'a mut TVMFFIAny>,
 }
 
@@ -1089,14 +1490,14 @@ impl<'a> InplaceValue<'a> {
     #[inline]
     fn from_raw(raw: &'a mut TVMFFIAny) -> Self {
         Self {
-            value: MapValue::from_raw(*raw),
+            value: StructuralView::from_raw(*raw),
             _scope: PhantomData,
         }
     }
 
     /// Borrow the value without its in-place capability.
     #[inline]
-    pub fn as_value(&self) -> &MapValue {
+    pub fn as_value(&self) -> &StructuralView {
         &self.value
     }
 
@@ -1111,7 +1512,7 @@ impl<'a> InplaceValue<'a> {
 }
 
 impl Deref for InplaceValue<'_> {
-    type Target = MapValue;
+    type Target = StructuralView;
 
     #[inline]
     fn deref(&self) -> &Self::Target {
@@ -1129,13 +1530,13 @@ pub struct StructuralVarRemap {
 
 impl StructuralVarRemap {
     /// Look up an identity replacement previously stored for `var`.
-    pub fn get(&self, var: &MapValue) -> Result<Option<Any>> {
+    pub fn get(&self, var: &StructuralView) -> Result<Option<Any>> {
         let key = object_identity_key(var.raw())?;
         Ok(self.entries.get(&key).map(|entry| entry.result.clone()))
     }
 
     /// Store a descent result or an [`Unchanged`] marker for `var`.
-    pub fn set(&mut self, var: &MapValue, mutated_value: &Any) -> Result<()> {
+    pub fn set(&mut self, var: &StructuralView, mutated_value: &Any) -> 
Result<()> {
         let key = object_identity_key(var.raw())?;
         self.entries.insert(
             key,
@@ -1162,7 +1563,11 @@ pub trait StructuralMutator: Sized {
     /// Dispatch one borrowed value without modifying its source storage.
     ///
     /// The structural-mutation engine calls this hook for each value.
-    fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any>;
+    fn dispatch_mutate(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Any>;
 
     /// Dispatch one value for which the engine permits an in-place attempt.
     ///
@@ -1191,6 +1596,23 @@ pub trait StructuralMutator: Sized {
     /// Re-enter this mutator for an owned value, permitting reuse only when
     /// the converted value remains uniquely owned.
     fn maybe_inplace_mutate<T>(&mut self, value: T, def_region_kind: 
DefRegionKind) -> Result<Any>
+    where
+        T: Into<Any>,
+    {
+        self.maybe_inplace_mutate_with_mode(value, def_region_kind, 
InplaceMode::Allow)
+    }
+
+    /// Re-enter for an owned value with an explicit region and in-place 
permission.
+    ///
+    /// `Allow` checks uniqueness before dispatch; `Disallow` uses ordinary
+    /// mutation without checking uniqueness. An independently owned 
replacement
+    /// or a later explicit owned entry can establish a new permission 
boundary.
+    fn maybe_inplace_mutate_with_mode<T>(
+        &mut self,
+        value: T,
+        def_region_kind: DefRegionKind,
+        mode: InplaceMode,
+    ) -> Result<Any>
     where
         T: Into<Any>,
     {
@@ -1199,13 +1621,17 @@ pub trait StructuralMutator: Sized {
             self,
             *value.as_raw_ffi_any(),
             def_region_kind,
-            Permit::MaybeInPlace,
+            mode.permit(),
         )?;
         Ok(if is_unchanged(&result) { value } else { result })
     }
 
     /// Apply default non-in-place mutation to `value`'s children.
-    fn default_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any> {
+    fn default_mutate(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Any> {
         user_default_mutate(self, value.raw(), def_region_kind, Permit::Copy)
             .and_then(|result| resolve_result(result, value.raw()))
     }
@@ -1233,13 +1659,25 @@ pub trait StructuralMutator: Sized {
         &mut self,
         value: InplaceValue<'_>,
         def_region_kind: DefRegionKind,
+    ) -> Result<Any> {
+        self.default_maybe_inplace_mutate_with_mode(value, def_region_kind, 
InplaceMode::Allow)
+    }
+
+    /// Apply default mutation with a capability and an explicit permission.
+    ///
+    /// `Disallow` overrides the capability and uses the copy path. `Allow`
+    /// rechecks uniqueness because the handler may have retained an owning
+    /// alias. Consuming the capability prevents a borrow from it surviving 
this
+    /// call. Unlike the C++ default-descent API, safe Rust cannot simply trust
+    /// the earlier uniqueness check after arbitrary user code has run.
+    fn default_maybe_inplace_mutate_with_mode(
+        &mut self,
+        value: InplaceValue<'_>,
+        def_region_kind: DefRegionKind,
+        mode: InplaceMode,
     ) -> Result<Any> {
         let raw = value.raw();
-        let permit = if object_is_unique(raw) {
-            Permit::MaybeInPlace
-        } else {
-            Permit::Copy
-        };
+        let permit = mode.permit_if_unique(raw);
         user_default_mutate(self, raw, def_region_kind, permit)
             .and_then(|result| resolve_result(result, raw))
     }
@@ -1257,7 +1695,7 @@ pub trait StructuralMutator: Sized {
     /// Default non-in-place mutation with an unchanged-or-replacement result.
     fn default_mutate_result(
         &mut self,
-        value: &MapValue,
+        value: &StructuralView,
         kind: DefRegionKind,
     ) -> Result<UnchangedOr<Any>> {
         user_default_mutate(self, value.raw(), kind, Permit::Copy)
@@ -1283,36 +1721,52 @@ pub trait StructuralMutator: Sized {
         &mut self,
         value: InplaceValue<'_>,
         kind: DefRegionKind,
+    ) -> Result<UnchangedOr<Any>> {
+        self.default_maybe_inplace_mutate_with_mode_result(value, kind, 
InplaceMode::Allow)
+    }
+
+    /// Default mutation with a capability and permission, preserving 
unchanged.
+    ///
+    /// Uses the same ownership checks as 
[`Self::default_maybe_inplace_mutate_with_mode`].
+    fn default_maybe_inplace_mutate_with_mode_result(
+        &mut self,
+        value: InplaceValue<'_>,
+        kind: DefRegionKind,
+        mode: InplaceMode,
     ) -> Result<UnchangedOr<Any>> {
         let raw = value.raw();
-        let permit = if object_is_unique(raw) {
-            Permit::MaybeInPlace
-        } else {
-            Permit::Copy
-        };
+        let permit = mode.permit_if_unique(raw);
         user_default_mutate(self, raw, kind, 
permit).and_then(UnchangedOr::from_carrier)
     }
 
     /// Look up a FreeVar or DAG-node substitution from the active mutation.
-    fn var_remap_get(&mut self, var: &MapValue) -> Result<Option<Any>> {
+    fn var_remap_get(&mut self, var: &StructuralView) -> Result<Option<Any>> {
         invocation_var_remap_get(self, var)
     }
 
     /// Store a FreeVar or DAG-node substitution for the active mutation.
-    fn var_remap_set(&mut self, var: &MapValue, mutated_value: &Any) -> 
Result<()> {
+    fn var_remap_set(&mut self, var: &StructuralView, mutated_value: &Any) -> 
Result<()> {
         invocation_var_remap_set(self, var, mutated_value)
     }
 }
 
 impl<D: MutateDispatch> StructuralMutator for D {
     #[inline(always)]
-    fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any> {
+    fn dispatch_mutate(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Any> {
         let mut mutator = Mutator {
-            current: MapValue::from_raw(value.raw()),
             def_region_kind,
+            inplace_mode: InplaceMode::Disallow,
             _not_send_sync: PhantomData,
         };
-        match MutateDispatch::dispatch_mutate(self, value, &mut mutator) {
+        match MutateDispatch::dispatch_mutate_value(
+            self,
+            MutateValue::borrowed(value),
+            &mut mutator,
+        ) {
             Some(result) => result,
             None => user_default_mutate(self, value.raw(), def_region_kind, 
Permit::Copy),
         }
@@ -1325,11 +1779,15 @@ impl<D: MutateDispatch> StructuralMutator for D {
         def_region_kind: DefRegionKind,
     ) -> Result<Any> {
         let mut mutator = Mutator {
-            current: MapValue::from_raw(value.raw()),
             def_region_kind,
+            inplace_mode: InplaceMode::Allow,
             _not_send_sync: PhantomData,
         };
-        match MutateDispatch::dispatch_mutate(self, value.as_value(), &mut 
mutator) {
+        match MutateDispatch::dispatch_mutate_value(
+            self,
+            MutateValue::new(value.as_value(), InplaceMode::Allow),
+            &mut mutator,
+        ) {
             Some(result) => result,
             None => {
                 let raw = value.raw();
@@ -1350,8 +1808,9 @@ trait MutateCallbackStrategy<State, Link, Marker> {
     fn try_mutate<Driver>(
         driver: &mut Driver,
         callback_ptr: *const Link,
-        value: &MapValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
+        inplace_mode: InplaceMode,
     ) -> Option<MutateResult>
     where
         Driver: MutateContextDriver<State>;
@@ -1365,22 +1824,28 @@ where
     fn try_mutate<Driver>(
         driver: &mut Driver,
         callback_ptr: *const Link,
-        value: &MapValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
+        inplace_mode: InplaceMode,
     ) -> Option<MutateResult>
     where
         Driver: MutateContextDriver<State>,
     {
         let mut mutator = MutateContext::<State, dyn 
MutateContextDriver<State>> {
             driver,
-            current: MapValue::from_raw(value.raw()),
             def_region_kind,
+            inplace_mode,
             _state: PhantomData,
             _not_send_sync: PhantomData,
         };
         // SAFETY: The owning `Rc` or the direct callback's stack slot remains 
live
         // and is never modified through the driver during recursive reentry.
-        unsafe { (&*callback_ptr).try_mutate(value, &mut mutator) }
+        unsafe {
+            (&*callback_ptr).try_mutate(
+                &mut Some(MutateValue::new(value, inplace_mode)),
+                &mut mutator,
+            )
+        }
     }
 }
 
@@ -1388,8 +1853,9 @@ where
 fn try_mutate_callbacks<State, Link, Marker, Driver>(
     driver: &mut Driver,
     callback_ptr: *const Link,
-    value: &MapValue,
+    value: &StructuralView,
     def_region_kind: DefRegionKind,
+    inplace_mode: InplaceMode,
 ) -> Option<MutateResult>
 where
     Link: MutateChainLink<State, Marker>,
@@ -1401,6 +1867,7 @@ where
         callback_ptr,
         value,
         def_region_kind,
+        inplace_mode,
     )
 }
 
@@ -1410,13 +1877,18 @@ where
     Link::Strategy: MutateCallbackStrategy<State, Link, Marker>,
 {
     #[inline(always)]
-    fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any> {
+    fn dispatch_mutate(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Any> {
         let callback_ptr = Rc::as_ptr(&self.callbacks);
         match try_mutate_callbacks::<State, Link, Marker, _>(
             self,
             callback_ptr,
             value,
             def_region_kind,
+            InplaceMode::Disallow,
         ) {
             Some(result) => result,
             None => user_default_mutate(self, value.raw(), def_region_kind, 
Permit::Copy),
@@ -1435,6 +1907,7 @@ where
             callback_ptr,
             value.as_value(),
             def_region_kind,
+            InplaceMode::Allow,
         ) {
             Some(result) => result,
             None => self
@@ -1450,13 +1923,18 @@ where
     Link::Strategy: MutateCallbackStrategy<(), Link, Marker>,
 {
     #[inline(always)]
-    fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any> {
+    fn dispatch_mutate(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Any> {
         let callback_ptr = std::ptr::from_ref(self.callbacks);
         match try_mutate_callbacks::<(), Link, Marker, _>(
             self,
             callback_ptr,
             value,
             def_region_kind,
+            InplaceMode::Disallow,
         ) {
             Some(result) => result,
             None => user_default_mutate(self, value.raw(), def_region_kind, 
Permit::Copy),
@@ -1475,6 +1953,7 @@ where
             callback_ptr,
             value.as_value(),
             def_region_kind,
+            InplaceMode::Allow,
         ) {
             Some(result) => result,
             None => self
@@ -1499,38 +1978,43 @@ where
     }
 
     #[inline(always)]
-    fn mutate_raw(
-        &mut self,
-        raw: TVMFFIAny,
-        def_region_kind: DefRegionKind,
-        permit: Permit,
-    ) -> Result<Any> {
-        dispatch_user_raw(self, raw, def_region_kind, permit)
+    fn mutate_borrowed(&mut self, value: AnyView<'_>, kind: DefRegionKind) -> 
Result<Any> {
+        dispatch_user_raw(self, *value.as_raw_ffi_any(), kind, Permit::Copy)
     }
 
     #[inline(always)]
-    fn default_mutate_raw(
+    fn mutate_owned(&mut self, value: Any, kind: DefRegionKind, mode: 
InplaceMode) -> Result<Any> {
+        StructuralMutator::maybe_inplace_mutate_with_mode(self, value, kind, 
mode)
+    }
+
+    #[inline(always)]
+    fn default_mutate_borrowed(&mut self, value: AnyView<'_>, kind: 
DefRegionKind) -> Result<Any> {
+        default_mutate_driver(self, *value.as_raw_ffi_any(), kind, 
Permit::Copy)
+    }
+
+    fn default_mutate_value(
         &mut self,
-        raw: TVMFFIAny,
-        def_region_kind: DefRegionKind,
+        value: MutateValue<'_>,
+        kind: DefRegionKind,
+        mode: InplaceMode,
     ) -> Result<Any> {
-        default_mutate_driver(self, raw, def_region_kind, Permit::Copy)
+        let permit = value.permit(mode);
+        default_mutate_driver(self, value.value.raw(), kind, permit)
     }
 
     #[inline(always)]
-    fn var_remap_get_raw(&mut self, raw: TVMFFIAny) -> Result<Option<Any>> {
-        <Self as StructuralMutator>::var_remap_get(self, 
&MapValue::from_raw(raw))
+    fn var_remap_get(&mut self, var: &StructuralView) -> Result<Option<Any>> {
+        <Self as StructuralMutator>::var_remap_get(self, var)
     }
 
     #[inline(always)]
-    fn var_remap_set_raw(&mut self, raw: TVMFFIAny, mutated_value: &Any) -> 
Result<()> {
-        <Self as StructuralMutator>::var_remap_set(self, 
&MapValue::from_raw(raw), mutated_value)
+    fn var_remap_set(&mut self, var: &StructuralView, mutated_value: &Any) -> 
Result<()> {
+        <Self as StructuralMutator>::var_remap_set(self, var, mutated_value)
     }
 }
 
-#[doc(hidden)]
 #[derive(Clone, Copy, PartialEq, Eq)]
-pub enum Permit {
+enum Permit {
     Copy,
     MaybeInPlace,
 }
@@ -1562,7 +2046,7 @@ impl<D: MapDispatch> NativeMapper<D> {
         // excluded because converting those borrowed special values into an
         // Any performs normalization rather than a bitwise copy.
         if is_plain_inline(raw.type_index) {
-            let value = MapValue::from_raw(raw);
+            let value = StructuralView::from_raw(raw);
             return match self.dispatch.dispatch_map(&value, def_region_kind) {
                 Some(result) => {
                     let mapped = result?;
@@ -1599,7 +2083,7 @@ impl<D: MapDispatch> NativeMapper<D> {
     ) -> Result<Any> {
         match self.order {
             WalkOrder::PreOrder => {
-                let value = MapValue::from_raw(raw);
+                let value = StructuralView::from_raw(raw);
                 let Some(callback_result) = self.dispatch.dispatch_map(&value, 
def_region_kind)
                 else {
                     return self.default_map_current_raw(raw, def_region_kind, 
permit);
@@ -1628,7 +2112,7 @@ impl<D: MapDispatch> NativeMapper<D> {
                 } else {
                     *mapped.as_raw_ffi_any()
                 };
-                let value = MapValue::from_raw(mapped_raw);
+                let value = StructuralView::from_raw(mapped_raw);
                 match self.dispatch.dispatch_map(&value, def_region_kind) {
                     Some(result) => result,
                     None => Ok(mapped),
@@ -2067,7 +2551,10 @@ fn active_mutator() -> Result<StructuralMutatorHandle> {
     })
 }
 
-fn invocation_var_remap_get<U: Sized>(mutator: &mut U, var: &MapValue) -> 
Result<Option<Any>> {
+fn invocation_var_remap_get<U: Sized>(
+    mutator: &mut U,
+    var: &StructuralView,
+) -> Result<Option<Any>> {
     let active = active_mutator()?;
     let context = std::ptr::from_mut(mutator).cast::<c_void>();
     unsafe {
@@ -2082,7 +2569,7 @@ fn invocation_var_remap_get<U: Sized>(mutator: &mut U, 
var: &MapValue) -> Result
 
 fn invocation_var_remap_set<U: Sized>(
     mutator: &mut U,
-    var: &MapValue,
+    var: &StructuralView,
     mutated_value: &Any,
 ) -> Result<()> {
     let active = active_mutator()?;
@@ -2185,11 +2672,11 @@ impl<D: MapDispatch> MutationDriver for NativeMapper<D> 
{
     }
 
     fn var_remap_get_raw(&mut self, raw: TVMFFIAny) -> Result<Option<Any>> {
-        self.remap.get(&MapValue::from_raw(raw))
+        self.remap.get(&StructuralView::from_raw(raw))
     }
 
     fn var_remap_set_raw(&mut self, raw: TVMFFIAny, replacement: &Any) -> 
Result<()> {
-        self.remap.set(&MapValue::from_raw(raw), replacement)
+        self.remap.set(&StructuralView::from_raw(raw), replacement)
     }
 }
 
@@ -2204,11 +2691,11 @@ impl<U: StructuralMutator> MutationDriver for U {
     }
 
     fn var_remap_get_raw(&mut self, raw: TVMFFIAny) -> Result<Option<Any>> {
-        self.var_remap_get(&MapValue::from_raw(raw))
+        self.var_remap_get(&StructuralView::from_raw(raw))
     }
 
     fn var_remap_set_raw(&mut self, raw: TVMFFIAny, replacement: &Any) -> 
Result<()> {
-        self.var_remap_set(&MapValue::from_raw(raw), replacement)
+        self.var_remap_set(&StructuralView::from_raw(raw), replacement)
     }
 }
 
@@ -2480,7 +2967,7 @@ fn dispatch_user_raw<U: StructuralMutator>(
         mutator
             .dispatch_maybe_inplace_mutate(InplaceValue::from_raw(&mut 
scoped_raw), def_region_kind)
     } else {
-        mutator.dispatch_mutate(&MapValue::from_raw(raw), def_region_kind)
+        mutator.dispatch_mutate(&StructuralView::from_raw(raw), 
def_region_kind)
     };
     result.map_err(|error| with_value_context(error, raw))
 }
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs 
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 0fc337ad..9031de5f 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -35,7 +35,7 @@
 //! opt in with a trailing [`DefRegionKind`] argument, and a visitor receives
 //! and forwards it when descending.
 //!
-//! Underneath both, [`VisitValue`] provides borrowed matching for typed Rust
+//! Underneath both, [`StructuralView`] provides borrowed matching for typed 
Rust
 //! dispatch. Rust supplies a temporary `ffi.StructuralVisitor` ABI object so
 //! every type's registered `__s_visit__` hook can enumerate its children and
 //! call back into the active Rust visitor. Types without a hook fall back to
@@ -204,12 +204,10 @@ impl IntoVisitResult for Result<Option<VisitInterrupt>> {
 #[doc(hidden)]
 pub type WalkCallbackResult = Result<WalkResult>;
 
-/// A borrowed view of a raw tvm-ffi value passed to structural-visit 
callbacks.
-///
-/// Generated visitors match this value without taking ownership: borrowed
-/// object-node handlers use [`VisitValue::as_node`], while POD or object-ref
-/// value handlers use [`VisitValue::cast`].
-pub use super::structural_common::StructuralValue as VisitValue;
+pub use super::StructuralView;
+
+/// Compatibility name for [`StructuralView`].
+pub use super::StructuralView as VisitValue;
 
 enum NativeHalt {
     Interrupt(Any),
@@ -233,7 +231,7 @@ pub use policy::{DefaultVisitPolicy, VisitPolicy, 
WalkWithPolicy};
 /// reborrow the visitor, so mutable state cannot remain borrowed across them.
 pub struct VisitContext<'a, State> {
     driver: &'a mut dyn VisitContextDriver<State>,
-    current: VisitValue,
+    current: StructuralView,
     def_region_kind: DefRegionKind,
     _not_send_sync: PhantomData<Rc<()>>,
 }
@@ -265,7 +263,7 @@ impl<State> VisitContext<'_, State> {
     }
 
     /// Complete borrowed value active at this callback.
-    pub fn current(&self) -> &VisitValue {
+    pub fn current(&self) -> &StructuralView {
         &self.current
     }
 
@@ -352,7 +350,7 @@ impl<State> VisitContext<'_, State> {
 /// Use [`VisitCallbacks`] when the chain needs mutable state.
 #[diagnostic::on_unimplemented(
     message = "`{Self}` is not a supported `structural_visit` visitor",
-    note = "accepted visitors: `&mut V` where `V: StructuralVisitor`; an `Fn` 
callback over an FFI value type `T`, `&N` of an object node type, or 
`&VisitValue`, followed by `&mut VisitContext<'_, ()>`; or a tuple of up to 12 
such callbacks (tuples may nest)",
+    note = "accepted visitors: `&mut V` where `V: StructuralVisitor`; an `Fn` 
callback over an FFI value type `T`, `&N` of an object node type, or 
`&StructuralView`, followed by `&mut VisitContext<'_, ()>`; or a tuple of up to 
12 such callbacks (tuples may nest)",
     note = "callback arguments need explicit type annotations; use 
`VisitCallbacks::new(state, callbacks)` for ordinary mutable callback state"
 )]
 pub trait IntoVisitor<Marker> {
@@ -375,13 +373,13 @@ pub trait VisitChainLink<State, Marker>: 
visit_sealed::SealedLink<State, Marker>
     #[doc(hidden)]
     fn try_visit(
         &self,
-        value: &VisitValue,
+        value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Option<Result<Option<VisitInterrupt>>>;
 }
 
 mod visit_sealed {
-    use super::{IntoVisitResult, ObjectCore, VisitContext, VisitValue};
+    use super::{IntoVisitResult, ObjectCore, StructuralView, VisitContext};
 
     pub trait SealedLink<State, Marker> {}
 
@@ -405,7 +403,7 @@ mod visit_sealed {
     impl<F, State, O> SealedLink<State, super::ByVisitCatchAllLink> for F
     where
         F: for<'value, 'visitor, 'driver> Fn(
-            &'value VisitValue,
+            &'value StructuralView,
             &'visitor mut VisitContext<'driver, State>,
         ) -> O,
         O: IntoVisitResult,
@@ -424,7 +422,7 @@ where
 {
     fn try_visit(
         &self,
-        value: &VisitValue,
+        value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Option<Result<Option<VisitInterrupt>>> {
         value
@@ -447,7 +445,7 @@ where
 {
     fn try_visit(
         &self,
-        value: &VisitValue,
+        value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Option<Result<Option<VisitInterrupt>>> {
         value
@@ -462,14 +460,14 @@ pub enum ByVisitCatchAllLink {}
 impl<F, State, O> VisitChainLink<State, ByVisitCatchAllLink> for F
 where
     F: for<'value, 'visitor, 'driver> Fn(
-        &'value VisitValue,
+        &'value StructuralView,
         &'visitor mut VisitContext<'driver, State>,
     ) -> O,
     O: IntoVisitResult,
 {
     fn try_visit(
         &self,
-        value: &VisitValue,
+        value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Option<Result<Option<VisitInterrupt>>> {
         Some(self(value, visitor).into_visit_result())
@@ -495,7 +493,7 @@ macro_rules! impl_visit_chain_link {
         {
             fn try_visit(
                 &self,
-                value: &VisitValue,
+                value: &StructuralView,
                 visitor: &mut VisitContext<'_, State>,
             ) -> Option<Result<Option<VisitInterrupt>>> {
                 $(
@@ -629,7 +627,7 @@ pub use super::dispatch::{ByWalkDispatch, DispatchWalker, 
WalkDispatch};
 /// tuple. `Marker` distinguishes the supported callback shapes.
 #[diagnostic::on_unimplemented(
     message = "`{Self}` is not a supported `structural_walk` walker",
-    note = "accepted walkers: `&mut V` where `V: WalkDispatch`; a closure over 
`&VisitValue`, \
+    note = "accepted walkers: `&mut V` where `V: WalkDispatch`; a closure over 
`&StructuralView`, \
             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)",
@@ -651,10 +649,14 @@ pub struct ClosureWalker<F> {
 
 impl<F, O> NativeVisit for ClosureWalker<F>
 where
-    F: FnMut(&VisitValue) -> O,
+    F: FnMut(&StructuralView) -> O,
     O: IntoWalkResult,
 {
-    fn visit(&mut self, value: &VisitValue, _def_region_kind: DefRegionKind) 
-> Result<WalkResult> {
+    fn visit(
+        &mut self,
+        value: &StructuralView,
+        _def_region_kind: DefRegionKind,
+    ) -> Result<WalkResult> {
         (self.callback)(value).into_walk_result()
     }
 }
@@ -664,7 +666,7 @@ pub enum ByValueClosure {}
 
 impl<F, O> IntoWalker<ByValueClosure> for F
 where
-    F: FnMut(&VisitValue) -> O,
+    F: FnMut(&StructuralView) -> O,
     O: IntoWalkResult,
 {
     type Walker = ClosureWalker<F>;
@@ -681,10 +683,14 @@ pub struct ClosureKindWalker<F> {
 
 impl<F, O> NativeVisit for ClosureKindWalker<F>
 where
-    F: FnMut(&VisitValue, DefRegionKind) -> O,
+    F: FnMut(&StructuralView, DefRegionKind) -> O,
     O: IntoWalkResult,
 {
-    fn visit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) -> 
Result<WalkResult> {
+    fn visit(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<WalkResult> {
         (self.callback)(value, def_region_kind).into_walk_result()
     }
 }
@@ -694,7 +700,7 @@ pub enum ByValueKindClosure {}
 
 impl<F, O> IntoWalker<ByValueKindClosure> for F
 where
-    F: FnMut(&VisitValue, DefRegionKind) -> O,
+    F: FnMut(&StructuralView, DefRegionKind) -> O,
     O: IntoWalkResult,
 {
     type Walker = ClosureKindWalker<F>;
@@ -705,7 +711,7 @@ where
 
 /// One link in a first-match [`structural_walk`] callback chain.
 ///
-/// Supported links are typed values, borrowed object nodes, `&VisitValue`,
+/// Supported links are typed values, borrowed object nodes, `&StructuralView`,
 /// and mutable [`WalkDispatch`] implementations, optionally followed by
 /// [`DefRegionKind`]. Tuples hold up to 12 links and may be nested.
 pub trait WalkChainLink<Marker>: sealed::SealedLink<Marker> {
@@ -714,13 +720,13 @@ pub trait WalkChainLink<Marker>: 
sealed::SealedLink<Marker> {
     #[doc(hidden)]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult>;
 }
 
 mod sealed {
-    use super::{DefRegionKind, IntoWalkResult, ObjectCore, VisitValue, 
WalkDispatch};
+    use super::{DefRegionKind, IntoWalkResult, ObjectCore, StructuralView, 
WalkDispatch};
 
     pub trait SealedLink<Marker> {}
 
@@ -750,13 +756,13 @@ mod sealed {
     }
     impl<F, O> SealedLink<super::ByCatchAllLink> for F
     where
-        F: for<'a> FnMut(&'a VisitValue) -> O,
+        F: for<'a> FnMut(&'a StructuralView) -> O,
         O: IntoWalkResult,
     {
     }
     impl<F, O> SealedLink<super::ByCatchAllKindLink> for F
     where
-        F: for<'a> FnMut(&'a VisitValue, DefRegionKind) -> O,
+        F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
         O: IntoWalkResult,
     {
     }
@@ -775,7 +781,7 @@ where
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         _def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         value
@@ -796,7 +802,7 @@ where
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         value
@@ -817,7 +823,7 @@ where
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         _def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         value
@@ -838,7 +844,7 @@ where
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         value
@@ -852,13 +858,13 @@ pub enum ByCatchAllLink {}
 
 impl<F, O> WalkChainLink<ByCatchAllLink> for F
 where
-    F: for<'a> FnMut(&'a VisitValue) -> O,
+    F: for<'a> FnMut(&'a StructuralView) -> O,
     O: IntoWalkResult,
 {
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         _def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         Some(self(value).into_walk_result())
@@ -870,13 +876,13 @@ pub enum ByCatchAllKindLink {}
 
 impl<F, O> WalkChainLink<ByCatchAllKindLink> for F
 where
-    F: for<'a> FnMut(&'a VisitValue, DefRegionKind) -> O,
+    F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
     O: IntoWalkResult,
 {
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         Some(self(value, def_region_kind).into_walk_result())
@@ -893,7 +899,7 @@ impl<V: WalkDispatch> WalkChainLink<ByWalkDispatchLink> for 
&mut V {
     #[inline]
     fn try_call(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Option<WalkCallbackResult> {
         self.dispatch_walk(value, def_region_kind)
@@ -922,7 +928,11 @@ where
     Link: WalkChainLink<Marker>,
 {
     #[inline]
-    fn visit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) -> 
Result<WalkResult> {
+    fn visit(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<WalkResult> {
         self.link
             .try_call(value, def_region_kind)
             .unwrap_or(Ok(WalkResult::Advance))
@@ -944,7 +954,7 @@ macro_rules! impl_chain_link {
             #[inline]
             fn try_call(
                 &mut self,
-                value: &VisitValue,
+                value: &StructuralView,
                 def_region_kind: DefRegionKind,
             ) -> Option<WalkCallbackResult> {
                 $(
@@ -1004,7 +1014,7 @@ pub trait StructuralVisitor: Sized {
     /// Visit one value under the definition-region state active at it.
     fn visit(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>>;
 
@@ -1033,7 +1043,7 @@ pub trait StructuralVisitor: Sized {
     #[inline]
     fn default_visit_children(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         default_user_visit_children(self, value, def_region_kind)
@@ -1042,7 +1052,7 @@ pub trait StructuralVisitor: Sized {
 
 fn default_user_visit_children<V: StructuralVisitor>(
     visitor: &mut V,
-    value: &VisitValue,
+    value: &StructuralView,
     def_region_kind: DefRegionKind,
 ) -> Result<Option<VisitInterrupt>> {
     let raw = value.raw();
@@ -1056,7 +1066,7 @@ fn default_user_visit_children<V: StructuralVisitor>(
 fn try_visit_callbacks<State, Link, Marker>(
     driver: &mut impl VisitContextDriver<State>,
     callback_ptr: *const Link,
-    value: &VisitValue,
+    value: &StructuralView,
     def_region_kind: DefRegionKind,
 ) -> Result<Option<VisitInterrupt>>
 where
@@ -1064,7 +1074,7 @@ where
 {
     let mut visitor = VisitContext {
         driver,
-        current: VisitValue::from_raw(value.raw()),
+        current: StructuralView::from_raw(value.raw()),
         def_region_kind,
         _not_send_sync: PhantomData,
     };
@@ -1083,7 +1093,7 @@ where
 {
     fn visit(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         let callback_ptr = Rc::as_ptr(&self.callbacks);
@@ -1092,7 +1102,7 @@ where
 
     fn default_visit_children(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         let Some(policy) = self.policy.as_ref().map(Rc::clone) else {
@@ -1113,7 +1123,7 @@ where
 {
     fn visit(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         let callback_ptr = std::ptr::from_ref(self.callbacks);
@@ -1155,7 +1165,7 @@ where
     ) -> Result<Option<VisitInterrupt>> {
         <Self as StructuralVisitor>::default_visit_children(
             self,
-            &VisitValue::from_raw(raw),
+            &StructuralView::from_raw(raw),
             def_region_kind,
         )
     }
@@ -1166,11 +1176,15 @@ where
 pub trait NativeVisit: Sized {
     const CUSTOM_DESCENT: bool = false;
 
-    fn visit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) -> 
Result<WalkResult>;
+    fn visit(
+        &mut self,
+        value: &StructuralView,
+        def_region_kind: DefRegionKind,
+    ) -> Result<WalkResult>;
 
     fn default_visit_children<const PRE_ORDER: bool>(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         default_walk_children::<Self, PRE_ORDER>(self, value, def_region_kind)
@@ -1179,7 +1193,7 @@ pub trait NativeVisit: Sized {
 
 fn default_walk_children<V: NativeVisit, const PRE_ORDER: bool>(
     visitor: &mut V,
-    value: &VisitValue,
+    value: &StructuralView,
     def_region_kind: DefRegionKind,
 ) -> Result<Option<VisitInterrupt>> {
     let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
@@ -1218,7 +1232,7 @@ impl<V: StructuralVisitor> ChildVisit for 
UserChildren<'_, V> {
         }
         match self
             .visitor
-            .visit(&VisitValue::from_raw(child), def_region_kind)
+            .visit(&StructuralView::from_raw(child), def_region_kind)
         {
             Ok(None) => Ok(()),
             Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
@@ -1238,7 +1252,7 @@ fn visit_raw<V: NativeVisit, const PRE_ORDER: bool>(
         return Ok(());
     }
 
-    let visit_value = VisitValue::from_raw(value);
+    let visit_value = StructuralView::from_raw(value);
     if PRE_ORDER {
         match visitor.visit(&visit_value, def_region_kind) {
             Ok(WalkResult::Advance) => {}
@@ -1679,7 +1693,7 @@ unsafe fn runtime_walk<V: NativeVisit, const PRE_ORDER: 
bool>(
     if !V::CUSTOM_DESCENT && raw.type_index < 
TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
         let visitor = &mut *context.cast::<V>();
         if PRE_ORDER {
-            match visitor.visit(&VisitValue::from_raw(raw), def_region_kind) {
+            match visitor.visit(&StructuralView::from_raw(raw), 
def_region_kind) {
                 Ok(WalkResult::Advance) => {}
                 Ok(WalkResult::Skip) => return Ok(()),
                 Ok(WalkResult::Interrupt) => return 
Err(NativeHalt::Interrupt(Any::new())),
@@ -1698,7 +1712,7 @@ unsafe fn runtime_walk<V: NativeVisit, const PRE_ORDER: 
bool>(
         // Post-order inline values have no children unless their type
         // registered a visit hook. Handle the common case directly here.
         if !has_registered_visit_hook(raw.type_index) {
-            return match visitor.visit(&VisitValue::from_raw(raw), 
def_region_kind) {
+            return match visitor.visit(&StructuralView::from_raw(raw), 
def_region_kind) {
                 Ok(WalkResult::Advance | WalkResult::Skip) => Ok(()),
                 Ok(WalkResult::Interrupt) => 
Err(NativeHalt::Interrupt(Any::new())),
                 Ok(WalkResult::InterruptWith(payload)) => 
Err(NativeHalt::Interrupt(payload)),
@@ -1718,7 +1732,7 @@ unsafe fn runtime_user_visit<V: StructuralVisitor>(
     if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
         return Ok(());
     }
-    match (&mut *context.cast::<V>()).visit(&VisitValue::from_raw(raw), 
def_region_kind) {
+    match (&mut *context.cast::<V>()).visit(&StructuralView::from_raw(raw), 
def_region_kind) {
         Ok(None) => Ok(()),
         Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
         Err(error) => Err(NativeHalt::Error(error)),
@@ -1940,7 +1954,7 @@ where
 ///
 /// `walker` is anything implementing [`IntoWalker`]: a `&mut` reference to a
 /// stateful [`WalkDispatch`] walker (`#[dispatch(walk)]`), a bare closure
-/// in any [`WalkChainLink`] shape (catch-all `&VisitValue`, typed, or node,
+/// in any [`WalkChainLink`] shape (catch-all `&StructuralView`, typed, or 
node,
 /// with an optional trailing [`DefRegionKind`]), or a tuple of such
 /// callbacks tried in order — the C++ callback overloads and variadic
 /// chain. The walker owns recursion: the handler runs once per value,
diff --git a/rust/tvm-ffi/src/extra/structural_visit/policy.rs 
b/rust/tvm-ffi/src/extra/structural_visit/policy.rs
index e2372ee2..9b6b3d1c 100644
--- a/rust/tvm-ffi/src/extra/structural_visit/policy.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit/policy.rs
@@ -37,7 +37,7 @@ pub trait VisitPolicy<State> {
     /// or when a matched callback requests default descent.
     fn default_visit(
         &self,
-        value: &VisitValue,
+        value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Result<Option<VisitInterrupt>>;
 }
@@ -48,7 +48,7 @@ pub struct DefaultVisitPolicy;
 impl<State> VisitPolicy<State> for DefaultVisitPolicy {
     fn default_visit(
         &self,
-        _value: &VisitValue,
+        _value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Result<Option<VisitInterrupt>> {
         visitor.visit_children()
@@ -60,7 +60,7 @@ impl<State, Outer: VisitPolicy<State>, Inner: 
VisitPolicy<State>> VisitPolicy<St
 {
     fn default_visit(
         &self,
-        value: &VisitValue,
+        value: &StructuralView,
         visitor: &mut VisitContext<'_, State>,
     ) -> Result<Option<VisitInterrupt>> {
         let kind = visitor.def_region_kind();
@@ -79,14 +79,14 @@ impl<State, Outer: VisitPolicy<State>, Inner: 
VisitPolicy<State>> VisitPolicy<St
 pub(super) fn visit_with_policy<State>(
     driver: &mut dyn VisitContextDriver<State>,
     policy: &impl VisitPolicy<State>,
-    value: &VisitValue,
+    value: &StructuralView,
     def_region_kind: DefRegionKind,
 ) -> Result<Option<VisitInterrupt>> {
     policy.default_visit(
         value,
         &mut VisitContext {
             driver,
-            current: VisitValue::from_raw(value.raw()),
+            current: StructuralView::from_raw(value.raw()),
             def_region_kind,
             _not_send_sync: PhantomData,
         },
@@ -115,7 +115,12 @@ impl<State, Policy: VisitPolicy<State>> 
VisitContextDriver<State>
         raw: TVMFFIAny,
         kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
-        visit_with_policy(self.driver, self.policy, 
&VisitValue::from_raw(raw), kind)
+        visit_with_policy(
+            self.driver,
+            self.policy,
+            &StructuralView::from_raw(raw),
+            kind,
+        )
     }
 }
 
@@ -141,7 +146,7 @@ impl<State, V: StructuralVisitor + 
VisitCallbackState<State>> VisitContextDriver
         kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         // Bypass the current policy; children still re-enter the complete 
visitor.
-        default_user_visit_children(self.visitor, &VisitValue::from_raw(raw), 
kind)
+        default_user_visit_children(self.visitor, 
&StructuralView::from_raw(raw), kind)
     }
 }
 
@@ -213,7 +218,7 @@ impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>> 
NativeVisit
 {
     const CUSTOM_DESCENT: bool = true;
 
-    fn visit(&mut self, value: &VisitValue, kind: DefRegionKind) -> 
Result<WalkResult> {
+    fn visit(&mut self, value: &StructuralView, kind: DefRegionKind) -> 
Result<WalkResult> {
         self.walker
             .dispatch_walk(value, kind)
             .unwrap_or(Ok(WalkResult::Advance))
@@ -221,7 +226,7 @@ impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>> 
NativeVisit
 
     fn default_visit_children<const PRE_ORDER: bool>(
         &mut self,
-        value: &VisitValue,
+        value: &StructuralView,
         kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
         let policy = Rc::clone(&self.policy);
@@ -267,6 +272,6 @@ impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>, 
const PRE_ORDER: bool>
         raw: TVMFFIAny,
         kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
-        default_walk_children::<_, PRE_ORDER>(self.visitor, 
&VisitValue::from_raw(raw), kind)
+        default_walk_children::<_, PRE_ORDER>(self.visitor, 
&StructuralView::from_raw(raw), kind)
     }
 }
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index dc44cedc..4bb5672c 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -49,9 +49,9 @@ pub use crate::error::{
 };
 pub use crate::extra::module::Module;
 pub use crate::extra::structural_mutate::{
-    structural_map, structural_mutate, CallbackMutator, InplaceValue, 
IntoMapResult, IntoMapper,
-    IntoMutator, MapChainLink, MapDispatch, MapValue, MutateCallbacks, 
MutateChainLink,
-    MutateContext, MutateDispatch, Mutator, StructuralMutator, 
StructuralVarRemap,
+    structural_map, structural_mutate, CallbackMutator, InplaceMode, 
InplaceValue, IntoMapResult,
+    IntoMapper, IntoMutator, MapChainLink, MapDispatch, MapValue, 
MutateCallbacks, MutateChainLink,
+    MutateContext, MutateDispatch, MutateValue, Mutator, StructuralMutator, 
StructuralVarRemap,
 };
 pub use crate::extra::structural_visit::{
     structural_visit, structural_walk, DefRegionKind, DefaultVisitPolicy, 
IntoVisitor,
@@ -60,6 +60,7 @@ pub use crate::extra::structural_visit::{
     WalkWithPolicy,
 };
 pub use crate::extra::unchanged::{Unchanged, UnchangedOr};
+pub use crate::extra::StructuralView;
 pub use crate::function::Function;
 pub use crate::object::ObjectRefCast;
 pub use crate::object::{
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 14670061..b7647603 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -23,10 +23,10 @@ use tvm_ffi::function::FunctionObj;
 use tvm_ffi::object::ObjectRef;
 use tvm_ffi::{
     dispatch, structural_map, structural_mutate, Any, AnyView, Array, 
CallbackMutator,
-    DefRegionKind, Error, FieldGetter, Function, InplaceValue, Map, 
MapDispatch, MapValue,
-    MutateCallbacks, Mutator, Object, ObjectArc, ObjectRefCore, Result, String 
as FfiString,
-    StructuralMutator, StructuralVarRemap, TypeIndex, Unchanged, UnchangedOr, 
WalkOrder,
-    RUNTIME_ERROR,
+    DefRegionKind, Error, FieldGetter, Function, InplaceMode, InplaceValue, 
Map, MapDispatch,
+    MapValue, MutateCallbacks, MutateValue, Mutator, Object, ObjectArc, 
ObjectRefCore, Result,
+    String as FfiString, StructuralMutator, StructuralVarRemap, TypeIndex, 
Unchanged, UnchangedOr,
+    WalkOrder, RUNTIME_ERROR,
 };
 
 struct IncrementIntegers;
@@ -329,6 +329,336 @@ fn 
user_mutator_recursive_entries_reenter_the_same_mutator() {
     assert_eq!(mutated.get(0).unwrap(), 2);
 }
 
+#[test]
+fn default_mutation_mode_preserves_ownership_and_unchanged_results() {
+    struct Controlled {
+        mode: InplaceMode,
+        increment: bool,
+    }
+    impl StructuralMutator for Controlled {
+        fn dispatch_mutate(&mut self, value: &MapValue, _: DefRegionKind) -> 
Result<Any> {
+            let integer = value.cast::<i64>().unwrap();
+            Ok(if self.increment {
+                Any::from(integer + 1)
+            } else {
+                Unchanged.into()
+            })
+        }
+        fn dispatch_maybe_inplace_mutate(
+            &mut self,
+            value: InplaceValue<'_>,
+            kind: DefRegionKind,
+        ) -> Result<Any> {
+            if self.increment {
+                self.default_maybe_inplace_mutate_with_mode(value, kind, 
self.mode)
+            } else {
+                let result =
+                    self.default_maybe_inplace_mutate_with_mode_result(value, 
kind, self.mode)?;
+                assert!(result.is_unchanged());
+                Ok(result.into())
+            }
+        }
+    }
+    for mode in [InplaceMode::Disallow, InplaceMode::Allow] {
+        for increment in [false, true] {
+            let root = Array::new(vec![1_i64]);
+            let pointer = array_pointer(&root);
+            let result = structural_mutate(root, &mut Controlled { mode, 
increment })
+                .and_then(Array::<i64>::try_from)
+                .unwrap();
+            assert_eq!(
+                array_pointer(&result) == pointer,
+                mode == InplaceMode::Allow || !increment
+            );
+            assert_eq!(result.get(0).unwrap(), if increment { 2 } else { 1 });
+        }
+    }
+}
+
+#[test]
+fn owned_entry_mode_is_forwarded_by_generated_and_closure_callbacks() {
+    struct Entry {
+        mode: InplaceMode,
+        pointer: usize,
+    }
+    #[dispatch(mutate)]
+    impl Entry {
+        fn mutate_bool(&mut self, _: bool, ctx: &mut Mutator) -> Result<Any> {
+            let child = Array::new(vec![1_i64]);
+            self.pointer = array_pointer(&child) as usize;
+            ctx.maybe_inplace_mutate_with_mode(self, child, 
DefRegionKind::Pattern, self.mode)
+        }
+        fn mutate_integer(&mut self, value: i64, ctx: &mut Mutator) -> i64 {
+            assert_eq!(ctx.def_region_kind(), DefRegionKind::Pattern);
+            value + 1
+        }
+    }
+    assert_eq!(InplaceMode::default(), InplaceMode::Disallow);
+    for mode in [InplaceMode::Disallow, InplaceMode::Allow] {
+        let mut entry = Entry { mode, pointer: 0 };
+        let result = structural_mutate(true, &mut entry)
+            .and_then(Array::<i64>::try_from)
+            .unwrap();
+        assert_eq!(
+            array_pointer(&result) as usize == entry.pointer,
+            mode == InplaceMode::Allow
+        );
+        assert_eq!(result.get(0).unwrap(), 2);
+
+        let pointer = Cell::new(0usize);
+        let result = structural_mutate(
+            true,
+            (
+                |_: bool, ctx: &mut CallbackMutator| {
+                    let child = Array::new(vec![1_i64]);
+                    pointer.set(array_pointer(&child) as usize);
+                    ctx.maybe_inplace_mutate_with_mode(child, 
DefRegionKind::Pattern, mode)
+                },
+                |value: i64, ctx: &mut CallbackMutator| {
+                    assert_eq!(ctx.def_region_kind(), DefRegionKind::Pattern);
+                    value + 1
+                },
+            ),
+        )
+        .and_then(Array::<i64>::try_from)
+        .unwrap();
+        assert_eq!(
+            array_pointer(&result) as usize == pointer.get(),
+            mode == InplaceMode::Allow
+        );
+        assert_eq!(result.get(0).unwrap(), 2);
+    }
+}
+
+#[test]
+fn consuming_callbacks_forward_permissions_without_temporary_owners() {
+    struct Forward {
+        requested: InplaceMode,
+        retain: bool,
+        alias: Option<Any>,
+        modes: Vec<InplaceMode>,
+    }
+    impl Forward {
+        fn observe(&mut self, value: &MutateValue<'_, Array<Any>>) {
+            self.modes.push(value.inplace_mode());
+            let node = value
+                .as_node::<tvm_ffi::collections::array::ArrayObj>()
+                .unwrap();
+            assert_eq!(node.size, 1);
+            // A temporary typed handle must not permanently revoke permission.
+            let temporary = value.cast::<Array<Any>>().unwrap();
+            if self.retain && self.alias.is_none() {
+                self.alias = Some(temporary.clone().into());
+            }
+            drop(temporary);
+        }
+    }
+    #[dispatch(mutate)]
+    impl Forward {
+        fn mutate_string(&mut self, _: MutateValue<'_, FfiString>) -> Any {
+            panic!("typed miss must preserve the capability for the next 
handler")
+        }
+        fn mutate_array(
+            &mut self,
+            value: MutateValue<'_, Array<Any>>,
+            ctx: &mut Mutator,
+            mode: InplaceMode,
+        ) -> Result<UnchangedOr<Any>> {
+            assert_eq!(mode, ctx.inplace_mode());
+            assert_eq!(mode, value.inplace_mode());
+            self.observe(&value);
+            ctx.default_mutate_with_mode_result(self, value, self.requested)
+        }
+        fn mutate_integer(&mut self, value: i64, ctx: &mut Mutator, mode: 
InplaceMode) -> i64 {
+            assert_eq!(mode, InplaceMode::Disallow);
+            assert_eq!(mode, ctx.inplace_mode());
+            value + 1
+        }
+    }
+    use InplaceMode::{Allow, Disallow};
+    for (requested, shared, retain) in [
+        (Allow, false, false),
+        (Disallow, false, false),
+        (Allow, true, false),
+        (Allow, false, true),
+    ] {
+        for generated in [true, false] {
+            let child = Array::new(vec![1_i64]);
+            let child_ptr = array_pointer(&child);
+            let root = Array::new(vec![child]);
+            let root_ptr = array_pointer(&root);
+            let original = shared.then(|| root.clone());
+            let mut state = Forward {
+                requested,
+                retain,
+                alias: None,
+                modes: vec![],
+            };
+            let result = if generated {
+                structural_mutate(root, &mut state).unwrap()
+            } else {
+                let miss = |_: MutateValue<'_, FfiString>,
+                            _: &mut CallbackMutator<Forward>|
+                 -> Any { panic!("typed miss must continue") };
+                let increment = |value: i64, _: &mut CallbackMutator<Forward>| 
value + 1;
+                let descend = |value: MutateValue<'_, Array<Any>>,
+                               ctx: &mut CallbackMutator<Forward>| {
+                    assert_eq!(value.inplace_mode(), ctx.inplace_mode());
+                    ctx.state_mut().observe(&value);
+                    let requested = ctx.state().requested;
+                    ctx.default_mutate_with_mode(value, requested)
+                };
+                let mut callbacks = MutateCallbacks::new(state, (miss, 
(increment, descend)));
+                let result = structural_mutate(root, &mut callbacks).unwrap();
+                state = callbacks.into_state();
+                result
+            };
+            let result = Array::<Array<i64>>::try_from(result).unwrap();
+            let child = result.get(0).unwrap();
+            let reuse = requested == Allow && !shared && !retain;
+            assert_eq!(
+                state.modes,
+                vec![
+                    if shared { Disallow } else { Allow },
+                    if reuse { Allow } else { Disallow }
+                ]
+            );
+            assert_eq!(array_pointer(&result) == root_ptr, reuse);
+            assert_eq!(array_pointer(&child) == child_ptr, reuse);
+            assert_eq!(child.get(0).unwrap(), 2);
+            for alias in 
original.map(Any::from).into_iter().chain(state.alias) {
+                let alias = Array::<Array<i64>>::try_from(alias).unwrap();
+                assert_eq!(alias.get(0).unwrap().get(0).unwrap(), 1);
+            }
+        }
+    }
+}
+
+#[test]
+fn consuming_default_descent_transfers_between_contexts_without_node_borrows() 
{
+    struct Inner<'a> {
+        value: Option<MutateValue<'a>>,
+        preserve_unchanged: bool,
+        result: Any,
+    }
+    #[dispatch(mutate)]
+    impl Inner<'_> {
+        fn mutate_bool(&mut self, _: bool, ctx: &mut Mutator) -> Result<Any> {
+            let value = self.value.take().unwrap();
+            self.result = if self.preserve_unchanged {
+                ctx.default_maybe_inplace_mutate_result(self, value)?.into()
+            } else {
+                ctx.default_maybe_inplace_mutate(self, value)?
+            };
+            Ok(Any::new())
+        }
+        fn mutate_integer(&mut self, value: i64) -> i64 {
+            value + 1
+        }
+    }
+    fn transfer(
+        value: MutateValue<'_>,
+        generated: bool,
+        preserve: bool,
+        retain: bool,
+    ) -> Result<Any> {
+        let alias = retain.then(|| value.cast::<Array<i64>>().unwrap());
+        assert_eq!(value.inplace_mode(), InplaceMode::Allow);
+        // Carry the result back to the array callback: Unchanged belongs to
+        // that input, not to the nested traversal's boolean root.
+        let mut inner = Inner {
+            value: Some(value),
+            preserve_unchanged: preserve,
+            result: Any::new(),
+        };
+        let result = if generated {
+            structural_mutate(true, &mut inner)?;
+            inner.result
+        } else {
+            let mut callbacks = MutateCallbacks::new(
+                inner,
+                (
+                    |_: bool, ctx: &mut CallbackMutator<Inner<'_>>| -> 
Result<Any> {
+                        let value = ctx.state_mut().value.take().unwrap();
+                        let result = if ctx.state().preserve_unchanged {
+                            
ctx.default_maybe_inplace_mutate_result(value)?.into()
+                        } else {
+                            ctx.default_maybe_inplace_mutate(value)?
+                        };
+                        ctx.state_mut().result = result;
+                        Ok(Any::new())
+                    },
+                    |value: i64, _: &mut CallbackMutator<Inner<'_>>| value + 1,
+                ),
+            );
+            structural_mutate(true, &mut callbacks)?;
+            callbacks.into_state().result
+        };
+        if let Some(alias) = alias {
+            assert_eq!(alias.get(0)?, 1);
+        }
+        Ok(result)
+    }
+    struct Outer {
+        preserve: bool,
+        retain: bool,
+    }
+    #[dispatch(mutate)]
+    impl Outer {
+        fn mutate_any(&mut self, value: MutateValue<'_>) -> Result<Any> {
+            transfer(value, true, self.preserve, self.retain)
+        }
+    }
+    for preserve in [false, true] {
+        for retain in [false, true] {
+            for generated in [false, true] {
+                let root = Array::new(vec![1_i64]);
+                let pointer = array_pointer(&root);
+                let result = if generated {
+                    structural_mutate(root, &mut Outer { preserve, retain })
+                } else {
+                    structural_mutate(root, |value: MutateValue<'_>, _: &mut 
CallbackMutator| {
+                        transfer(value, false, preserve, retain)
+                    })
+                }
+                .and_then(Array::<i64>::try_from)
+                .unwrap();
+                assert_eq!(array_pointer(&result) == pointer, !retain);
+                assert_eq!(result.get(0).unwrap(), 2);
+            }
+        }
+    }
+}
+
+#[test]
+fn consuming_default_descent_preserves_unchanged_and_propagates_errors() {
+    for fail in [false, true] {
+        let root = Array::new(vec![1_i64]);
+        let pointer = array_pointer(&root);
+        let result = structural_mutate(
+            root,
+            |value: MutateValue<'_>, ctx: &mut CallbackMutator| -> 
Result<UnchangedOr<Any>> {
+                if value.cast::<i64>().is_some() {
+                    return if fail {
+                        Err(Error::new(RUNTIME_ERROR, "child failed", ""))
+                    } else {
+                        Ok(UnchangedOr::unchanged())
+                    };
+                }
+                let result = ctx.default_maybe_inplace_mutate_result(value)?;
+                assert!(result.is_unchanged());
+                Ok(result)
+            },
+        );
+        if fail {
+            assert!(result.err().unwrap().to_string().contains("child 
failed"));
+        } else {
+            let result = Array::<i64>::try_from(result.unwrap()).unwrap();
+            assert_eq!(array_pointer(&result), pointer);
+        }
+    }
+}
+
 #[test]
 fn reflected_fields_use_shallow_copy_and_setters() {
     let source = reflected_object();
@@ -583,7 +913,7 @@ impl GeneratedMapper {
         Any::from(value + 1)
     }
 
-    fn map_any(&mut self, value: &MapValue) -> Result<Any> {
+    fn map_any(&mut self, value: &tvm_ffi::StructuralView) -> Result<Any> {
         self.catch_all += 1;
         Ok(value.to_owned())
     }
@@ -696,15 +1026,20 @@ fn generated_mutate_dispatch_recurses_through_context() {
 
 #[derive(Default)]
 struct GeneratedDefaultingDispatch {
+    preserve_unchanged: bool,
     arrays: usize,
     integers: Vec<i64>,
 }
 
 #[dispatch(mutate)]
 impl GeneratedDefaultingDispatch {
-    fn mutate_array(&mut self, _array: Array<i64>, mutator: &mut Mutator) -> 
Result<Any> {
+    fn mutate_array(&mut self, array: Array<i64>, mutator: &mut Mutator) -> 
Result<Any> {
         self.arrays += 1;
-        mutator.default_mutate(self)
+        if self.preserve_unchanged {
+            mutator.default_mutate_result(self, &array).map(Into::into)
+        } else {
+            mutator.default_mutate(self, &array)
+        }
     }
 
     fn mutate_integer(&mut self, value: i64) -> Any {
@@ -715,14 +1050,19 @@ impl GeneratedDefaultingDispatch {
 
 #[test]
 fn generated_mutate_dispatch_can_default_recurse_from_a_typed_handler() {
-    let mut mutator = GeneratedDefaultingDispatch::default();
-    let mutated = structural_mutate(Array::new(vec![1i64, 2]), &mut mutator)
-        .and_then(Array::<i64>::try_from)
-        .unwrap();
+    for preserve_unchanged in [false, true] {
+        let mut mutator = GeneratedDefaultingDispatch {
+            preserve_unchanged,
+            ..Default::default()
+        };
+        let mutated = structural_mutate(Array::new(vec![1i64, 2]), &mut 
mutator)
+            .and_then(Array::<i64>::try_from)
+            .unwrap();
 
-    assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2, 3]);
-    assert_eq!(mutator.arrays, 1);
-    assert_eq!(mutator.integers, vec![1, 2]);
+        assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2, 3]);
+        assert_eq!(mutator.arrays, 1);
+        assert_eq!(mutator.integers, vec![1, 2]);
+    }
 }
 
 #[test]
@@ -832,7 +1172,7 @@ fn recursive_mutate_returns_unchanged_or_a_replacement() {
         }
 
         // Recurse into containers. Keep the unchanged marker if no child 
changed.
-        mutator.default_mutate_result()
+        mutator.default_mutate_result(value)
     }
 
     // No rewrite: the public entry resolves unchanged to the original array.
@@ -1061,11 +1401,11 @@ fn stateful_mutate_integer(value: i64, mutator: &mut 
CallbackMutator<CallbackMut
 }
 
 fn stateful_mutate_default(
-    _value: &MapValue,
+    value: &tvm_ffi::StructuralView,
     mutator: &mut CallbackMutator<CallbackMutateStats>,
 ) -> Result<Any> {
     mutator.state_mut().defaults += 1;
-    mutator.default_mutate()
+    mutator.default_mutate(value)
 }
 
 #[test]
@@ -1103,13 +1443,12 @@ fn stateful_mutate_recursive(
     value: &MapValue,
     mutator: &mut CallbackMutator<CallbackMutateDepth>,
 ) -> Result<Any> {
-    assert_eq!(mutator.current().type_index(), value.type_index());
     {
         let state = mutator.state_mut();
         state.current += 1;
         state.maximum = state.maximum.max(state.current);
     }
-    let mutated = mutator.default_mutate()?;
+    let mutated = mutator.default_mutate(value)?;
     {
         let state = mutator.state_mut();
         state.current -= 1;
@@ -1137,7 +1476,7 @@ fn 
callback_mutator_state_can_change_around_recursive_reborrows() {
 }
 
 #[test]
-fn callback_mutate_current_default_is_repeatable_copy_path() {
+fn callback_mutate_explicit_default_is_repeatable_copy_path() {
     let root = Array::new(vec![1i64, 2]);
     let root_pointer = array_pointer(&root);
     let defaults = Cell::new(0);
@@ -1145,10 +1484,10 @@ fn 
callback_mutate_current_default_is_repeatable_copy_path() {
         root,
         (
             |value: i64, _mutator: &mut CallbackMutator| Any::from(value + 1),
-            |_value: &MapValue, mutator: &mut CallbackMutator| -> Result<Any> {
+            |value: &MapValue, mutator: &mut CallbackMutator| -> Result<Any> {
                 defaults.set(defaults.get() + 1);
-                let first = mutator.default_mutate()?;
-                let second = mutator.default_mutate()?;
+                let first = mutator.default_mutate(value)?;
+                let second = mutator.default_mutate(value)?;
                 assert_ne!(any_object_pointer(&first), 
any_object_pointer(&second));
                 Ok(first)
             },
@@ -1182,9 +1521,9 @@ fn 
callback_mutate_match_is_final_and_same_fn_can_reenter() {
     let calls = Cell::new(0);
     let mutated = structural_mutate(
         Array::new(vec![1i64, 2]),
-        |_value: &MapValue, mutator: &mut CallbackMutator| {
+        |value: &MapValue, mutator: &mut CallbackMutator| {
             calls.set(calls.get() + 1);
-            mutator.default_mutate()
+            mutator.default_mutate(value)
         },
     )
     .and_then(Array::<i64>::try_from)
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs 
b/rust/tvm-ffi/tests/test_structural_visit.rs
index 9e73b206..e1b42cba 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -1009,7 +1009,7 @@ impl GenericDispatchProbe {
         WalkResult::Advance
     }
 
-    fn walk_any(&mut self, _value: &VisitValue) -> WalkResult {
+    fn walk_any(&mut self, _value: &tvm_ffi::StructuralView) -> WalkResult {
         self.catch_all += 1;
         WalkResult::Advance
     }

Reply via email to