xso: add type-erased and dyn-compatible variant of AsXml

Jonas Schรคfer created

That way, we don't need to know the specific type of iterator or even
iteree anymore. This can turn out useful when working with `Box<dyn _>`,
that is, in contexts where we don't know the (possible or actual) types
at compile time.

Change summary

xso/ChangeLog    |  1 +
xso/src/asxml.rs | 22 ++++++++++++++++++++++
xso/src/lib.rs   | 18 +++++++++++++++++-
3 files changed, 40 insertions(+), 1 deletion(-)

Detailed changes

xso/ChangeLog ๐Ÿ”—

@@ -53,6 +53,7 @@ Version NEXT:
       - `xso::convert_via_fromstr_and_display`, a declarative macro to provide
         `AsXmlText` and `FromXmlText` implementations based on the standard
         library's `Display` and `FromStr` traits.
+      - `AsXmlDyn`, a dyn-compatible variant of `AsXml` (!573).
     * Changes
       - Generated AsXml iterator and FromXml builder types are now
         doc(hidden), to not clutter hand-written documentation with auto

xso/src/asxml.rs ๐Ÿ”—

@@ -127,6 +127,28 @@ impl<T: AsXml> fmt::Display for PrintRawXml<'_, T> {
     }
 }
 
+/// Dyn-compatible version of [`AsXml`].
+///
+/// This trait is automatically implemented for all types which implement
+/// `AsXml`.
+pub trait AsXmlDyn {
+    /// Return an iterator which emits the contents of the struct or enum as
+    /// serialisable [`Item`] items.
+    fn as_xml_dyn_iter<'x>(
+        &'x self,
+    ) -> Result<Box<dyn Iterator<Item = Result<Item<'x>, Error>> + 'x>, Error>;
+}
+
+impl<T: AsXml> AsXmlDyn for T {
+    /// Return an iterator which emits the contents of the struct or enum as
+    /// serialisable [`Item`] items by calling [`AsXml::as_xml_dyn_iter`].
+    fn as_xml_dyn_iter<'x>(
+        &'x self,
+    ) -> Result<Box<dyn Iterator<Item = Result<Item<'x>, Error>> + 'x>, Error> {
+        <T as AsXml>::as_xml_dyn_iter(self)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

xso/src/lib.rs ๐Ÿ”—

@@ -100,7 +100,7 @@ pub mod exports {
     }
 }
 
-use alloc::{borrow::Cow, string::String, vec::Vec};
+use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
 
 #[doc(inline)]
 pub use fromxml::Context;
@@ -150,6 +150,22 @@ pub trait AsXml {
     /// Return an iterator which emits the contents of the struct or enum as
     /// serialisable [`Item`] items.
     fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
+
+    /// Return the same iterator as [`as_xml_iter`][`Self::as_xml_iter`], but
+    /// boxed to erase the concrete iterator type.
+    ///
+    /// The provided implementation uses a simple cast. In most cases, it does
+    /// not make sense to override the implementation. The only exception is
+    /// if [`Self::ItemIter`] is already a boxed type, in which case
+    /// overriding this method can avoid double-boxing the iterator.
+    fn as_xml_dyn_iter<'x>(
+        &'x self,
+    ) -> Result<
+        Box<dyn Iterator<Item = Result<Item<'x>, self::error::Error>> + 'x>,
+        self::error::Error,
+    > {
+        self.as_xml_iter().map(|x| Box::new(x) as Box<_>)
+    }
 }
 
 /// Trait for a temporary object allowing to construct a struct from