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 2e7dcacb [Rust] Use Result<UnchangedOr<Any>> as the structural map and 
mutate result (#925)
2e7dcacb is described below

commit 2e7dcacb6f21dcc3cc9603493f06f3760d9fa7e8
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Nov 4 15:36:19 2026 -0500

    [Rust] Use Result<UnchangedOr<Any>> as the structural map and mutate result 
(#925)
    
    Rust structural map and mutate dispatchers now produce
    `Result<UnchangedOr<Any>>`, the same type that `mutate` and
    `default_mutate` return and that the C++ `Mutate` returns. A handler can
    return the result of recursion directly, and keeping a value is written
    explicitly as `Unchanged.into()`.
    
    - `structural_map`, `structural_mutate`, and the mutation engine require
    dispatchers producing `Option<Result<UnchangedOr<Any>>>`.
    `#[dispatch(map, ...)]` and `#[dispatch(mutate, ...)]` declare
    `Result<UnchangedOr<Any>>`. The engine still carries the result as an
    `Any`, since `UnchangedOr` is a transparent wrapper.
    - `UnchangedOr<Any>` converts from `Unchanged`, from managed references
    and scalars (a replacement), and from typed `UnchangedOr<T>` results, so
    `Unchanged.into()`, `expr.into()`, and `typed_result.into()` work in
    handlers. An `Any` replacement uses `UnchangedOr::changed`.
    - When a handler's return type and the declared result are both written
    as `Result<..>`, `#[dispatch]` converts the success value with `Into`,
    so handlers may return `Result<UnchangedOr<Expr>>` or `Result<i64>`.
    - Closure callbacks keep accepting `Unchanged`, `UnchangedOr<T>`, and
    replacement values, optionally wrapped in `Result`. `IntoMutateResult`
    now produces `Result<UnchangedOr<Any>>`.
    - The Rust guide, doc examples, and tests use the new result type.
---
 docs/guides/rust_lang_guide.rst              |  62 ++--
 rust/tvm-ffi-macros/src/dispatch.rs          |  17 +-
 rust/tvm-ffi-macros/src/lib.rs               |  10 +-
 rust/tvm-ffi/src/extra/dispatch.rs           |   3 +-
 rust/tvm-ffi/src/extra/structural_map.rs     |  43 ++-
 rust/tvm-ffi/src/extra/structural_mutate.rs  |  67 +++--
 rust/tvm-ffi/src/extra/unchanged.rs          |  62 ++++
 rust/tvm-ffi/tests/test_dispatch.rs          |   6 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs | 404 +++++++++++++++------------
 9 files changed, 415 insertions(+), 259 deletions(-)

diff --git a/docs/guides/rust_lang_guide.rst b/docs/guides/rust_lang_guide.rst
index d0be4100..3414cb7d 100644
--- a/docs/guides/rust_lang_guide.rst
+++ b/docs/guides/rust_lang_guide.rst
@@ -836,8 +836,8 @@ Structural Mapping and Mutation
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 ``structural_map`` is the transforming counterpart to ``structural_walk``. An
-inherent ``#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]`` impl 
groups
-``map_*`` methods returning the engine result ``Result<Any>``. Methods are 
tested in source order, the
+inherent ``#[dispatch(map, (AnyView<'_>, DefRegionKind), 
Result<UnchangedOr<Any>>)]`` impl groups
+``map_*`` methods returning the engine result ``Result<UnchangedOr<Any>>``. 
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``; an ``AnyView<'_>`` 
method is a
 catch-all and should therefore come last:
@@ -846,6 +846,7 @@ catch-all and should therefore come last:
 
    use tvm_ffi::{
        dispatch, structural_map, Any, Array, DefRegionKind, AnyView, Result, 
WalkOrder,
+       UnchangedOr, Unchanged,
    };
 
    #[derive(Default)]
@@ -853,15 +854,15 @@ catch-all and should therefore come last:
        integers: usize,
    }
 
-   #[dispatch(map, (tvm_ffi::AnyView<'_>, DefRegionKind), Result<Any>)]
+   #[dispatch(map, (tvm_ffi::AnyView<'_>, DefRegionKind), 
Result<UnchangedOr<Any>>)]
    impl Increment {
-       fn map_integer(&mut self, value: i64, _kind: DefRegionKind) -> 
Result<Any> {
+       fn map_integer(&mut self, value: i64, _kind: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
            self.integers += 1;
-           Ok(Any::from(value + 1))
+           Ok((value + 1).into())
        }
 
-       fn map_other(&mut self, value: AnyView<'_>) -> Result<Any> {
-           Ok(Any::from(value))
+       fn map_other(&mut self, _value: AnyView<'_>) -> 
Result<UnchangedOr<Any>> {
+           Ok(Unchanged.into())
        }
    }
 
@@ -914,12 +915,16 @@ example, an integer handler can return ``Result<i64>`` to 
report failures and us
 completed before a later error are not rolled back, and the consumed root is
 not returned on error.
 
-Callbacks can also return ``Unchanged`` or ``UnchangedOr<T>``, optionally 
wrapped
-in ``Result``. A pre-order map still maps the original value's children when
-a callback returns ``Unchanged``; a post-order map keeps the result of child
-mapping. Use ``mutate`` and ``default_mutate`` to preserve unchanged during 
recursion.
-They return ``Result<UnchangedOr<Any>>``; owning-value helpers and top-level
-functions resolve the marker to the original value.
+Handlers of a ``#[dispatch]`` struct return ``Result<UnchangedOr<Any>>``, the
+same type that ``mutate`` and ``default_mutate`` return, so a handler can 
return
+the result of recursion directly. Return ``Unchanged.into()`` to keep the 
value,
+or ``value.into()`` to replace it with a managed reference or scalar; wrap an
+``Any`` replacement with ``UnchangedOr::changed``. Closures may also return
+``Unchanged``, ``UnchangedOr<T>``, or a replacement value, optionally wrapped 
in
+``Result``. A pre-order map still maps the original value's children when a
+callback returns ``Unchanged``; a post-order map keeps the result of child
+mapping. Owning-value helpers and top-level functions resolve the marker to the
+original value.
 
 ``structural_mutate`` accepts an owned or borrowed dispatcher. Its handlers 
keep
 mutable pass state on ``self`` and receive ``StructuralMutatorView<'_, Self>`` 
for recursion.
@@ -933,6 +938,7 @@ Base implementations delegate to the corresponding 
``StructuralVisitorObj`` or
 
    use tvm_ffi::{
        dispatch, structural_mutate, Any, Array, MutationView, 
StructuralMutatorView, Result,
+       UnchangedOr,
    };
 
    #[derive(Default)]
@@ -940,14 +946,14 @@ Base implementations delegate to the corresponding 
``StructuralVisitorObj`` or
        integers: usize,
    }
 
-   #[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut 
StructuralMutatorView<'_, Self>), Result<Any>)]
+   #[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut 
StructuralMutatorView<'_, Self>), Result<UnchangedOr<Any>>)]
    impl Stats {
-       fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
+       fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
            self.integers += 1;
-           Ok(Any::from(value + 1))
+           Ok((value + 1).into())
        }
-       fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
-           mutator.default_mutate(self, value).map(Into::into)
+       fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
+           mutator.default_mutate(self, value)
        }
    }
 
@@ -983,7 +989,7 @@ only copy permission. Read permission from 
``value.inplace_mode()``; see
 ``MutationView`` for ownership requirements.
 
 An inherent ``#[dispatch]`` impl declares the engine's required mutation tuple
-and ``Result<Any>`` return type. An unmatched value follows default mutation 
with
+and ``Result<UnchangedOr<Any>>`` return type. An unmatched value follows 
default mutation with
 its current permission. A handler may omit the context argument even when no
 other handler consumes it:
 
@@ -991,6 +997,7 @@ other handler consumes it:
 
    use tvm_ffi::{
        dispatch, structural_mutate, Any, Array, StructuralMutatorView, Result,
+       UnchangedOr,
    };
 
    #[derive(Default)]
@@ -998,11 +1005,11 @@ other handler consumes it:
        integers: usize,
    }
 
-   #[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut 
StructuralMutatorView<'_, Self>), Result<Any>)]
+   #[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut 
StructuralMutatorView<'_, Self>), Result<UnchangedOr<Any>>)]
    impl Increment {
-       fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
+       fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
            self.integers += 1;
-           Ok(Any::from(value + 1))
+           Ok((value + 1).into())
        }
    }
 
@@ -1021,17 +1028,18 @@ while retaining typed scalar replacements:
 
    use tvm_ffi::{
        dispatch, structural_mutate, Any, Array, InplaceMode, MutationView, 
StructuralMutatorView, Result,
+       UnchangedOr,
    };
 
    struct Increment;
 
-   #[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut 
StructuralMutatorView<'_, Self>), Result<Any>)]
+   #[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut 
StructuralMutatorView<'_, Self>), Result<UnchangedOr<Any>>)]
    impl Increment {
-       fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
-           Ok(Any::from(value + 1))
+       fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
+           Ok((value + 1).into())
        }
-       fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
-           mutator.default_mutate(self, 
value.with_inplace_mode(InplaceMode::Disallow)).map(Into::into)
+       fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
+           mutator.default_mutate(self, 
value.with_inplace_mode(InplaceMode::Disallow))
        }
    }
 
diff --git a/rust/tvm-ffi-macros/src/dispatch.rs 
b/rust/tvm-ffi-macros/src/dispatch.rs
index b6701b7e..2e9b4ff1 100644
--- a/rust/tvm-ffi-macros/src/dispatch.rs
+++ b/rust/tvm-ffi-macros/src/dispatch.rs
@@ -273,7 +273,17 @@ fn expand(item: &ItemImpl, args: &DispatchArgs) -> 
syn::Result<TokenStream2> {
             quote!(::<#(#type_args),*>)
         };
         let invocation = quote_spanned! {method.span()=> 
#dispatch_self.#method #type_args (#bound #(, #forwarded)*) };
-        let call = quote_spanned! {method.span()=> 
::core::option::Option::Some(::core::convert::Into::<#output>::into(#invocation))
 };
+        // `Into` cannot convert between two `Result`s, so a `Result` handler 
of a
+        // `Result` dispatcher converts its success value instead.
+        let returns_result = matches!(
+            &handler.method.sig.output,
+            syn::ReturnType::Type(_, ty) if is_result(ty)
+        );
+        let call = if returns_result && is_result(&output) {
+            quote_spanned! {method.span()=> 
::core::option::Option::Some(::core::result::Result::map(#invocation, 
::core::convert::Into::into)) }
+        } else {
+            quote_spanned! {method.span()=> 
::core::option::Option::Some(::core::convert::Into::<#output>::into(#invocation))
 }
+        };
         let pattern = match &handler.argument {
             Argument::BorrowedNode => {
                 let Type::Reference(reference) = pattern_type else {
@@ -440,6 +450,11 @@ fn parse_handler<'a>(method: &'a ImplItemMethod, args: 
&DispatchArgs) -> syn::Re
     })
 }
 
+/// Whether `ty` is written as a `Result<..>` path.
+fn is_result(ty: &Type) -> bool {
+    matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|s| 
s.ident == "Result"))
+}
+
 fn is_capability(ty: &Type) -> bool {
     matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|s| 
s.ident == "MutationView"))
 }
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 69813ddd..5acdbd98 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -36,9 +36,13 @@ mod utils;
 /// receiver reference (`&T` or `&mut T`). The inherent `.dispatch(value, ...)`
 /// method owns matching; `.dispatch_tuple((value, ...))` unpacks and forwards.
 /// Both methods always inline. A matched result is converted with `Into<Ret>`
-/// and wrapped in `Some`. Only the outer `None` means no match; matched errors
-/// and inner `None` are final. Conversion uses ordinary trait implementations,
-/// without recursively converting results or wrapping values in `Ok`.
+/// and wrapped in `Some`. When both the handler's return type and `Ret` are
+/// written as `Result<..>`, the success value is converted with `Into` 
instead,
+/// so a `Result<UnchangedOr<Expr>>` or `Result<i64>` handler fits a
+/// `Result<UnchangedOr<Any>>` dispatcher. Only the outer `None` means no 
match;
+/// matched errors and inner `None` are final. Conversion uses ordinary trait
+/// implementations, without recursively converting results or wrapping values
+/// in `Ok`.
 ///
 /// Handlers match an owned FFI value, a borrowed object node, or an optional
 /// by-value `AnyView` catch-all, which must be last. Each handler receives its
diff --git a/rust/tvm-ffi/src/extra/dispatch.rs 
b/rust/tvm-ffi/src/extra/dispatch.rs
index a8c6e110..cca63484 100644
--- a/rust/tvm-ffi/src/extra/dispatch.rs
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -124,7 +124,8 @@ pub mod marker {
 /// use tvm_ffi::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
 /// let callbacks = (|value: i64| Any::from(value + 1), |_value: AnyView<'_>| 
Any::new());
 /// let mut dispatcher = IntoDispatcher::<(AnyView<'_>,), Option<Result<Any>>, 
_>::into_dispatcher(callbacks);
-/// let result = 
dispatcher.dispatch_tuple((AnyView::from(&41_i64),)).unwrap()?;
+/// let result: Option<Result<Any>> = 
dispatcher.dispatch_tuple((AnyView::from(&41_i64),));
+/// let result = result.unwrap()?;
 /// assert_eq!(result.try_as::<i64>(), Some(42));
 /// # Ok::<(), tvm_ffi::Error>(())
 /// ```
diff --git a/rust/tvm-ffi/src/extra/structural_map.rs 
b/rust/tvm-ffi/src/extra/structural_map.rs
index 6ba5c8ca..50fe8677 100644
--- a/rust/tvm-ffi/src/extra/structural_map.rs
+++ b/rust/tvm-ffi/src/extra/structural_map.rs
@@ -26,7 +26,7 @@ use super::structural_mutate::{
 };
 use super::structural_visit::DefRegionKind;
 use super::structural_walk::WalkOrder;
-use super::unchanged::is_unchanged;
+use super::unchanged::{UnchangedOr, is_unchanged};
 use crate::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
 use std::borrow::BorrowMut;
 
@@ -37,6 +37,13 @@ use std::borrow::BorrowMut;
 impl<O: IntoMutateResult> super::dispatch::CallbackResult<Result<Any>> for O {
     #[inline(always)]
     fn callback_result(self) -> Result<Any> {
+        self.into_mutate_result().map(Any::from)
+    }
+}
+
+impl<O: IntoMutateResult> 
super::dispatch::CallbackResult<Result<UnchangedOr<Any>>> for O {
+    #[inline(always)]
+    fn callback_result(self) -> Result<UnchangedOr<Any>> {
         self.into_mutate_result()
     }
 }
@@ -51,7 +58,8 @@ struct MapMutator<'a, D, const PRE_ORDER: bool> {
 
 fn run_mapper<D>(root: Any, dispatch: &mut D, order: WalkOrder) -> Result<Any>
 where
-    for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>,
+    for<'s, 'v> &'s mut D:
+        Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<UnchangedOr<Any>>>>,
 {
     match order {
         WalkOrder::PreOrder => {
@@ -73,10 +81,11 @@ impl<'v, 'c, 'r, 'a, D, const PRE_ORDER: bool>
             MutationView<'v>,
             &'c mut StructuralMutatorView<'r, MapMutator<'a, D, PRE_ORDER>>,
         ),
-        Option<Result<Any>>,
+        Option<Result<UnchangedOr<Any>>>,
     > for &mut MapMutator<'a, D, PRE_ORDER>
 where
-    for<'s, 'value> &'s mut D: Dispatcher<(AnyView<'value>, DefRegionKind), 
Option<Result<Any>>>,
+    for<'s, 'value> &'s mut D:
+        Dispatcher<(AnyView<'value>, DefRegionKind), 
Option<Result<UnchangedOr<Any>>>>,
 {
     #[inline(always)]
     fn dispatch_tuple(
@@ -85,14 +94,15 @@ where
             MutationView<'v>,
             &'c mut StructuralMutatorView<'r, MapMutator<'a, D, PRE_ORDER>>,
         ),
-    ) -> Option<Result<Any>> {
-        Some(self.map_current(value, mutator))
+    ) -> Option<Result<UnchangedOr<Any>>> {
+        Some(self.map_current(value, mutator).map(UnchangedOr::from_any))
     }
 }
 
 impl<D, const PRE_ORDER: bool> MapMutator<'_, D, PRE_ORDER>
 where
-    for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>,
+    for<'s, 'v> &'s mut D:
+        Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<UnchangedOr<Any>>>>,
 {
     #[inline(always)]
     fn map_current(
@@ -113,8 +123,9 @@ where
             else {
                 return mutator.default_mutate(self, value).map(Into::into);
             };
-            let mapped =
-                callback_result.map_err(|error| 
with_mutation_error_context(error, raw))?;
+            let mapped: Any = callback_result
+                .map_err(|error| with_mutation_error_context(error, raw))?
+                .into();
             if is_unchanged(&mapped) || raw_ffi_any_same_as(raw, 
*mapped.as_raw_ffi_any()) {
                 // Drop temporary ownership before default descent checks 
uniqueness.
                 drop(mapped);
@@ -138,8 +149,9 @@ where
                 AnyView::from(&mapped)
             };
             match self.dispatch.dispatch_tuple((current, kind)) {
-                Some(Ok(result)) if is_unchanged(&result) => Ok(mapped),
+                Some(Ok(result)) if result.is_unchanged() => Ok(mapped),
                 Some(result) => result
+                    .map(Any::from)
                     .map_err(|error| with_mutation_error_context(error, 
*current.as_raw_ffi_any())),
                 None => Ok(mapped),
             }
@@ -191,8 +203,15 @@ pub fn structural_map<R, M, H, D, O>(root: R, mapper: H, 
order: WalkOrder) -> Re
 where
     R: Into<Any>,
     O: BorrowMut<D>,
-    for<'v> H: IntoDispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>, M, Target = D, Output = O>,
-    for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<Any>>>,
+    for<'v> H: IntoDispatcher<
+            (AnyView<'v>, DefRegionKind),
+            Option<Result<UnchangedOr<Any>>>,
+            M,
+            Target = D,
+            Output = O,
+        >,
+    for<'s, 'v> &'s mut D:
+        Dispatcher<(AnyView<'v>, DefRegionKind), 
Option<Result<UnchangedOr<Any>>>>,
 {
     run_mapper(root.into(), mapper.into_dispatcher().borrow_mut(), order)
 }
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 83440788..99434c65 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -316,28 +316,29 @@ mod callback_result_sealed {
     impl<T: Into<Any>> Sealed for Result<T> {}
 }
 
-/// Convert an infallible or fallible map or mutation callback result into 
[`Result<Any>`].
+/// Convert an infallible or fallible map or mutation callback result into
+/// [`Result<UnchangedOr<Any>>`].
 ///
-/// A callback may return any value convertible into [`Any`], or wrap it in
-/// [`Result`] to use `?`.
+/// A callback may return any value convertible into [`Any`], including
+/// [`Unchanged`] and [`UnchangedOr`], or wrap it in [`Result`] to use `?`.
 ///
 /// This trait is sealed and is not an extension point.
 #[doc(hidden)]
 pub trait IntoMutateResult: callback_result_sealed::Sealed {
-    fn into_mutate_result(self) -> Result<Any>;
+    fn into_mutate_result(self) -> Result<UnchangedOr<Any>>;
 }
 
 impl<T: Into<Any>> IntoMutateResult for T {
     #[inline(always)]
-    fn into_mutate_result(self) -> Result<Any> {
-        Ok(self.into())
+    fn into_mutate_result(self) -> Result<UnchangedOr<Any>> {
+        Ok(UnchangedOr::from_any(self.into()))
     }
 }
 
 impl<T: Into<Any>> IntoMutateResult for Result<T> {
     #[inline(always)]
-    fn into_mutate_result(self) -> Result<Any> {
-        self.map(Into::into)
+    fn into_mutate_result(self) -> Result<UnchangedOr<Any>> {
+        self.map(|value| UnchangedOr::from_any(value.into()))
     }
 }
 
@@ -395,13 +396,13 @@ impl InplaceMode {
 ///
 /// Moving the handle into a nested callback cannot bypass that borrow:
 /// ```compile_fail,E0505
-/// use tvm_ffi::{dispatch, structural_mutate, Any, MutationView, 
StructuralMutatorView, Result};
+/// use tvm_ffi::{dispatch, structural_mutate, Any, MutationView, 
StructuralMutatorView, Result, UnchangedOr};
 /// struct Inner<'a>(Option<MutationView<'a>>);
-/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
 /// impl<'a> Inner<'a> {
-///     fn mutate_bool(&mut self, _: bool, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
+///     fn mutate_bool(&mut self, _: bool, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
 ///         let pending = self.0.take().unwrap();
-///         mutator.default_mutate(self, pending).map(Into::into)
+///         mutator.default_mutate(self, pending)
 ///     }
 /// }
 /// fn invalid(value: MutationView<'_>) {
@@ -414,16 +415,16 @@ impl InplaceMode {
 ///
 /// A copied inspection view also cannot survive low-level default mutation:
 /// ```compile_fail,E0505
-/// use tvm_ffi::{dispatch, Any, MutationView, StructuralMutatorView, Result, 
Unchanged};
+/// use tvm_ffi::{dispatch, Any, MutationView, StructuralMutatorView, Result, 
Unchanged, UnchangedOr};
 /// struct Pass;
-/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
 /// impl Pass {
-///     fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
+///     fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
 ///         let view = value.as_any_view();
 ///         let copy = view;
 ///         mutator.default_mutate(self, value).unwrap();
 ///         println!("{}", copy.type_index());
-///         Ok(Any::from(Unchanged))
+///         Ok(Unchanged.into())
 ///     }
 /// }
 /// ```
@@ -731,14 +732,14 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
         O: BorrowMut<D>,
         for<'v, 'c, 'r> H: IntoDispatcher<
                 (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-                Option<Result<Any>>,
+                Option<Result<UnchangedOr<Any>>>,
                 M,
                 Target = D,
                 Output = O,
             >,
         for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
                 (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-                Option<Result<Any>>,
+                Option<Result<UnchangedOr<Any>>>,
             >,
     {
         let mut storage = dispatcher.into_dispatcher();
@@ -1066,7 +1067,7 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
     where
         for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
                 (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-                Option<Result<Any>>,
+                Option<Result<UnchangedOr<Any>>>,
             >,
     {
         &StructuralMutatorVTable {
@@ -1091,7 +1092,7 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
     where
         for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
                 (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-                Option<Result<Any>>,
+                Option<Result<UnchangedOr<Any>>>,
             >,
     {
         let context = (*Self::from_handle(runtime)).dispatcher;
@@ -1115,11 +1116,14 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
         ) {
             // Map adapters attribute errors to their possibly replaced value;
             // direct mutation attributes callback failures to this input.
-            Some(result) => result_into_raw_ffi_any(if CALLBACK_CONTEXT {
-                result.map_err(|error| with_mutation_error_context(error, raw))
-            } else {
-                result
-            }),
+            Some(result) => {
+                let result = result.map(Any::from);
+                result_into_raw_ffi_any(if CALLBACK_CONTEXT {
+                    result.map_err(|error| with_mutation_error_context(error, 
raw))
+                } else {
+                    result
+                })
+            }
             None => {
                 Self::default_mutate_raw(runtime, 
std::ptr::from_mut(dispatch).cast(), raw, mode)
             }
@@ -1226,7 +1230,7 @@ impl<Base: StructuralMutatorBase> 
StructuralMutateEngine<Base> {
     where
         for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
                 (MutationView<'v>, &'c mut StructuralMutatorView<'r, D, Base>),
-                Option<Result<Any>>,
+                Option<Result<UnchangedOr<Any>>>,
             >,
     {
         let mut base = Base::new()?;
@@ -1835,17 +1839,18 @@ impl StructuralMutatorObj {
 /// # fn main() -> tvm_ffi::Result<()> {
 /// use tvm_ffi::{
 ///     dispatch, structural_mutate, Any, Array, MutationView, Result, 
StructuralMutatorView,
+///     UnchangedOr,
 /// };
 ///
 /// struct Double;
-/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
 /// impl Double {
 ///     fn mutate_integer(
 ///         &mut self,
 ///         value: i64,
 ///         _: &mut StructuralMutatorView<'_, Self>,
-///     ) -> Result<Any> {
-///         Ok(Any::from(value * 2))
+///     ) -> Result<UnchangedOr<Any>> {
+///         Ok((value * 2).into())
 ///     }
 /// }
 ///
@@ -1864,7 +1869,7 @@ where
                 MutationView<'v>,
                 &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
             ),
-            Option<Result<Any>>,
+            Option<Result<UnchangedOr<Any>>>,
             M,
             Target = D,
             Output = O,
@@ -1874,7 +1879,7 @@ where
                 MutationView<'v>,
                 &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
             ),
-            Option<Result<Any>>,
+            Option<Result<UnchangedOr<Any>>>,
         >,
 {
     StructuralMutateEngine::mutate_root(root, dispatcher)
diff --git a/rust/tvm-ffi/src/extra/unchanged.rs 
b/rust/tvm-ffi/src/extra/unchanged.rs
index 862fa6f2..e7b9cd4a 100644
--- a/rust/tvm-ffi/src/extra/unchanged.rs
+++ b/rust/tvm-ffi/src/extra/unchanged.rs
@@ -165,6 +165,15 @@ impl<T: ContainerElement> UnchangedOr<T> {
 }
 
 impl UnchangedOr<Any> {
+    /// Wrap a successful erased carrier, which may hold the unchanged marker.
+    #[inline(always)]
+    pub(crate) fn from_any(data: Any) -> Self {
+        Self {
+            data,
+            _marker: PhantomData,
+        }
+    }
+
     #[inline(always)]
     pub(crate) fn from_carrier(data: Any) -> Result<Self> {
         if data.type_index() == TVMFFITypeIndex::kTVMFFIError as i32 {
@@ -317,3 +326,56 @@ unsafe impl<T: ContainerElement> AnyCompatible for 
UnchangedOr<T> {
         crate::type_traits::type_schema("UnchangedOr", 
&[T::container_type_schema()])
     }
 }
+
+// 
-----------------------------------------------------------------------------
+// Conversions into an erased mutation result.
+// 
-----------------------------------------------------------------------------
+
+/// `Unchanged.into()` keeps the original value.
+impl From<Unchanged> for UnchangedOr<Any> {
+    #[inline]
+    fn from(_: Unchanged) -> Self {
+        Self::unchanged()
+    }
+}
+
+/// A managed reference replaces the original value.
+impl<T: crate::object::ObjectRefCore + AnyCompatible> From<T> for 
UnchangedOr<Any> {
+    #[inline]
+    fn from(value: T) -> Self {
+        Self::changed(Any::from(value))
+    }
+}
+
+/// A typed result keeps its unchanged marker or replacement.
+impl<T: crate::object::ObjectRefCore + AnyCompatible> From<UnchangedOr<T>> for 
UnchangedOr<Any> {
+    #[inline]
+    fn from(value: UnchangedOr<T>) -> Self {
+        Self::from_any(Any::from(value))
+    }
+}
+
+macro_rules! impl_unchanged_or_any_from_scalar {
+    ($($ty:ty),*) => {
+        $(
+            /// A scalar replaces the original value.
+            impl From<$ty> for UnchangedOr<Any> {
+                #[inline]
+                fn from(value: $ty) -> Self {
+                    Self::changed(Any::from(value))
+                }
+            }
+
+            /// A typed scalar result keeps its unchanged marker or 
replacement.
+            impl From<UnchangedOr<$ty>> for UnchangedOr<Any> {
+                #[inline]
+                fn from(value: UnchangedOr<$ty>) -> Self {
+                    Self::from_any(Any::from(value))
+                }
+            }
+        )*
+    };
+}
+impl_unchanged_or_any_from_scalar!(
+    bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64
+);
diff --git a/rust/tvm-ffi/tests/test_dispatch.rs 
b/rust/tvm-ffi/tests/test_dispatch.rs
index 179c26ea..5bfafcfb 100644
--- a/rust/tvm-ffi/tests/test_dispatch.rs
+++ b/rust/tvm-ffi/tests/test_dispatch.rs
@@ -69,15 +69,13 @@ fn into_dispatcher_supports_owned_borrowed_and_callbacks() 
-> Result<()> {
     let mut tuple =
         IntoDispatcher::<(AnyView<'_>, i64, &str), Outcome, 
_>::into_dispatcher(callbacks);
     assert_eq!(
-        tuple
-            .dispatch_tuple((AnyView::from(&40_i64), 4, "unused"))
+        Dispatcher::<_, Outcome>::dispatch_tuple(&mut tuple, 
(AnyView::from(&40_i64), 4, "unused"))
             .unwrap()?
             .try_as::<i64>(),
         Some(44)
     );
     assert_eq!(
-        tuple
-            .dispatch_tuple((AnyView::from(&true), 4, "unused"))
+        Dispatcher::<_, Outcome>::dispatch_tuple(&mut tuple, 
(AnyView::from(&true), 4, "unused"))
             .unwrap()?
             .try_as::<i64>(),
         Some(-1)
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index c623cdbb..503fe295 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -24,25 +24,26 @@ use tvm_ffi::function::FunctionObj;
 use tvm_ffi::object::ObjectRef;
 use tvm_ffi::{
     dispatch, structural_map, structural_mutate, structural_visit, 
structural_walk, Any, AnyView,
-    Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function, 
InplaceMode, IntoDispatcher, Map,
-    MutationView, Object, ObjectArc, ObjectRefCore, Result, String as 
FfiString,
+    Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function, 
InplaceMode, IntoDispatcher,
+    Map, MutationView, Object, ObjectArc, ObjectRefCore, Result, String as 
FfiString,
     StructuralMutatorView, StructuralVarRemap, StructuralVisitorView, 
TypeIndex, Unchanged,
     UnchangedOr, VisitInterrupt, WalkOrder, WalkResult, RUNTIME_ERROR,
 };
 
 struct IncrementIntegers;
 
-impl<'receiver, 'value> Dispatcher<(AnyView<'value>, DefRegionKind), 
Option<Result<Any>>>
+impl<'receiver, 'value>
+    Dispatcher<(AnyView<'value>, DefRegionKind), 
Option<Result<UnchangedOr<Any>>>>
     for &'receiver mut IncrementIntegers
 {
     #[inline(always)]
     fn dispatch_tuple(
         self,
         (value, _def_region_kind): (AnyView<'value>, DefRegionKind),
-    ) -> Option<Result<Any>> {
+    ) -> Option<Result<UnchangedOr<Any>>> {
         value
             .try_as::<i64>()
-            .map(|integer| Ok(Any::from(integer + 1)))
+            .map(|integer| Ok((integer + 1).into()))
     }
 }
 
@@ -68,7 +69,7 @@ impl<'s, 'v, 'c, 'r>
             MutationView<'v>,
             &'c mut StructuralMutatorView<'r, ManualIncrement>,
         ),
-        Option<Result<Any>>,
+        Option<Result<UnchangedOr<Any>>>,
     > for &'s mut ManualIncrement
 {
     #[inline(always)]
@@ -78,7 +79,7 @@ impl<'s, 'v, 'c, 'r>
             MutationView<'v>,
             &'c mut StructuralMutatorView<'r, ManualIncrement>,
         ),
-    ) -> Option<Result<Any>> {
+    ) -> Option<Result<UnchangedOr<Any>>> {
         Some(if value.as_any_view().try_as::<bool>() == Some(true) {
             let key = self.remap_probe.as_ref().unwrap().clone();
             (|| {
@@ -112,59 +113,57 @@ impl<'s, 'v, 'c, 'r>
                 assert_eq!(mutator.def_region_kind(), initial);
                 let replacement = mutator.var_remap_get(self, 
AnyView::from(&key))?.unwrap();
                 self.remap.set(AnyView::from(&key), &replacement)?;
-                Ok(replacement)
+                Ok(UnchangedOr::changed(replacement))
             })()
         } else if let Some(integer) = value.as_any_view().try_as::<i64>() {
-            Ok(Any::from(integer + 1))
+            Ok((integer + 1).into())
         } else {
-            mutator.default_mutate(self, value).map(Into::into)
+            mutator.default_mutate(self, value)
         })
     }
 }
-#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
+#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<UnchangedOr<Any>>)]
 impl ManualIncrement {
-    fn map_integer(&mut self, value: i64, _: DefRegionKind) -> Result<Any> {
-        Ok(Any::from(value + 1))
+    fn map_integer(&mut self, value: i64, _: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
+        Ok((value + 1).into())
     }
 }
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl ReplaceNone {
     fn mutate_any(
         &mut self,
         value: MutationView<'_>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         if value.as_any_view().type_index() == TypeIndex::kTVMFFINone as i32 {
             self.calls += 1;
-            Ok(Any::from(8i64))
+            Ok(8i64.into())
         } else {
-            mutator.default_mutate(self, value).map(Into::into)
+            mutator.default_mutate(self, value)
         }
     }
 }
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl RecursiveEntryMutator {
     fn mutate_any(
         &mut self,
         value: MutationView<'_>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         if value.as_any_view().type_index() == TypeIndex::kTVMFFINone as i32 {
             if self.use_owned_value {
                 let value = Array::new(vec![1i64]);
                 self.owned_value_pointer = Some(array_pointer(&value) as 
usize);
                 let mut owner: Any = value.into();
                 let change = mutator.mutate(self, &mut owner)?;
-                Ok(change.value_or(owner))
+                Ok(UnchangedOr::changed(change.value_or(owner)))
             } else {
-                mutator
-                    .mutate(self, MutationView::borrowed(AnyView::from(&1i64)))
-                    .map(Into::into)
+                mutator.mutate(self, 
MutationView::borrowed(AnyView::from(&1i64)))
             }
         } else if let Some(integer) = value.as_any_view().try_as::<i64>() {
-            Ok(Any::from(integer + 1))
+            Ok((integer + 1).into())
         } else {
-            mutator.default_mutate(self, value).map(Into::into)
+            mutator.default_mutate(self, value)
         }
     }
 }
@@ -447,44 +446,40 @@ fn 
user_mutator_recursive_entries_reenter_the_same_mutator() {
     struct BoundDispatcher {
         visits: usize,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl BoundDispatcher {
         fn mutate_bool(
             &mut self,
             _: bool,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             for operation in 0..4 {
                 let mut other = Self::default();
                 let key = Any::new();
                 let rejected =
                     std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| 
match operation {
-                        0 => mutator
-                            .mutate(&mut other, 
MutationView::borrowed(AnyView::from(&1i64)))
-                            .map(Into::into),
-                        1 => mutator
-                            .default_mutate(
-                                &mut other,
-                                MutationView::borrowed(AnyView::from(&1i64)),
-                            )
-                            .map(Into::into),
+                        0 => {
+                            mutator.mutate(&mut other, 
MutationView::borrowed(AnyView::from(&1i64)))
+                        }
+                        1 => mutator.default_mutate(
+                            &mut other,
+                            MutationView::borrowed(AnyView::from(&1i64)),
+                        ),
                         2 => mutator
                             .var_remap_get(&mut other, AnyView::from(&key))
-                            .map(|_| Any::new()),
+                            .map(|_| UnchangedOr::changed(Any::new())),
                         _ => mutator
                             .var_remap_set(&mut other, AnyView::from(&key), 
&key)
-                            .map(|_| Any::new()),
+                            .map(|_| UnchangedOr::changed(Any::new())),
                     }));
                 assert!(rejected.is_err());
                 assert_eq!(other.visits, 0);
             }
-            mutator
-                .mutate(self, MutationView::borrowed(AnyView::from(&1i64)))
-                .map(Into::into)
+            mutator.mutate(self, MutationView::borrowed(AnyView::from(&1i64)))
         }
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
             self.visits += 1;
-            Ok(Any::from(value + 1))
+            Ok((value + 1).into())
         }
     }
     assert_eq!(
@@ -508,11 +503,11 @@ fn 
default_mutation_mode_preserves_ownership_and_unchanged_results() {
         increment: bool,
     }
 
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Controlled {
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
             Ok(if self.increment {
-                Any::from(value + 1)
+                (value + 1).into()
             } else {
                 Unchanged.into()
             })
@@ -521,12 +516,10 @@ fn 
default_mutation_mode_preserves_ownership_and_unchanged_results() {
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let mode = self.mode;
             if self.increment {
-                mutator
-                    .default_mutate(self, value.with_inplace_mode(mode))
-                    .map(Into::into)
+                mutator.default_mutate(self, value.with_inplace_mode(mode))
             } else {
                 let result = mutator.default_mutate(self, 
value.with_inplace_mode(mode))?;
                 assert!(result.is_unchanged());
@@ -553,15 +546,15 @@ fn 
default_mutation_mode_preserves_ownership_and_unchanged_results() {
 #[test]
 fn borrowed_capabilities_cannot_enable_low_level_default_reuse() {
     struct BorrowedEntry;
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl BorrowedEntry {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             if let Some(integer) = value.as_any_view().try_as::<i64>() {
-                return Ok(Any::from(integer + 1));
+                return Ok((integer + 1).into());
             }
             assert_eq!(value.as_any_view().try_as::<bool>(), Some(true));
             let source = Array::new(vec![1_i64]);
@@ -596,25 +589,25 @@ fn 
borrowed_capabilities_keep_their_mode_in_dispatch_adapters() {
         source: &'a Array<i64>,
         result: Option<Any>,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Inspect<'a> {
         fn mutate_bool(
             &mut self,
             _: bool,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let source = self.source;
             let value = MutationView::borrowed(AnyView::from(source))
                 .try_cast::<Array<i64>>()
                 .unwrap_or_else(|_| unreachable!());
             self.result = Some(mutator.mutate(self, value)?.into());
-            Ok(Any::new())
+            Ok(UnchangedOr::changed(Any::new()))
         }
         fn mutate_array(
             &mut self,
             value: MutationView<'_, Array<i64>>,
             _mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             assert_eq!(value.inplace_mode(), InplaceMode::Disallow);
             Unchanged.into_mutate_result()
         }
@@ -643,13 +636,13 @@ fn owned_entry_mode_is_forwarded_by_generated_callbacks() 
{
         increment: bool,
         shared: bool,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Entry {
         fn mutate_bool(
             &mut self,
             _: bool,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let child = Array::new(vec![1_i64]);
             self.pointer = array_pointer(&child) as usize;
             mutator.with_def_region_kind(DefRegionKind::Pattern, |mutator| {
@@ -678,17 +671,17 @@ fn owned_entry_mode_is_forwarded_by_generated_callbacks() 
{
                 if let Some(alias) = alias {
                     assert_eq!(Array::<i64>::try_from(alias)?.get(0)?, 1);
                 }
-                Ok(result)
+                Ok(UnchangedOr::changed(result))
             })
         }
         fn mutate_integer(
             &mut self,
             value: i64,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             assert_eq!(mutator.def_region_kind(), DefRegionKind::Pattern);
             Ok(if self.increment {
-                Any::from(value + 1)
+                (value + 1).into()
             } else {
                 Unchanged.into()
             })
@@ -747,16 +740,16 @@ fn 
consuming_callbacks_forward_permissions_without_temporary_owners() {
             drop(temporary);
         }
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Forward {
-        fn mutate_string(&mut self, _: MutationView<'_, FfiString>) -> 
Result<Any> {
+        fn mutate_string(&mut self, _: MutationView<'_, FfiString>) -> 
Result<UnchangedOr<Any>> {
             panic!("typed miss must preserve the capability for the next 
handler")
         }
         fn mutate_array(
             &mut self,
             value: MutationView<'_, Array<Any>>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let mode = value.inplace_mode();
             let count = value.as_any_view().debug_strong_count();
             let value = tvm_ffi::match_any!(value {
@@ -778,8 +771,8 @@ fn 
consuming_callbacks_forward_permissions_without_temporary_owners() {
             &mut self,
             value: i64,
             _mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
-            Ok(Any::from(value + 1))
+        ) -> Result<UnchangedOr<Any>> {
+            Ok((value + 1).into())
         }
     }
 
@@ -853,23 +846,23 @@ fn 
consuming_default_descent_transfers_between_contexts_without_node_borrows() {
         value: Option<MutationView<'a>>,
         result: Any,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Inner<'a> {
         fn mutate_bool(
             &mut self,
             _: bool,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let value = self.value.take().unwrap();
             self.result = mutator.default_mutate(self, value)?.into();
-            Ok(Any::new())
+            Ok(UnchangedOr::changed(Any::new()))
         }
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
-            Ok(Any::from(value + 1))
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
+            Ok((value + 1).into())
         }
     }
 
-    fn transfer(value: MutationView<'_>, retain: bool) -> Result<Any> {
+    fn transfer(value: MutationView<'_>, retain: bool) -> 
Result<UnchangedOr<Any>> {
         let alias = retain.then(|| 
value.as_any_view().try_as::<Array<i64>>().unwrap());
         assert_eq!(value.inplace_mode(), InplaceMode::Allow);
         // Carry the result back to the array callback: Unchanged belongs to
@@ -883,18 +876,18 @@ fn 
consuming_default_descent_transfers_between_contexts_without_node_borrows() {
         if let Some(alias) = alias {
             assert_eq!(alias.get(0)?, 1);
         }
-        Ok(result)
+        result.try_into()
     }
     struct Outer {
         retain: bool,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Outer {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             _mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             transfer(value, self.retain)
         }
     }
@@ -919,13 +912,13 @@ fn 
consuming_default_descent_transfers_between_contexts_without_node_borrows() {
 #[test]
 fn consuming_default_descent_preserves_unchanged_and_propagates_errors() {
     struct Preserve(bool);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Preserve {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             if value.as_any_view().try_as::<i64>().is_some() {
                 return if self.0 {
                     Err(Error::new(RUNTIME_ERROR, "child failed", ""))
@@ -1045,17 +1038,17 @@ fn 
callback_errors_preserve_message_and_add_object_context() {
         }
     }
 
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Delegate {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             if value.as_any_view().try_as::<i64>().is_some() {
                 return Err(self.0.clone());
             }
-            mutator.default_mutate(self, value).map(Into::into)
+            mutator.default_mutate(self, value)
         }
     }
 
@@ -1366,16 +1359,16 @@ struct GeneratedMapper {
     catch_all: usize,
 }
 
-#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
+#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<UnchangedOr<Any>>)]
 impl GeneratedMapper {
-    fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> Result<Any> {
+    fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
         self.integers.push((value, kind));
-        Ok(Any::from(value + 1))
+        Ok((value + 1).into())
     }
 
-    fn map_any(&mut self, value: tvm_ffi::AnyView<'_>) -> Result<Any> {
+    fn map_any(&mut self, value: tvm_ffi::AnyView<'_>) -> 
Result<UnchangedOr<Any>> {
         self.catch_all += 1;
-        Ok(Any::from(value))
+        Ok(UnchangedOr::changed(Any::from(value)))
     }
 }
 
@@ -1399,29 +1392,29 @@ struct GeneratedLeafDispatch {
     integers: Vec<(i64, DefRegionKind)>,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl GeneratedLeafDispatch {
     fn mutate_integer(
         &mut self,
         value: i64,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let region = mutator.def_region_kind();
         self.integers.push((value, region));
-        Ok(Any::from(value + 1))
+        Ok((value + 1).into())
     }
 }
 
 struct GeneratedStatelessDispatch;
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl GeneratedStatelessDispatch {
     fn mutate_integer(
         &mut self,
         value: i64,
         _mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
-        Ok(Any::from(value + 1))
+    ) -> Result<UnchangedOr<Any>> {
+        Ok((value + 1).into())
     }
 }
 
@@ -1458,13 +1451,13 @@ struct GeneratedRecursiveDispatch {
     integers: Vec<(i64, DefRegionKind)>,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl GeneratedRecursiveDispatch {
     fn mutate_array(
         &mut self,
         array: Array<i64>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let region = mutator.def_region_kind();
         self.arrays.push(region);
         let mut mutated = Vec::with_capacity(array.len());
@@ -1482,10 +1475,10 @@ impl GeneratedRecursiveDispatch {
         &mut self,
         value: i64,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let region = mutator.def_region_kind();
         self.integers.push((value, region));
-        Ok(Any::from(value + 10))
+        Ok((value + 10).into())
     }
 }
 
@@ -1510,22 +1503,20 @@ struct GeneratedDefaultingDispatch {
     integers: Vec<i64>,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl GeneratedDefaultingDispatch {
     fn mutate_array(
         &mut self,
         array: Array<i64>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         self.arrays += 1;
-        mutator
-            .default_mutate(self, 
MutationView::borrowed(AnyView::from(&array)))
-            .map(Into::into)
+        mutator.default_mutate(self, 
MutationView::borrowed(AnyView::from(&array)))
     }
 
-    fn mutate_integer(&mut self, value: i64) -> Result<Any> {
+    fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
         self.integers.push(value);
-        Ok(Any::from(value + 1))
+        Ok((value + 1).into())
     }
 }
 
@@ -1568,7 +1559,7 @@ fn pre_order_mapping_preserves_inplace_permission() {
         };
         let mut mapper = IntoDispatcher::<
             (AnyView<'_>, DefRegionKind),
-            Option<Result<Any>>,
+            Option<Result<UnchangedOr<Any>>>,
             _,
         >::into_dispatcher(callback);
         let output = structural_map(root, &mut mapper, 
WalkOrder::PreOrder).unwrap();
@@ -1644,13 +1635,13 @@ fn callbacks_return_values_convertible_into_any() {
     assert_eq!(mapped.iter().collect::<Vec<_>>(), vec![21, 22]);
 
     struct Double;
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Double {
         fn mutate_integer(
             &mut self,
             integer: i64,
             _: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             (integer * 2).into_mutate_result()
         }
     }
@@ -1663,13 +1654,13 @@ fn callbacks_return_values_convertible_into_any() {
 #[test]
 fn recursive_mutate_returns_unchanged_or_a_replacement() {
     struct ClampNegativeIntegers;
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl ClampNegativeIntegers {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             if let Some(integer) = value.as_any_view().try_as::<i64>() {
                 if integer >= 0 {
                     // Keep this input without constructing an owning return 
value.
@@ -1756,7 +1747,7 @@ fn post_order_unchanged_preserves_descendant_rewrites() {
             let mapped = if borrowed {
                 let mut mapper = IntoDispatcher::<
                     (AnyView<'_>, DefRegionKind),
-                    Option<Result<Any>>,
+                    Option<Result<UnchangedOr<Any>>>,
                     _,
                 >::into_dispatcher(callbacks);
                 structural_map(root, &mut mapper, WalkOrder::PostOrder)
@@ -1937,21 +1928,19 @@ struct CallbackMutateStats {
     defaults: usize,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl CallbackMutateStats {
-    fn mutate_integer(&mut self, value: i64) -> Result<Any> {
+    fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
         self.integers.push(value);
-        Ok(Any::from(value + 1))
+        Ok((value + 1).into())
     }
     fn mutate_default(
         &mut self,
         value: AnyView<'_>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         self.defaults += 1;
-        mutator
-            .default_mutate(self, MutationView::borrowed(value))
-            .map(Into::into)
+        mutator.default_mutate(self, MutationView::borrowed(value))
     }
 }
 
@@ -1983,13 +1972,13 @@ struct CallbackMutateDepth {
     exits: usize,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl CallbackMutateDepth {
     fn mutate_any(
         &mut self,
         value: AnyView<'_>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         self.current += 1;
         self.maximum = self.maximum.max(self.current);
         let mutated = mutator.default_mutate(self, 
MutationView::borrowed(value))?;
@@ -2022,16 +2011,16 @@ fn 
callback_mutate_explicit_default_is_repeatable_copy_path() {
     let root_pointer = array_pointer(&root);
     let defaults = Cell::new(0);
     struct Repeat<'a>(&'a Cell<i32>);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Repeat<'a> {
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
-            Ok(Any::from(value + 1))
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
+            Ok((value + 1).into())
         }
         fn mutate_any(
             &mut self,
             value: AnyView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             self.0.set(self.0.get() + 1);
             let first = mutator
                 .default_mutate(self, MutationView::borrowed(value))?
@@ -2040,7 +2029,7 @@ fn 
callback_mutate_explicit_default_is_repeatable_copy_path() {
                 .default_mutate(self, MutationView::borrowed(value))?
                 .value_or_else(|| value.into());
             assert_ne!(any_object_pointer(&first), 
any_object_pointer(&second));
-            Ok(first)
+            Ok(UnchangedOr::changed(first))
         }
     }
     let mutated = structural_mutate(root, Repeat(&defaults))
@@ -2055,18 +2044,18 @@ fn 
callback_mutate_explicit_default_is_repeatable_copy_path() {
 fn callback_mutate_match_is_final_and_same_fn_can_reenter() {
     let integer_calls = Cell::new(0);
     struct First<'a>(&'a Cell<i32>);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> First<'a> {
         fn mutate_array(
             &mut self,
             _: Array<i64>,
             _: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
-            Ok(Any::from(Array::new(vec![10i64])))
+        ) -> Result<UnchangedOr<Any>> {
+            Ok(Array::new(vec![10i64]).into())
         }
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
             self.0.set(self.0.get() + 1);
-            Ok(Any::from(value + 1))
+            Ok((value + 1).into())
         }
     }
     let mutated = structural_mutate(Array::new(vec![1i64]), 
First(&integer_calls))
@@ -2077,17 +2066,15 @@ fn 
callback_mutate_match_is_final_and_same_fn_can_reenter() {
 
     let calls = Cell::new(0);
     struct Reenter<'a>(&'a Cell<i32>);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Reenter<'a> {
         fn mutate_any(
             &mut self,
             value: AnyView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             self.0.set(self.0.get() + 1);
-            mutator
-                .default_mutate(self, MutationView::borrowed(value))
-                .map(Into::into)
+            mutator.default_mutate(self, MutationView::borrowed(value))
         }
     }
     let mutated = structural_mutate(Array::new(vec![1i64, 2]), Reenter(&calls))
@@ -2108,21 +2095,21 @@ fn 
callback_mutate_supports_node_links_nested_tuples_and_reflection() {
     );
     let regions = RefCell::new(Vec::new());
     struct Nodes<'a>(&'a RefCell<Vec<DefRegionKind>>);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Nodes<'a> {
-        fn mutate_bool(&mut self, _: bool) -> Result<Any> {
-            Ok(Any::new())
+        fn mutate_bool(&mut self, _: bool) -> Result<UnchangedOr<Any>> {
+            Ok(UnchangedOr::changed(Any::new()))
         }
-        fn mutate_function(&mut self, _: &FunctionObj) -> Result<Any> {
-            Ok(Any::from(7i64))
+        fn mutate_function(&mut self, _: &FunctionObj) -> 
Result<UnchangedOr<Any>> {
+            Ok(7i64.into())
         }
         fn mutate_integer(
             &mut self,
             value: i64,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             self.0.borrow_mut().push(mutator.def_region_kind());
-            Ok(Any::from(value + 1))
+            Ok((value + 1).into())
         }
     }
     let mutated = structural_mutate(root, Nodes(&regions)).unwrap();
@@ -2143,20 +2130,18 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
     let borrowed_child = Array::new(vec![1i64]);
     let borrowed_pointer = array_pointer(&borrowed_child);
     struct Borrowed<'a>(&'a Array<i64>);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Borrowed<'a> {
         fn mutate_bool(
             &mut self,
             _: bool,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let child = self.0;
-            mutator
-                .mutate(self, MutationView::borrowed(AnyView::from(child)))
-                .map(Into::into)
+            mutator.mutate(self, MutationView::borrowed(AnyView::from(child)))
         }
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
-            Ok(Any::from(value + 1))
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
+            Ok((value + 1).into())
         }
     }
     let mutated = structural_mutate(true, Borrowed(&borrowed_child))
@@ -2168,21 +2153,21 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
 
     let owned_pointer = Cell::new(0usize);
     struct Owned<'a>(&'a Cell<usize>);
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl<'a> Owned<'a> {
         fn mutate_bool(
             &mut self,
             _: bool,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let child = Array::new(vec![1i64]);
             self.0.set(array_pointer(&child) as usize);
             let mut owner: Any = child.into();
             let change = mutator.mutate(self, MutationView::from(&mut owner))?;
-            Ok(change.value_or(owner))
+            Ok(UnchangedOr::changed(change.value_or(owner)))
         }
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
-            Ok(Any::from(value + 1))
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
+            Ok((value + 1).into())
         }
     }
     let mutated = structural_mutate(true, Owned(&owned_pointer))
@@ -2195,34 +2180,32 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
 #[test]
 fn nested_callback_mutate_restores_the_outer_active_mutator() {
     struct Inner;
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Inner {
         fn mutate_integer(
             &mut self,
             value: i64,
             _: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
-            Ok(Any::from(value + 10))
+        ) -> Result<UnchangedOr<Any>> {
+            Ok((value + 10).into())
         }
     }
     #[repr(align(128))]
     struct Outer;
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Outer {
         fn mutate_integer(
             &mut self,
             value: i64,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             assert_eq!(std::ptr::from_mut(self).addr() % 128, 0);
             if value != 1 {
-                return Ok(Any::from(value + 1));
+                return Ok((value + 1).into());
             }
             let inner = structural_mutate(2i64, Inner)?;
             assert_eq!(i64::try_from(inner).unwrap(), 12);
-            mutator
-                .mutate(self, MutationView::borrowed(AnyView::from(&3i64)))
-                .map(Into::into)
+            mutator.mutate(self, MutationView::borrowed(AnyView::from(&3i64)))
         }
     }
     let mutated = structural_mutate(Array::new(vec![1i64]), Outer)
@@ -2254,7 +2237,7 @@ fn mutation_descent_preserves_ownership_and_markers() {
         mode: InplaceMode,
     }
     impl Ownership {
-        fn map_integer(&mut self, x: i64, _kind: DefRegionKind) -> Result<Any> 
{
+        fn map_integer(&mut self, x: i64, _kind: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
             (if self.increment {
                 UnchangedOr::changed(x + 1)
             } else {
@@ -2264,22 +2247,22 @@ fn mutation_descent_preserves_ownership_and_markers() {
         }
     }
     struct MapOwnership(Ownership);
-    #[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
+    #[dispatch(map, (AnyView<'_>, DefRegionKind), Result<UnchangedOr<Any>>)]
     impl MapOwnership {
-        fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<Any> {
+        fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
             self.0.map_integer(value, kind)
         }
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Ownership {
-        fn mutate_integer(&mut self, value: i64) -> Result<Any> {
+        fn mutate_integer(&mut self, value: i64) -> Result<UnchangedOr<Any>> {
             self.map_integer(value, DefRegionKind::None)
         }
         fn mutate_array(
             &mut self,
             value: MutationView<'_, Array<i64>>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             if self.retain {
                 self.retained = Some(Any::from(value.as_any_view()));
             }
@@ -2347,33 +2330,41 @@ fn mutation_regions_retargeting_and_error_restore() {
     #[derive(Default)]
     struct Regions(Vec<(i64, DefRegionKind)>, DefRegionKind);
     impl Regions {
-        fn map_integer(&mut self, x: i64, kind: DefRegionKind) -> Result<Any> {
+        fn map_integer(&mut self, x: i64, kind: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
             self.0.push((x, kind));
-            Ok(Any::from(x))
+            Ok(x.into())
         }
-        fn map_array(&mut self, value: Array<i64>, kind: DefRegionKind) -> 
Result<Any> {
+        fn map_array(
+            &mut self,
+            value: Array<i64>,
+            kind: DefRegionKind,
+        ) -> Result<UnchangedOr<Any>> {
             self.0.push((200, kind));
             Ok(value.into())
         }
     }
     #[derive(Default)]
     struct MapRegions(Regions);
-    #[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
+    #[dispatch(map, (AnyView<'_>, DefRegionKind), Result<UnchangedOr<Any>>)]
     impl MapRegions {
-        fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<Any> {
+        fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<UnchangedOr<Any>> {
             self.0.map_integer(value, kind)
         }
-        fn map_array(&mut self, value: Array<i64>, kind: DefRegionKind) -> 
Result<Any> {
+        fn map_array(
+            &mut self,
+            value: Array<i64>,
+            kind: DefRegionKind,
+        ) -> Result<UnchangedOr<Any>> {
             self.0.map_array(value, kind)
         }
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl Regions {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             if value.as_any_view().try_as::<bool>() == Some(true) {
                 assert_eq!(mutator.def_region_kind(), Use);
                 assert_eq!(
@@ -2390,7 +2381,9 @@ fn mutation_regions_retargeting_and_error_restore() {
                             let array = Array::new(vec![5_i64]);
                             mutator
                                 .default_mutate(self, 
MutationView::borrowed(AnyView::from(&array)))
-                                .map(|result| result.value_or_else(|| 
array.into()))
+                                .map(|result| {
+                                    
UnchangedOr::changed(result.value_or_else(|| array.into()))
+                                })
                         }
                     })
                 })?;
@@ -2419,7 +2412,7 @@ fn mutation_regions_retargeting_and_error_restore() {
                 assert_eq!(mutator.def_region_kind(), Use);
                 // The next callback also observes the restored ABI region.
                 mutator.mutate(self, 
MutationView::borrowed(AnyView::from(&6_i64)))?;
-                return Ok(result.into());
+                return Ok(result);
             }
             if value.as_any_view().try_as::<bool>() == Some(false) {
                 return mutator.with_def_region_kind(Pattern, |mutator| {
@@ -2427,7 +2420,7 @@ fn mutation_regions_retargeting_and_error_restore() {
                     let array = Array::new(vec![1_i64]);
                     mutator
                         .default_mutate(self, 
MutationView::borrowed(AnyView::from(&array)))
-                        .map(|result| result.value_or_else(|| array.into()))
+                        .map(|result| 
UnchangedOr::changed(result.value_or_else(|| array.into())))
                 });
             }
             if let Some(x) = value.as_any_view().try_as::<i64>() {
@@ -2517,13 +2510,13 @@ fn mapping_preserves_post_recursion_state() {
         depth: usize,
         completed: usize,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
     impl PostState {
         fn mutate_any(
             &mut self,
             value: MutationView<'_>,
             mutator: &mut StructuralMutatorView<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             let depth = self.depth;
             self.depth += 1;
             let result = mutator.default_mutate(self, value)?;
@@ -2562,3 +2555,54 @@ fn mapping_preserves_primitive_replacement_descent() {
         assert_eq!(i64::try_from(unchanged).unwrap(), 7);
     }
 }
+
+#[test]
+fn dispatch_handlers_may_return_typed_results() {
+    // A Result handler of a Result<UnchangedOr<Any>> dispatcher converts its
+    // success value, so handlers can return typed replacements or typed 
markers.
+    struct Typed;
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
+    impl Typed {
+        fn mutate_integer(&mut self, value: i64) -> Result<i64> {
+            Ok(value * 10)
+        }
+        fn mutate_array(
+            &mut self,
+            value: MutationView<'_, Array<i64>>,
+            mutator: &mut StructuralMutatorView<'_, Self>,
+        ) -> Result<UnchangedOr<Array<i64>>> {
+            mutator.default_mutate(self, value)?.try_cast()
+        }
+    }
+    let mutated = structural_mutate(Array::new(vec![1i64, 2]), Typed)
+        .and_then(Array::<i64>::try_from)
+        .unwrap();
+    assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![10, 20]);
+
+    struct KeepTyped;
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<UnchangedOr<Any>>)]
+    impl KeepTyped {
+        fn mutate_array(&mut self, _: MutationView<'_, Array<i64>>) -> 
Result<UnchangedOr<Array<i64>>> {
+            Ok(UnchangedOr::unchanged())
+        }
+    }
+    let source = Array::new(vec![1i64, 2]);
+    let kept = structural_mutate(source.clone(), KeepTyped)
+        .and_then(Array::<i64>::try_from)
+        .unwrap();
+    assert!(kept.same_as(&source));
+}
+
+#[test]
+fn map_callbacks_may_return_typed_results() {
+    let mapped = structural_map(
+        Array::new(vec![1i64, 2]),
+        |value: i64| -> Result<UnchangedOr<i64>> {
+            Ok(if value == 1 { UnchangedOr::changed(5) } else { 
UnchangedOr::unchanged() })
+        },
+        WalkOrder::PreOrder,
+    )
+    .and_then(Array::<i64>::try_from)
+    .unwrap();
+    assert_eq!(mapped.iter().collect::<Vec<_>>(), vec![5, 2]);
+}

Reply via email to