Repository: mitsuhiko/minijinja (language: rust)

Issue:
2.0: Non Object Objects
Because `ValueKind` was non exhaustive 1.x carries a weird behavior where some objects are maps that should not be. For instance functions are for some filters behaving like empty maps. This also carries non optimal results where for instance the serialize into an empty struct.

On MiniJinja 2.0 the one support for this was the `ObjectType::Plain` support which at least made these objects not falsy. Now they are fully represented as `Map` which causes code such as `{% if namespace %}...{% endif %}` to fail despite namespace being a real function.

My thinking is that one solution would be to introduce the concept of plain objects and non enumerable enumerators.

* `ObjectRepr::Plain` -> `ValueKind::Plain`. This is an object that rather than behaving like a map just stringifies
* `Enumerator::NonEnumerable` -> returns a `len()` of `None` which makes it also return an `is_empty()` of `false`.

```rust
impl Object for BoxedFunction {
    fn repr(self: &Arc<Self>) -> ObjectRepr {
        ObjectRepr::Plain
    }

    fn enumeration(self: &Arc<Self>) -> Enumeration {
        Enumeration::NonEnumerable
    }

    fn call(self: &Arc<Self>, state: &State, args: &[Value]) -> Result<Value, Error> {
        self.invoke(state, args)
    }

    fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self:?}")
    }
}
```

References the following comment: https://github.com/mitsuhiko/minijinja/issues/418#issuecomment-2027922645


Discussion / hints:

Fix patch:
diff --git a/examples/dynamic-objects/src/main.rs b/examples/dynamic-objects/src/main.rs
index b037eed0..fbf9ee08 100644
--- a/examples/dynamic-objects/src/main.rs
+++ b/examples/dynamic-objects/src/main.rs
@@ -80,8 +80,7 @@ fn main() {
     env.add_function("cycler", make_cycler);
     env.add_global("magic", Value::from_object(Magic));
     env.add_global("seq", Value::from_object(SimpleDynamicSeq));
-    // TODO: add this back
-    //env.add_global("real_iter", Value::from_iterator((0..10).chain(20..30)));
+    env.add_global("real_iter", Value::from_iterator((0..10).chain(20..30)));
     env.add_template("template.html", include_str!("template.html"))
         .unwrap();
 
diff --git a/examples/self-referential-context/src/main.rs b/examples/self-referential-context/src/main.rs
index e06f296c..e4dceb12 100644
--- a/examples/self-referential-context/src/main.rs
+++ b/examples/self-referential-context/src/main.rs
@@ -22,7 +22,7 @@ impl Object for SelfReferentialContext {
                 return Enumeration::Values(keys.collect());
             }
         }
-        Enumeration::Empty
+        Enumeration::Sized(0)
     }
 }
 
diff --git a/examples/undefined-tracking/src/main.rs b/examples/undefined-tracking/src/main.rs
index bf8181b6..10ec2690 100644
--- a/examples/undefined-tracking/src/main.rs
+++ b/examples/undefined-tracking/src/main.rs
@@ -39,7 +39,7 @@ impl Object for TrackedContext {
                 return Enumeration::Values(keys.collect());
             }
         }
-        Enumeration::Empty
+        Enumeration::Sized(0)
     }
 }
 
diff --git a/examples/value-tracking/src/main.rs b/examples/value-tracking/src/main.rs
index 57808c17..013cbda5 100644
--- a/examples/value-tracking/src/main.rs
+++ b/examples/value-tracking/src/main.rs
@@ -32,7 +32,7 @@ impl Object for TrackedContext {
                 return Enumeration::Values(keys.collect());
             }
         }
-        Enumeration::Empty
+        Enumeration::Sized(0)
     }
 }
 
diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs
index 840adbe9..8d549033 100644
--- a/minijinja/src/filters.rs
+++ b/minijinja/src/filters.rs
@@ -294,7 +294,7 @@ mod builtins {
 
     use crate::error::ErrorKind;
     use crate::value::ops::as_f64;
-    use crate::value::{Kwargs, ValueKind, ValueRepr};
+    use crate::value::{Kwargs, ObjectRepr, ValueKind, ValueRepr};
     use std::borrow::Cow;
     use std::cmp::Ordering;
     use std::fmt::Write;
@@ -505,10 +505,11 @@ mod builtins {
     pub fn reverse(v: Value) -> Result<Value, Error> {
         if let Some(s) = v.as_str() {
             Ok(Value::from(s.chars().rev().collect::<String>()))
-        } else if let Some(obj) = v.as_object() {
-            Ok(Value::from_object_iter(obj, |this| {
-                Box::new(this.values().rev())
-            }))
+        } else if let Some(iter) = v.as_object().and_then(|x| x.try_iter()) {
+            // TODO: support reversible iterators?
+            let mut values = iter.collect::<Vec<_>>();
+            values.reverse();
+            Ok(Value::from_iter(values))
         } else {
             Err(Error::new(
                 ErrorKind::InvalidOperation,
@@ -547,9 +548,9 @@ mod builtins {
                 rv.push(c);
             }
             Ok(rv)
-        } else if let Some(obj) = val.as_object() {
+        } else if let Some(iter) = val.as_object().and_then(|x| x.try_iter()) {
             let mut rv = String::new();
-            for item in obj.values() {
+            for item in iter {
                 if !rv.is_empty() {
                     rv.push_str(joiner);
                 }
@@ -730,9 +731,8 @@ mod builtins {
     pub fn first(value: Value) -> Result<Value, Error> {
         if let Some(s) = value.as_str() {
             Ok(s.chars().next().map_or(Value::UNDEFINED, Value::from))
-        } else if let Some(s) = value.as_object() {
-            // FIXME: Seq only?
-            Ok(s.values().next().unwrap_or(Value::UNDEFINED))
+        } else if let Some(mut iter) = value.as_object().and_then(|x| x.try_iter()) {
+            Ok(iter.next().unwrap_or(Value::UNDEFINED))
         } else {
             Err(Error::new(
                 ErrorKind::InvalidOperation,
@@ -760,9 +760,15 @@ mod builtins {
     pub fn last(value: Value) -> Result<Value, Error> {
         if let Some(s) = value.as_str() {
             Ok(s.chars().next_back().map_or(Value::UNDEFINED, Value::from))
-        } else if let Some(obj) = value.as_object() {
-            // FIXME: Seq only?
-            Ok(obj.values().next_back().unwrap_or(Value::UNDEFINED))
+        } else if let Some(obj) = value
+            .as_object()
+            .filter(|x| matches!(x.repr(), ObjectRepr::Seq))
+        {
+            Ok(match obj.enumeration().len() {
+                Some(0) | None => None,
+                Some(idx) => obj.get_value(&Value::from(idx - 1)),
+            }
+            .unwrap_or_default())
         } else {
             Err(Error::new(
                 ErrorKind::InvalidOperation,
diff --git a/minijinja/src/functions.rs b/minijinja/src/functions.rs
index c7e4d326..101f83a9 100644
--- a/minijinja/src/functions.rs
+++ b/minijinja/src/functions.rs
@@ -99,9 +99,9 @@ env.add_function("is_adult", is_adult);
 use std::fmt;
 use std::sync::Arc;
 
-use crate::error::{Error, ErrorKind};
+use crate::error::Error;
 use crate::utils::SealedMarker;
-use crate::value::{ArgType, FunctionArgs, FunctionResult, Object, Value};
+use crate::value::{ArgType, Enumeration, FunctionArgs, FunctionResult, Object, ObjectRepr, Value};
 use crate::vm::State;
 
 type FuncFunc = dyn Fn(&State, &[Value]) -> Result<Value, Error> + Sync + Send + 'static;
@@ -248,12 +248,16 @@ impl fmt::Debug for BoxedFunction {
 }
 
 impl Object for BoxedFunction {
-    fn call(self: &Arc<Self>, state: &State, args: &[Value]) -> Result<Value, Error> {
-        self.invoke(state, args)
+    fn repr(self: &Arc<Self>) -> ObjectRepr {
+        ObjectRepr::Plain
     }
 
-    fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{self:?}")
+    fn enumeration(self: &Arc<Self>) -> Enumeration {
+        Enumeration::NonEnumerable
+    }
+
+    fn call(self: &Arc<Self>, state: &State, args: &[Value]) -> Result<Value, Error> {
+        self.invoke(state, args)
     }
 }
 
@@ -261,6 +265,7 @@ impl Object for BoxedFunction {
 mod builtins {
     use super::*;
 
+    use crate::error::ErrorKind;
     use crate::value::{ObjectRepr, Rest, ValueMap, ValueRepr};
 
     /// Returns a range.
@@ -334,7 +339,9 @@ mod builtins {
             None => ValueMap::default(),
             Some(value) => match value.0 {
                 ValueRepr::Undefined => ValueMap::default(),
-                ValueRepr::Object(obj) if obj.repr() == ObjectRepr::Map => obj.iter().collect(),
+                ValueRepr::Object(obj) if obj.repr() == ObjectRepr::Map => {
+                    obj.try_iter_pairs().into_iter().flatten().collect()
+                }
                 _ => return Err(Error::from(ErrorKind::InvalidOperation)),
             },
         };
@@ -388,8 +395,12 @@ mod builtins {
     pub fn namespace(defaults: Option<Value>) -> Result<Value, Error> {
         let ns = crate::value::namespace_object::Namespace::default();
         if let Some(defaults) = defaults {
-            if let Some(obj) = defaults.as_object() {
-                for (key, value) in obj.iter() {
+            if let Some(pairs) = defaults
+                .as_object()
+                .filter(|x| matches!(x.repr(), ObjectRepr::Map))
+                .and_then(|x| x.try_iter_pairs())
+            {
+                for (key, value) in pairs {
                     if let Some(key) = key.as_str() {
                         ns.set_field(key, value);
                     }
diff --git a/minijinja/src/utils.rs b/minijinja/src/utils.rs
index fafb1ffc..4dcd4b96 100644
--- a/minijinja/src/utils.rs
+++ b/minijinja/src/utils.rs
@@ -5,7 +5,7 @@ use std::iter::{once, repeat};
 use std::str::Chars;
 
 use crate::error::{Error, ErrorKind};
-use crate::value::{OwnedValueIterator, StringType, Value, ValueKind, ValueRepr};
+use crate::value::{StringType, Value, ValueIter, ValueKind, ValueRepr};
 use crate::Output;
 
 /// internal marker to seal up some trait methods
@@ -161,9 +161,8 @@ impl UndefinedBehavior {
     /// If the value is undefined, then iteration fails if the behavior is set to strict,
     /// otherwise it succeeds with an empty iteration.  This is also internally used in the
     /// engine to convert values to lists.
-    pub(crate) fn try_iter(self, value: Value) -> Result<OwnedValueIterator, Error> {
-        self.assert_iterable(&value)
-            .and_then(|_| value.try_iter_owned())
+    pub(crate) fn try_iter(self, value: Value) -> Result<ValueIter, Error> {
+        self.assert_iterable(&value).and_then(|_| value.try_iter())
     }
 
     /// Are we strict on iteration?
diff --git a/minijinja/src/value/argtypes.rs b/minijinja/src/value/argtypes.rs
index 0749d6ad..26037a9e 100644
--- a/minijinja/src/value/argtypes.rs
+++ b/minijinja/src/value/argtypes.rs
@@ -6,7 +6,9 @@ use std::sync::Arc;
 
 use crate::error::{Error, ErrorKind};
 use crate::utils::UndefinedBehavior;
-use crate::value::{DynObject, Packed, StringType, Value, ValueKind, ValueMap, ValueRepr};
+use crate::value::{
+    DynObject, ObjectRepr, Packed, StringType, Value, ValueKind, ValueMap, ValueRepr,
+};
 use crate::vm::State;
 
 use super::{Enumeration, Object};
@@ -968,11 +970,14 @@ impl<'a, T: ArgType<'a, Output = T>> ArgType<'a> for Vec<T> {
         match value {
             None => Ok(Vec::new()),
             Some(value) => {
-                let seq = ok!(value
+                let iter = ok!(value
                     .as_object()
+                    // XXX: needs updating for iterators
+                    .filter(|x| matches!(x.repr(), ObjectRepr::Seq))
+                    .and_then(|x| x.try_iter())
                     .ok_or_else(|| { Error::new(ErrorKind::InvalidOperation, "not a sequence") }));
                 let mut rv = Vec::new();
-                for value in seq.values() {
+                for value in iter {
                     rv.push(ok!(T::from_value_owned(value)));
                 }
                 Ok(rv)
@@ -981,11 +986,14 @@ impl<'a, T: ArgType<'a, Output = T>> ArgType<'a> for Vec<T> {
     }
 
     fn from_value_owned(value: Value) -> Result<Self, Error> {
-        let seq = ok!(value
+        let iter = ok!(value
             .as_object()
+            // XXX: needs updating for iterators
+            .filter(|x| matches!(x.repr(), ObjectRepr::Seq))
+            .and_then(|x| x.try_iter())
             .ok_or_else(|| { Error::new(ErrorKind::InvalidOperation, "not a sequence") }));
         let mut rv = Vec::new();
-        for value in seq.values() {
+        for value in iter {
             rv.push(ok!(T::from_value_owned(value)));
         }
         Ok(rv)
diff --git a/minijinja/src/value/deserialize.rs b/minijinja/src/value/deserialize.rs
index 1bfc2411..5d5499bc 100644
--- a/minijinja/src/value/deserialize.rs
+++ b/minijinja/src/value/deserialize.rs
@@ -180,16 +180,21 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer {
             ValueRepr::Undefined | ValueRepr::None => visitor.visit_unit(),
             ValueRepr::Bytes(ref v) => visitor.visit_bytes(v),
             ValueRepr::Object(o) => match o.repr() {
-                ObjectRepr::Seq => visitor.visit_seq(de::value::SeqDeserializer::new(
-                    o.values().map(ValueDeserializer::new),
-                )),
-                ObjectRepr::Map => {
-                    let iter = o
-                        .iter()
-                        .map(|(k, v)| (ValueDeserializer::new(k), ValueDeserializer::new(v)));
-
-                    visitor.visit_map(de::value::MapDeserializer::new(iter))
+                ObjectRepr::Plain => Err(de::Error::custom("cannot deserialize plain objects")),
+                ObjectRepr::Seq | ObjectRepr::Iterator => {
+                    visitor.visit_seq(de::value::SeqDeserializer::new(
+                        o.try_iter()
+                            .into_iter()
+                            .flatten()
+                            .map(ValueDeserializer::new),
+                    ))
                 }
+                ObjectRepr::Map => visitor.visit_map(de::value::MapDeserializer::new(
+                    o.try_iter_pairs()
+                        .into_iter()
+                        .flatten()
+                        .map(|(k, v)| (ValueDeserializer::new(k), ValueDeserializer::new(v))),
+                )),
             },
         }
     }
@@ -307,7 +312,12 @@ impl<'de> de::VariantAccess<'de> for VariantDeserializer {
         match self.value.as_ref().and_then(|x| x.as_object()) {
             Some(obj) if matches!(obj.repr(), ObjectRepr::Seq) => {
                 de::Deserializer::deserialize_any(
-                    de::value::SeqDeserializer::new(obj.values().map(ValueDeserializer::new)),
+                    de::value::SeqDeserializer::new(
+                        obj.try_iter()
+                            .into_iter()
+                            .flatten()
+                            .map(ValueDeserializer::new),
+                    ),
                     visitor,
                 )
             }
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index e9550979..9f3e6935 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -37,9 +37,8 @@
 //! `Value::from_iterator` function instead:
 //!
 //! ```
-//! // TODO: Did this create a use-once value?
-//! // # use minijinja::value::Value;
-//! // let value: Value = Value::from_iterator(1..10);
+//! # use minijinja::value::Value;
+//! let value: Value = Value::from_iterator(1..10);
 //! ```
 //!
 //! To to into the inverse directly the various [`TryFrom`](std::convert::TryFrom)
@@ -136,14 +135,14 @@ use std::cmp::Ordering;
 use std::collections::BTreeMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
-use std::marker::PhantomData;
-use std::sync::Arc;
+use std::sync::{Arc, Mutex};
 
 use serde::ser::{Serialize, Serializer};
 
 use crate::error::{Error, ErrorKind, Result};
 use crate::functions;
 use crate::utils::OnDrop;
+use crate::value::object::ValueDbgRender;
 use crate::value::ops::as_f64;
 use crate::value::serialize::transform;
 use crate::vm::State;
@@ -167,7 +166,6 @@ mod serialize;
 
 #[cfg(feature = "deserialization")]
 pub use self::deserialize::ViaDeserialize;
-use self::object::ObjectKeyValueIter;
 
 // We use in-band signalling to roundtrip some internal values.  This is
 // not ideal but unfortunately there is no better system in serde today.
@@ -270,6 +268,10 @@ pub enum ValueKind {
     Seq,
     /// The value is a key/value mapping.
     Map,
+    /// An iterator
+    Iterator,
+    /// A plain object without specific behavior.
+    Plain,
 }
 
 impl fmt::Display for ValueKind {
@@ -283,6 +285,8 @@ impl fmt::Display for ValueKind {
             ValueKind::Bytes => "bytes",
             ValueKind::Seq => "sequence",
             ValueKind::Map => "map",
+            ValueKind::Iterator => "iterator",
+            ValueKind::Plain => "plain object",
         })
     }
 }
@@ -385,14 +389,15 @@ impl PartialEq for Value {
                 Some(ops::CoerceResult::Str(a, b)) => a == b,
                 None => {
                     if let (Some(a), Some(b)) = (self.as_object(), other.as_object()) {
-                        let (a_keys, b_keys) = (a.enumeration(), b.enumeration());
-                        if a_keys.len() != b_keys.len() {
-                            return false;
+                        if a.repr() != b.repr() {
+                            false
+                        } else if let (Some(ak), Some(bk)) =
+                            (a.try_iter_pairs(), b.try_iter_pairs())
+                        {
+                            ak.eq(bk)
+                        } else {
+                            false
                         }
-
-                        a_keys
-                            .into_iter()
-                            .all(|key| a.get_value(&key) == b.get_value(&key))
                     } else {
                         false
                     }
@@ -435,9 +440,11 @@ impl Ord for Value {
                         (Ok(a), Ok(b)) => a.cmp(b),
                         _ => self.len().cmp(&other.len()),
                     },
-                    (ValueKind::Map, ValueKind::Map) => match (self.as_object(), other.as_object())
-                    {
-                        (Some(a), Some(b)) => a.iter().cmp(b.iter()),
+                    (ValueKind::Map, ValueKind::Map) => match (
+                        self.as_object().and_then(|x| x.try_iter_pairs()),
+                        other.as_object().and_then(|x| x.try_iter_pairs()),
+                    ) {
+                        (Some(a), Some(b)) => a.cmp(b),
                         _ => self.len().cmp(&other.len()),
                     },
                     _ => Ordering::Equal,
@@ -597,53 +604,125 @@ impl Value {
         Value::from(ValueRepr::Object(value.into()))
     }
 
-    /// Creates a sequence that iterates over the given value.
+    /// Creates a value from an iterator.
     ///
-    /// The function is invoked to create an iterator which is then turned into
-    /// a sequence or iterator.
-    pub fn from_object_iter<T, F>(object: T, maker: F) -> Value
+    /// This takes an iterator (yielding values that can be turned into a [`Value`])
+    /// and returns a value that can be iterated over.  Today this value looks a bit like
+    /// a sequence (and will pretend to be one) but this is misleading.  Such values are
+    /// actually objects implementing [`IteratorObject`] but due to backwards
+    /// compatibility reasons it's not possible to give them a distinct type.
+    ///
+    /// Iterators that implement [`ExactSizeIterator`] or have a matching lower and upper
+    /// bound on the [`Iterator::size_hint`] report a known `loop.length`.  Iterators that
+    /// do not fulfill these requirements will not.  The same is true for `revindex` and
+    /// similar properties.
+    ///
+    /// ```
+    /// # use minijinja::value::Value;
+    /// let val = Value::from_iterator(0..10);
+    /// ```
+    ///
+    /// This iterator can only be iterated over once.
+    pub fn from_iterator<I, T>(iter: I) -> Value
     where
-        T: Send + Sync + 'static,
-        F: for<'a> Fn(&'a T) -> Box<dyn Iterator<Item = Value> + Send + Sync + 'a>
-            + Send
-            + Sync
-            + 'static,
+        I: Iterator<Item = T> + Send + Sync + 'static,
+        T: Into<Value> + 'static,
     {
-        struct IterObject {
-            iter: Box<dyn Iterator<Item = Value> + Send + Sync + 'static>,
-            _object: DynObject,
-        }
+        struct SimpleIteratorObject<I, T>(Mutex<Option<I>>)
+        where
+            I: Iterator<Item = T> + Send + Sync + 'static,
+            T: Into<Value> + 'static;
 
-        impl Iterator for IterObject {
-            type Item = Value;
+        impl<I, T> fmt::Debug for SimpleIteratorObject<I, T>
+        where
+            I: Iterator<Item = T> + Send + Sync + 'static,
+            T: Into<Value> + 'static,
+        {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.debug_tuple("<iterator>").finish()
+            }
+        }
 
-            fn next(&mut self) -> Option<Self::Item> {
-                self.iter.next()
+        impl<I, T> Object for SimpleIteratorObject<I, T>
+        where
+            I: Iterator<Item = T> + Send + Sync + 'static,
+            T: Into<Value> + 'static,
+        {
+            fn repr(self: &Arc<Self>) -> ObjectRepr {
+                ObjectRepr::Iterator
             }
 
-            fn size_hint(&self) -> (usize, Option<usize>) {
-                self.iter.size_hint()
+            fn custom_iter(
+                self: &Arc<Self>,
+            ) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+                let iter = self.0.lock().unwrap().take();
+
+                struct Iter<I, T>(Option<I>)
+                where
+                    I: Iterator<Item = T> + Send + Sync + 'static,
+                    T: Into<Value> + 'static;
+
+                impl<I, T> Iterator for Iter<I, T>
+                where
+                    I: Iterator<Item = T> + Send + Sync + 'static,
+                    T: Into<Value> + 'static,
+                {
+                    type Item = Value;
+
+                    fn next(&mut self) -> Option<Self::Item> {
+                        match &mut self.0 {
+                            Some(x) => x.next().map(Into::into),
+                            None => None,
+                        }
+                    }
+
+                    fn size_hint(&self) -> (usize, Option<usize>) {
+                        match &self.0 {
+                            Some(x) => x.size_hint(),
+                            None => (0, Some(0)),
+                        }
+                    }
+                }
+
+                Some(Box::new(Iter(iter)))
             }
         }
+        Value::from_object(SimpleIteratorObject(Mutex::new(Some(iter.fuse()))))
+    }
 
-        struct IterObjectMaker<T, F> {
+    /// Creates an iterator that iterates over the given value.
+    ///
+    /// Unlike [`from_iterator`](Self::from_iterator) this returns an iterator
+    /// that can be iterated over multiple times.  Because of this it also will
+    /// render like a sequence when debug printed.
+    ///
+    /// ```rust
+    /// # use minijinja::value::Value;
+    /// let val = Value::from_object_iter(vec![1, 2, 3], |vec| {
+    ///     Box::new(vec.iter().copied().map(Value::from))
+    /// });
+    /// assert_eq!(val.to_string(), "[1, 2, 3]");
+    /// ````
+    pub fn from_object_iter<T, F>(object: T, maker: F) -> Value
+    where
+        T: Send + Sync + 'static,
+        F: for<'a> Fn(&'a T) -> Box<dyn Iterator<Item = Value> + Send + Sync + 'a>
+            + Send
+            + Sync
+            + 'static,
+    {
+        struct FromObjectIter<T, F> {
             maker: F,
             object: T,
         }
 
-        impl fmt::Debug for IterObject {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                f.debug_tuple("IterObject").finish()
-            }
-        }
-
-        impl<T, F> fmt::Debug for IterObjectMaker<T, F> {
+        impl<T, F> fmt::Debug for FromObjectIter<T, F> {
             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                f.debug_struct("IterObjectMaker").finish()
+                f.debug_struct("FromObjectIter").finish()
             }
         }
 
-        impl<T, F> Object for IterObjectMaker<T, F>
+        impl<T, F> Object for FromObjectIter<T, F>
         where
             T: Send + Sync + 'static,
             F: for<'a> Fn(&'a T) -> Box<dyn Iterator<Item = Value> + Send + Sync + 'a>
@@ -652,25 +731,49 @@ impl Value {
                 + 'static,
         {
             fn repr(self: &Arc<Self>) -> ObjectRepr {
-                ObjectRepr::Seq
+                ObjectRepr::Iterator
             }
 
-            // XXX: this seems very wrong.
-            // see also https://github.com/mitsuhiko/minijinja/issues/453
-            fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
-                Some(key.clone())
+            fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result
+            where
+                Self: Sized + 'static,
+            {
+                let mut dbg = f.debug_list();
+                for value in self.try_iter().into_iter().flatten() {
+                    dbg.entry(&ValueDbgRender(&value));
+                }
+                dbg.finish()
             }
 
-            fn enumeration(self: &Arc<Self>) -> Enumeration {
+            fn custom_iter(
+                self: &Arc<Self>,
+            ) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+                struct IterWrapper {
+                    iter: Box<dyn Iterator<Item = Value> + Send + Sync + 'static>,
+                    _object: DynObject,
+                }
+
+                impl Iterator for IterWrapper {
+                    type Item = Value;
+
+                    fn next(&mut self) -> Option<Self::Item> {
+                        self.iter.next()
+                    }
+
+                    fn size_hint(&self) -> (usize, Option<usize>) {
+                        self.iter.size_hint()
+                    }
+                }
+
                 let iter: Box<dyn Iterator<Item = Value> + Send + Sync + '_> =
                     (self.maker)(&self.object);
                 let iter = unsafe { std::mem::transmute(iter) };
                 let _object = DynObject::new(self.clone());
-                Enumeration::Iterator(Box::new(IterObject { iter, _object }))
+                Some(Box::new(IterWrapper { iter, _object }))
             }
         }
 
-        Value::from_object(IterObjectMaker { maker, object })
+        Value::from_object(FromObjectIter { maker, object })
     }
 
     /// Creates a callable value from a function.
@@ -709,6 +812,8 @@ impl Value {
             ValueRepr::Object(ref obj) => match obj.repr() {
                 ObjectRepr::Map => ValueKind::Map,
                 ObjectRepr::Seq => ValueKind::Seq,
+                ObjectRepr::Iterator => ValueKind::Iterator,
+                ObjectRepr::Plain => ValueKind::Plain,
             },
         }
     }
@@ -923,11 +1028,31 @@ impl Value {
     /// }
     /// # Ok(()) }
     /// ```
-    pub fn try_iter(&self) -> Result<ValueIter<'_>, Error> {
-        self.try_iter_owned().map(|inner| ValueIter {
-            _marker: PhantomData,
-            inner,
-        })
+    pub fn try_iter(&self) -> Result<ValueIter, Error> {
+        let imp = match self.0 {
+            ValueRepr::None | ValueRepr::Undefined => ValueIterImpl::Empty,
+            ValueRepr::String(ref s, _) => {
+                ValueIterImpl::Chars(0, s.chars().count(), Arc::clone(s))
+            }
+            ValueRepr::Object(ref obj) => {
+                if let Some(iter) = obj.try_iter() {
+                    ValueIterImpl::Dyn(iter)
+                } else {
+                    return Err(Error::new(
+                        ErrorKind::InvalidOperation,
+                        format!("{} is not iterable", self.kind()),
+                    ));
+                }
+            }
+            _ => {
+                return Err(Error::new(
+                    ErrorKind::InvalidOperation,
+                    format!("{} is not iterable", self.kind()),
+                ))
+            }
+        };
+
+        Ok(ValueIter { imp })
     }
 
     /// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t.
@@ -985,12 +1110,15 @@ impl Value {
         }
 
         match self.0 {
-            ValueRepr::Object(ref dy) => {
-                let len = dy.enumeration().len();
-                let idx = len.and_then(|n| index(key, || n));
-                let value = idx.map(Value::from);
-                dy.get_value(value.as_ref().unwrap_or(key))
-            }
+            ValueRepr::Object(ref dy) => match dy.repr() {
+                ObjectRepr::Map | ObjectRepr::Plain | ObjectRepr::Iterator => dy.get_value(key),
+                ObjectRepr::Seq => {
+                    let len = dy.enumeration().len();
+                    let idx = len.and_then(|n| index(key, || n));
+                    let value = idx.map(Value::from);
+                    dy.get_value(value.as_ref().unwrap_or(key))
+                }
+            },
             ValueRepr::String(ref s, _) => {
                 let idx = some!(index(key, || s.chars().count()));
                 s.chars().nth(idx).map(Value::from)
@@ -1087,29 +1215,6 @@ impl Value {
         ))
     }
 
-    /// Iterates over the value without holding a reference.
-    pub(crate) fn try_iter_owned(&self) -> Result<OwnedValueIterator, Error> {
-        let (iter_state, len) = match self.0 {
-            ValueRepr::None | ValueRepr::Undefined => (ValueIteratorState::Empty, Some(0)),
-            ValueRepr::String(ref s, _) => (
-                ValueIteratorState::Chars(0, Arc::clone(s)),
-                Some(s.chars().count()),
-            ),
-            ValueRepr::Object(ref obj) => {
-                let len = obj.enumeration().len();
-                (ValueIteratorState::Dyn(obj.repr(), obj.iter()), len)
-            }
-            _ => {
-                return Err(Error::new(
-                    ErrorKind::InvalidOperation,
-                    format!("{} is not iterable", self.kind()),
-                ))
-            }
-        };
-
-        Ok(OwnedValueIterator { iter_state, len })
-    }
-
     #[cfg(feature = "builtins")]
     pub(crate) fn get_path(&self, path: &str) -> Result<Value, Error> {
         let mut rv = self.clone();
@@ -1161,12 +1266,15 @@ impl Serialize for Value {
             ValueRepr::String(ref s, _) => serializer.serialize_str(s),
             ValueRepr::Bytes(ref b) => serializer.serialize_bytes(b),
             ValueRepr::Object(ref o) => match o.repr() {
-                ObjectRepr::Seq => {
+                ObjectRepr::Plain => serializer.serialize_str(&o.to_string()),
+                ObjectRepr::Seq | ObjectRepr::Iterator => {
                     use serde::ser::SerializeSeq;
                     let enumeration = o.enumeration();
                     let mut seq = ok!(serializer.serialize_seq(enumeration.len()));
-                    for item in o.values() {
-                        ok!(seq.serialize_element(&item));
+                    if let Some(iter) = o.try_iter() {
+                        for item in iter {
+                            ok!(seq.serialize_element(&item));
+                        }
                     }
 
                     seq.end()
@@ -1174,8 +1282,10 @@ impl Serialize for Value {
                 ObjectRepr::Map => {
                     use serde::ser::SerializeMap;
                     let mut map = ok!(serializer.serialize_map(None));
-                    for (key, value) in o.iter() {
-                        ok!(map.serialize_entry(&key, &value));
+                    if let Some(iter) = o.try_iter_pairs() {
+                        for (key, value) in iter {
+                            ok!(map.serialize_entry(&key, &value));
+                        }
                     }
 
                     map.end()
@@ -1185,77 +1295,47 @@ impl Serialize for Value {
     }
 }
 
-/// Iterates over a value.
-pub struct ValueIter<'a> {
-    _marker: PhantomData<&'a Value>,
-    inner: OwnedValueIterator,
+/// Utility to iterate over values.
+pub struct ValueIter {
+    imp: ValueIterImpl,
 }
 
-impl<'a> Iterator for ValueIter<'a> {
+impl Iterator for ValueIter {
     type Item = Value;
 
-    #[inline(always)]
     fn next(&mut self) -> Option<Self::Item> {
-        self.inner.next()
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.inner.size_hint()
-    }
-}
-
-pub(crate) struct OwnedValueIterator {
-    iter_state: ValueIteratorState,
-    len: Option<usize>,
-}
-
-impl Iterator for OwnedValueIterator {
-    type Item = Value;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        self.iter_state.advance_state().map(|x| {
-            if let Some(ref mut len) = self.len {
-                *len -= 1;
+        match &mut self.imp {
+            ValueIterImpl::Empty => None,
+            ValueIterImpl::Chars(offset, len, ref s) => {
+                (s as &str)[*offset..].chars().next().map(|c| {
+                    *offset += c.len_utf8();
+                    *len -= 1;
+                    Value::from(c)
+                })
             }
-            x
-        })
+            ValueIterImpl::Dyn(iter) => iter.next(),
+        }
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
-        if let ValueIteratorState::Dyn(_, ref iter) = self.iter_state {
-            return iter.size_hint();
+        match self.imp {
+            ValueIterImpl::Empty => (0, Some(0)),
+            ValueIterImpl::Chars(_, len, _) => (0, Some(len)),
+            ValueIterImpl::Dyn(ref iter) => iter.size_hint(),
         }
-
-        (self.len.unwrap_or(0), self.len)
     }
 }
 
-impl fmt::Debug for OwnedValueIterator {
+impl fmt::Debug for ValueIter {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("ValueIterator").finish()
     }
 }
 
-enum ValueIteratorState {
+enum ValueIterImpl {
     Empty,
-    Chars(usize, Arc<str>),
-    Dyn(ObjectRepr, ObjectKeyValueIter),
-}
-
-impl ValueIteratorState {
-    fn advance_state(&mut self) -> Option<Value> {
-        match self {
-            ValueIteratorState::Empty => None,
-            ValueIteratorState::Chars(offset, ref s) => {
-                (s as &str)[*offset..].chars().next().map(|c| {
-                    *offset += c.len_utf8();
-                    Value::from(c)
-                })
-            }
-            ValueIteratorState::Dyn(ObjectRepr::Map, iter) => iter.next().map(|kv| kv.0),
-            ValueIteratorState::Dyn(ObjectRepr::Seq, iter) => iter.next().map(|kv| kv.1),
-        }
-    }
+    Chars(usize, usize, Arc<str>),
+    Dyn(Box<dyn Iterator<Item = Value> + Send + Sync>),
 }
 
 #[cfg(test)]
diff --git a/minijinja/src/value/object.rs b/minijinja/src/value/object.rs
index 7ece8f1d..3df7b96a 100644
--- a/minijinja/src/value/object.rs
+++ b/minijinja/src/value/object.rs
@@ -10,7 +10,7 @@ use crate::value::{intern, Value, ValueMap, ValueRepr};
 use crate::vm::State;
 
 /// A trait that represents a dynamic object.
-pub trait Object: fmt::Debug {
+pub trait Object: fmt::Debug + Send + Sync {
     /// Indicates the natural representation of an object.
     ///
     /// The default implementation returns [`ObjectRepr::Map`].
@@ -27,9 +27,25 @@ pub trait Object: fmt::Debug {
     /// Returns the enumeration of the object.
     ///
     /// For more information see [`Enumeration`].  The default implementation
-    /// returns the empty enumeration.
+    /// returns a empty enumeration if the object repr is a `Map` or `Seq`,
+    /// and `NonEnumerable` for `Plain` objects or `Iterator`s.
     fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::Empty
+        match self.repr() {
+            ObjectRepr::Plain | ObjectRepr::Iterator => Enumeration::NonEnumerable,
+            ObjectRepr::Map | ObjectRepr::Seq => Enumeration::Sized(0),
+        }
+    }
+
+    /// Overrides the default iteration behavior.
+    ///
+    /// If this returns `None` then the default object iteration as
+    /// defined by the object's `repr` and `enumeration` is used.
+    /// When this is implemented it's recommended that the object
+    /// repr is set to [`ObjectRepr::Iterator`].  The engine does
+    /// ensure that it can also be implemented for other object types
+    /// but the behavior can be confusing.
+    fn custom_iter(self: &Arc<Self>) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+        None
     }
 
     /// The engine calls this to invoke the object itself.
@@ -71,44 +87,82 @@ pub trait Object: fmt::Debug {
     ///
     /// The default implementation is specific to the behavior of
     /// [`repr`](Self::repr) and usually does not need modification.
-    fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        struct DbgRender<'a>(&'a Value);
-
-        impl<'a> fmt::Debug for DbgRender<'a> {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                if let ValueRepr::Object(ref obj) = self.0 .0 {
-                    obj.render(f)
-                } else {
-                    fmt::Debug::fmt(&self.0, f)
-                }
-            }
-        }
-
+    fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result
+    where
+        Self: Sized + 'static,
+    {
         match self.repr() {
             ObjectRepr::Map => {
                 let mut dbg = f.debug_map();
-                for key in self.enumeration() {
-                    if let Some(value) = self.get_value(&key) {
-                        dbg.entry(&DbgRender(&key), &DbgRender(&value));
-                    }
+                for (key, value) in self.try_iter_pairs().into_iter().flatten() {
+                    dbg.entry(&ValueDbgRender(&key), &ValueDbgRender(&value));
                 }
-
                 dbg.finish()
             }
             ObjectRepr::Seq => {
                 let mut dbg = f.debug_list();
-                for key in self.enumeration() {
-                    if let Some(value) = self.get_value(&key) {
-                        dbg.entry(&DbgRender(&value));
-                    }
+                for value in self.try_iter().into_iter().flatten() {
+                    dbg.entry(&ValueDbgRender(&value));
                 }
-
                 dbg.finish()
             }
+            ObjectRepr::Plain | ObjectRepr::Iterator => {
+                write!(f, "{self:?}")
+            }
         }
     }
 }
 
+macro_rules! impl_object_helpers {
+    ($vis:vis $self_ty: ty) => {
+        /// Iterates over an object.
+        $vis fn try_iter(self: $self_ty) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+            if let Some(iter) = self.custom_iter() {
+                Some(iter)
+            } else {
+                let iter = some!(self.clone().enumeration().try_into_iter());
+                Some(match self.repr() {
+                    ObjectRepr::Plain | ObjectRepr::Iterator => return None,
+                    ObjectRepr::Map => Box::new(iter),
+                    ObjectRepr::Seq => {
+                        let self_clone = self.clone();
+                        Box::new(
+                            iter.map(move |key| self_clone.get_value(&key).unwrap_or_default()),
+                        )
+                    }
+                })
+            }
+        }
+
+        /// Iterate over key and value at once.
+        $vis fn try_iter_pairs(
+            self: $self_ty,
+        ) -> Option<Box<dyn Iterator<Item = (Value, Value)> + Send + Sync>> {
+            let self_clone = self.clone();
+            if let Some(iter) = self.custom_iter() {
+                let repr = self.repr();
+                Some(Box::new(
+                    iter.enumerate().map(move |(idx, item)| {
+                        match repr {
+                            ObjectRepr::Seq | ObjectRepr::Plain | ObjectRepr::Iterator => (Value::from(idx), item),
+                            ObjectRepr::Map => {
+                                let value = self_clone.get_value(&item);
+                                (item, value.unwrap_or_default())
+                            }
+                        }
+                    })
+                ))
+            } else {
+                let iter = some!(self.clone().enumeration().try_into_iter());
+                Some(Box::new(iter.map(move |key| {
+                    let value = self_clone.get_value(&key);
+                    (key, value.unwrap_or_default())
+                })))
+            }
+        }
+    };
+}
+
 /// Provides utility methods for working with objects.
 pub trait ObjectExt: Object + Send + Sync + 'static {
     /// Creates a new iterator enumeration that projects into the given object.
@@ -144,31 +198,36 @@ pub trait ObjectExt: Object + Send + Sync + 'static {
         let _object = self.clone();
         Enumeration::Iterator(Box::new(IterObject { iter, _object }))
     }
+
+    impl_object_helpers!(&Arc<Self>);
 }
 
 impl<T: Object + Send + Sync + 'static> ObjectExt for T {}
 
 /// Utility type to enumerate an object.
 ///
-/// The purpose of this type is to reveal the contents of an object.  Depending
-/// on the shape of the object different values are appropriate.
+/// The purpose of this type is to reveal the contents of an object by the key.  An
+/// enumeration always reveals the indexes or keys of an object.  The user of
+/// such an enumeration must thus call into [`get_value`](Object::get_value) to
+/// reveal the associated value to that key.
+///
+/// Enumerations are used as the primary method to automatically derive the
+/// iteration behavior of an object.
 #[non_exhaustive]
 pub enum Enumeration {
-    /// An empty object or sequences.
-    Empty,
     /// A list of known values.
     ///
     /// If the object is a sequence these are the values, if the object is a
     /// map this are actually the keys.
     Values(Vec<Value>),
-    /// A slice of static strings, usually to represent keys.
+    /// A slice of static string keys.
     Static(&'static [&'static str]),
-    /// A dynamic iterator over some contents.
+    /// A dynamic iterator over keys.
     Iterator(Box<dyn Iterator<Item = Value> + Send + Sync>),
-    /// A dynamic iterator that also can be reversed.
-    ReversibleIter(Box<dyn DoubleEndedIterator<Item = Value> + Send + Sync>),
-    /// Iteration is done by calling [`get_value`](Object::get_value) from 0 to `usize`.
+    /// Indicates indexes from 0 to `usize`.
     Sized(usize),
+    /// A non enumerable enumeration.  This fails iteration.
+    NonEnumerable,
 }
 
 /// Iterates over an enumeration.
@@ -178,18 +237,20 @@ enum EnumerationIterRepr {
     Values(std::vec::IntoIter<Value>),
     Static(std::slice::Iter<'static, &'static str>),
     Iterator(Box<dyn Iterator<Item = Value> + Send + Sync>),
-    ReversibleIter(Box<dyn DoubleEndedIterator<Item = Value> + Send + Sync>),
     Sized(Range<usize>),
-    Empty,
 }
 
 /// Defines the natural representation of this object.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum ObjectRepr {
+    /// An object that has no reasonable representation.  Usually stringifies.
+    Plain,
     /// serializes to {...} and over the enumeration, values
     Map,
     /// serializes to [...] over its values
     Seq,
+    /// Similar to `Seq` but without indexing
+    Iterator,
 }
 
 type_erase! {
@@ -200,6 +261,8 @@ type_erase! {
 
         fn enumeration(&self) -> Enumeration;
 
+        fn custom_iter(&self) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>>;
+
         fn call(
             &self,
             state: &State<'_, '_>,
@@ -221,18 +284,6 @@ type_erase! {
     }
 }
 
-/// Iterates over [`Object`]
-pub struct ObjectValueIter {
-    enumeration: EnumerationIter,
-    object: DynObject,
-}
-
-/// Iterates over [`Object`]
-pub struct ObjectKeyValueIter {
-    enumeration: EnumerationIter,
-    object: DynObject,
-}
-
 impl DynObject {
     /// Checks if the object is of a specific type.
     ///
@@ -241,33 +292,17 @@ impl DynObject {
         self.downcast::<T>().is_some()
     }
 
-    /// Iterator over the values of an object.
-    // XXX: remove this
-    // see https://github.com/mitsuhiko/minijinja/issues/456
-    pub fn values(&self) -> ObjectValueIter {
-        ObjectValueIter {
-            enumeration: self.enumeration().into_iter(),
-            object: self.clone(),
-        }
-    }
-
-    /// Iterator over the keys, values of an object.
-    // XXX: make this iterate over keys for maps and values for sequences and iterators.
-    // see https://github.com/mitsuhiko/minijinja/issues/456
-    pub fn iter(&self) -> ObjectKeyValueIter {
-        ObjectKeyValueIter {
-            enumeration: self.enumeration().into_iter(),
-            object: self.clone(),
-        }
-    }
+    impl_object_helpers!(pub &Self);
 }
 
 impl Hash for DynObject {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.iter().for_each(|(k, v)| {
-            k.hash(state);
-            v.hash(state);
-        })
+        if let Some(iter) = self.try_iter_pairs() {
+            for (key, value) in iter {
+                key.hash(state);
+                value.hash(state);
+            }
+        }
     }
 }
 
@@ -277,52 +312,6 @@ impl fmt::Display for DynObject {
     }
 }
 
-impl Iterator for ObjectValueIter {
-    type Item = Value;
-
-    #[inline(always)]
-    fn next(&mut self) -> Option<Self::Item> {
-        self.object.get_value(&self.enumeration.next()?)
-    }
-
-    #[inline(always)]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.enumeration.size_hint()
-    }
-}
-
-impl DoubleEndedIterator for ObjectValueIter {
-    #[inline(always)]
-    fn next_back(&mut self) -> Option<Self::Item> {
-        self.object.get_value(&self.enumeration.next_back()?)
-    }
-}
-
-impl Iterator for ObjectKeyValueIter {
-    type Item = (Value, Value);
-
-    #[inline(always)]
-    fn next(&mut self) -> Option<Self::Item> {
-        let key = self.enumeration.next()?;
-        let value = self.object.get_value(&key)?;
-        Some((key, value))
-    }
-
-    #[inline(always)]
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.enumeration.size_hint()
-    }
-}
-
-impl DoubleEndedIterator for ObjectKeyValueIter {
-    #[inline(always)]
-    fn next_back(&mut self) -> Option<Self::Item> {
-        let key = self.enumeration.next_back()?;
-        let value = self.object.get_value(&key)?;
-        Some((key, value))
-    }
-}
-
 impl Enumeration {
     /// Returns the length if the object has one.
     pub fn len(&self) -> Option<usize> {
@@ -333,12 +322,8 @@ impl Enumeration {
                 (a, Some(b)) if a == b => a,
                 _ => return None,
             },
-            Enumeration::ReversibleIter(i) => match i.size_hint() {
-                (a, Some(b)) if a == b => a,
-                _ => return None,
-            },
             Enumeration::Sized(v) => *v,
-            Enumeration::Empty => 0,
+            Enumeration::NonEnumerable => return None,
         })
     }
 
@@ -346,22 +331,16 @@ impl Enumeration {
     pub fn is_empty(&self) -> bool {
         self.len() == Some(0)
     }
-}
-
-impl IntoIterator for Enumeration {
-    type Item = Value;
-
-    type IntoIter = EnumerationIter;
 
-    fn into_iter(self) -> Self::IntoIter {
-        EnumerationIter(match self {
+    /// Converts the enumeration into an iterator if possible.
+    fn try_into_iter(self) -> Option<EnumerationIter> {
+        Some(EnumerationIter(match self {
             Enumeration::Values(v) => EnumerationIterRepr::Values(v.into_iter()),
             Enumeration::Static(v) => EnumerationIterRepr::Static(v.iter()),
             Enumeration::Iterator(i) => EnumerationIterRepr::Iterator(i),
-            Enumeration::ReversibleIter(i) => EnumerationIterRepr::ReversibleIter(i),
             Enumeration::Sized(i) => EnumerationIterRepr::Sized(0..i),
-            Enumeration::Empty => EnumerationIterRepr::Empty,
-        })
+            Enumeration::NonEnumerable => return None,
+        }))
     }
 }
 
@@ -373,9 +352,7 @@ impl Iterator for EnumerationIter {
             EnumerationIterRepr::Values(iter) => iter.next(),
             EnumerationIterRepr::Static(iter) => iter.next().copied().map(intern).map(Value::from),
             EnumerationIterRepr::Iterator(iter) => iter.next(),
-            EnumerationIterRepr::ReversibleIter(iter) => iter.next(),
             EnumerationIterRepr::Sized(iter) => iter.next().map(Value::from),
-            EnumerationIterRepr::Empty => None,
         }
     }
 
@@ -384,31 +361,12 @@ impl Iterator for EnumerationIter {
             EnumerationIterRepr::Values(iter) => iter.size_hint(),
             EnumerationIterRepr::Static(iter) => iter.size_hint(),
             EnumerationIterRepr::Iterator(iter) => iter.size_hint(),
-            EnumerationIterRepr::ReversibleIter(iter) => iter.size_hint(),
             EnumerationIterRepr::Sized(iter) => iter.size_hint(),
-            EnumerationIterRepr::Empty => (0, Some(0)),
-        }
-    }
-}
-
-// XXX: this trait implementation is not correct for iterators.
-// Tracked in https://github.com/mitsuhiko/minijinja/issues/455
-impl DoubleEndedIterator for EnumerationIter {
-    fn next_back(&mut self) -> Option<Self::Item> {
-        match &mut self.0 {
-            EnumerationIterRepr::Values(iter) => iter.next_back(),
-            EnumerationIterRepr::Static(iter) => {
-                iter.next_back().copied().map(intern).map(Value::from)
-            }
-            EnumerationIterRepr::Iterator(iter) => iter.next(), // FIXME: ?
-            EnumerationIterRepr::ReversibleIter(iter) => iter.next_back(),
-            EnumerationIterRepr::Sized(iter) => iter.next_back().map(Value::from),
-            EnumerationIterRepr::Empty => None,
         }
     }
 }
 
-impl<T: Into<Value> + Clone + fmt::Debug> Object for Vec<T> {
+impl<T: Into<Value> + Clone + Send + Sync + fmt::Debug> Object for Vec<T> {
     fn repr(self: &Arc<Self>) -> ObjectRepr {
         ObjectRepr::Seq
     }
@@ -456,3 +414,16 @@ where
         })
     }
 }
+
+/// Utility type that displays a value in debug except for objects which are rendered.
+pub(crate) struct ValueDbgRender<'a>(pub &'a Value);
+
+impl<'a> fmt::Debug for ValueDbgRender<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let ValueRepr::Object(ref obj) = self.0 .0 {
+            obj.render(f)
+        } else {
+            fmt::Debug::fmt(&self.0, f)
+        }
+    }
+}
diff --git a/minijinja/src/value/ops.rs b/minijinja/src/value/ops.rs
index 33a19f34..d6b6e00c 100644
--- a/minijinja/src/value/ops.rs
+++ b/minijinja/src/value/ops.rs
@@ -113,12 +113,17 @@ pub fn slice(value: Value, start: Value, stop: Value, step: Value) -> Result<Val
             ))
         }
         ValueRepr::Undefined | ValueRepr::None => Ok(Value::from(Vec::<Value>::new())),
-        ValueRepr::Object(obj) => {
-            let len = obj.enumeration().len().unwrap_or_default(); // FIXME: <--
+        ValueRepr::Object(obj) if obj.repr() == ObjectRepr::Seq => {
+            let len = obj.enumeration().len().unwrap_or_default();
             let (start, len) = get_offset_and_len(start, stop, || len);
-            Ok(Value::from_object_iter(obj.clone(), move |this| {
-                Box::new(this.values().skip(start).take(len).step_by(step))
-            }))
+            Ok(Value::from_iter(
+                obj.try_iter()
+                    .into_iter()
+                    .flatten()
+                    .skip(start)
+                    .take(len)
+                    .step_by(step),
+            ))
         }
         _ => error,
     }
@@ -266,8 +271,11 @@ pub fn contains(container: &Value, value: &Value) -> Result<Value, Error> {
         }
     } else if let ValueRepr::Object(ref obj) = container.0 {
         match obj.repr() {
+            ObjectRepr::Plain => false,
             ObjectRepr::Map => obj.get_value(value).is_some(),
-            ObjectRepr::Seq => obj.values().any(|v| &v == value),
+            ObjectRepr::Seq | ObjectRepr::Iterator => {
+                obj.try_iter().into_iter().flatten().any(|v| &v == value)
+            }
         }
     } else {
         return Err(Error::new(
diff --git a/minijinja/src/vm/context.rs b/minijinja/src/vm/context.rs
index 4216d0f0..555bbd5c 100644
--- a/minijinja/src/vm/context.rs
+++ b/minijinja/src/vm/context.rs
@@ -5,7 +5,7 @@ use std::sync::Arc;
 
 use crate::environment::Environment;
 use crate::error::{Error, ErrorKind};
-use crate::value::{OwnedValueIterator, Value, ValueRepr};
+use crate::value::{Value, ValueIter, ValueRepr};
 use crate::vm::loop_object::Loop;
 
 #[cfg(feature = "macros")]
@@ -20,7 +20,7 @@ pub(crate) struct LoopState {
     // first item is the target jump instruction, the second argument
     // tells us if we need to end capturing.
     pub(crate) current_recursion_jump: Option<(usize, bool)>,
-    pub(crate) iterator: OwnedValueIterator,
+    pub(crate) iterator: ValueIter,
     pub(crate) object: Arc<Loop>,
 }
 
@@ -108,6 +108,11 @@ impl Stack {
         self.values.pop().unwrap()
     }
 
+    pub fn reverse_top(&mut self, n: usize) {
+        let start = self.values.len() - n;
+        self.values[start..].reverse();
+    }
+
     pub fn slice_top(&mut self, n: usize) -> &[Value] {
         &self.values[self.values.len() - n..]
     }
diff --git a/minijinja/src/vm/macro_object.rs b/minijinja/src/vm/macro_object.rs
index 2db10ac3..6790b886 100644
--- a/minijinja/src/vm/macro_object.rs
+++ b/minijinja/src/vm/macro_object.rs
@@ -36,9 +36,9 @@ impl Object for Macro {
     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
         match key.as_str()? {
             "name" => Some(Value::from(self.name.clone())),
-            "arguments" => Some(Value::from_object_iter(self.clone(), |this| {
-                Box::new(this.arg_spec.iter().cloned().map(Value::from))
-            })),
+            "arguments" => Some(Value::from_iter(
+                self.arg_spec.iter().cloned().map(Value::from),
+            )),
             "caller" => Some(Value::from(self.caller_reference)),
             _ => None,
         }
diff --git a/minijinja/src/vm/mod.rs b/minijinja/src/vm/mod.rs
index 1e5c31fb..bb4dd7b3 100644
--- a/minijinja/src/vm/mod.rs
+++ b/minijinja/src/vm/mod.rs
@@ -389,7 +389,7 @@ impl<'env> Vm<'env> {
                     stack.push(Value::from_object(v))
                 }
                 Instruction::UnpackList(count) => {
-                    ctx_ok!(self.unpack_list(&mut stack, count));
+                    ctx_ok!(self.unpack_list(&mut stack, *count));
                 }
                 Instruction::Add => func_binop!(add),
                 Instruction::Sub => func_binop!(sub),
@@ -704,7 +704,7 @@ impl<'env> Vm<'env> {
         let obj = name.as_object();
         let choices = obj
             .as_ref()
-            .map(|d| d.values())
+            .and_then(|d| d.try_iter())
             .into_iter()
             .flatten()
             .chain(obj.is_none().then(|| name.clone()));
@@ -961,28 +961,27 @@ impl<'env> Vm<'env> {
         Ok(())
     }
 
-    fn unpack_list(&self, stack: &mut Stack, count: &usize) -> Result<(), Error> {
+    fn unpack_list(&self, stack: &mut Stack, count: usize) -> Result<(), Error> {
         let top = stack.pop();
-        let obj = ok!(top
+        let iter = ok!(top
             .as_object()
-            .ok_or_else(|| Error::new(ErrorKind::CannotUnpack, "not a sequence")));
+            .and_then(|x| x.try_iter())
+            .ok_or_else(|| Error::new(ErrorKind::CannotUnpack, "value is not iterable")));
 
-        match obj.enumeration().len() {
-            Some(n) if n == *count => {
-                for item in obj.values().rev() {
-                    stack.push(item);
-                }
+        let mut n = 0;
+        for item in iter {
+            stack.push(item);
+            n += 1;
+        }
 
-                Ok(())
-            }
-            Some(n) => Err(Error::new(
-                ErrorKind::CannotUnpack,
-                format!("sequence of wrong length (expected {}, got {})", *count, n,),
-            )),
-            None => Err(Error::new(
+        if n == count {
+            stack.reverse_top(n);
+            Ok(())
+        } else {
+            Err(Error::new(
                 ErrorKind::CannotUnpack,
-                "cannot unpack unsized value",
-            )),
+                format!("sequence of wrong length (expected {}, got {})", count, n,),
+            ))
         }
     }
 
@@ -999,7 +998,8 @@ impl<'env> Vm<'env> {
 
         let arg_spec = match stack.pop().0 {
             ValueRepr::Object(args) => args
-                .values()
+                .try_iter()
+                .unwrap()
                 .map(|value| match &value.0 {
                     ValueRepr::String(arg, _) => arg.clone(),
                     _ => unreachable!(),


Test patch:
diff --git a/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap b/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap
index 58221a3d..6cf8a1b2 100644
--- a/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap
+++ b/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap
@@ -12,12 +12,12 @@ input_file: minijinja/tests/inputs/loop_bad_unpacking.txt
 
 Error {
     kind: CannotUnpack,
-    detail: "not a sequence",
+    detail: "value is not iterable",
     name: "loop_bad_unpacking.txt",
     line: 2,
 }
 
-cannot unpack: not a sequence (in loop_bad_unpacking.txt:2)
+cannot unpack: value is not iterable (in loop_bad_unpacking.txt:2)
 --------------------------- loop_bad_unpacking.txt ----------------------------
    1 | <ul>
    2 > {% for a, b in seq %}
@@ -47,4 +47,3 @@ Referenced variables: {
     ],
 }
 -------------------------------------------------------------------------------
-
diff --git a/minijinja/tests/test_environment.rs b/minijinja/tests/test_environment.rs
index 764e529d..e3920f56 100644
--- a/minijinja/tests/test_environment.rs
+++ b/minijinja/tests/test_environment.rs
@@ -3,8 +3,8 @@ use std::collections::BTreeMap;
 use insta::assert_snapshot;
 use similar_asserts::assert_eq;
 
-use minijinja::value::{from_args, Value, ValueKind};
-use minijinja::{Environment, Error, ErrorKind};
+use minijinja::Environment;
+use minijinja::Value;
 
 #[test]
 fn test_basic() {
@@ -152,7 +152,11 @@ fn test_keep_trailing_newlines() {
 }
 
 #[test]
+#[cfg(feature = "builtins")]
 fn test_unknown_method_callback() {
+    use minijinja::value::{from_args, ValueKind};
+    use minijinja::{Error, ErrorKind};
+
     let mut env = Environment::new();
     env.set_unknown_method_callback(|_state, value, method, args| {
         if value.kind() == ValueKind::Map && method == "items" {
diff --git a/minijinja/tests/test_value.rs b/minijinja/tests/test_value.rs
index bdc0d77c..7deeb391 100644
--- a/minijinja/tests/test_value.rs
+++ b/minijinja/tests/test_value.rs
@@ -4,7 +4,7 @@ use insta::assert_snapshot;
 use similar_asserts::assert_eq;
 
 use minijinja::value::{DynObject, Enumeration, Kwargs, Object, ObjectRepr, Rest, Value};
-use minijinja::{args, Environment, Error};
+use minijinja::{args, render, Environment, Error};
 
 #[test]
 fn test_sort() {
@@ -456,7 +456,7 @@ fn test_values_in_vec() {
 fn test_seq_object_borrow() {
     fn connect(values: DynObject) -> String {
         let mut rv = String::new();
-        for item in values.values() {
+        for item in values.try_iter().into_iter().flatten() {
             rv.push_str(&item.to_string())
         }
         rv
@@ -476,40 +476,40 @@ fn test_seq_object_borrow() {
     );
 }
 
-// #[test]
-// fn test_iterator() {
-//     let value = Value::from_iterator(0..10);
-//     assert_eq!(value.to_string(), "<iterator>");
-//     let rv = render!(
-//         "{% for item in iter %}[{{ item }}]{% endfor %}",
-//         iter => value
-//     );
-//     assert_snapshot!(rv, @"[0][1][2][3][4][5][6][7][8][9]");
-//
-//     let rv = render!(
-//         "{% for item in iter %}- {{ item }}: {{ loop.index }} / {{ loop.length }}\n{% endfor %}",
-//         iter => Value::from_iterator('a'..'f')
-//     );
-//     assert_snapshot!(rv, @r###"
-//     - a: 1 / 5
-//     - b: 2 / 5
-//     - c: 3 / 5
-//     - d: 4 / 5
-//     - e: 5 / 5
-//     "###);
-//
-//     let rv = render!(
-//         "{% for item in iter %}- {{ item }}: {{ loop.index }} / {{ loop.length|default('?') }}\n{% endfor %}",
-//      iter => Value::from_iterator((0..10).filter(|x| x % 2 == 0))
-//     );
-//     assert_snapshot!(rv, @r###"
-//     - 0: 1 / ?
-//     - 2: 2 / ?
-//     - 4: 3 / ?
-//     - 6: 4 / ?
-//     - 8: 5 / ?
-//     "###);
-// }
+#[test]
+fn test_iterator() {
+    let value = Value::from_iterator(0..10);
+    assert_eq!(value.to_string(), "<iterator>");
+    let rv = render!(
+        "{% for item in iter %}[{{ item }}]{% endfor %}",
+        iter => value
+    );
+    assert_snapshot!(rv, @"[0][1][2][3][4][5][6][7][8][9]");
+
+    let rv = render!(
+        "{% for item in iter %}- {{ item }}: {{ loop.index }} / {{ loop.length }}\n{% endfor %}",
+        iter => Value::from_iterator('a'..'f')
+    );
+    assert_snapshot!(rv, @r###"
+    - a: 1 / 5
+    - b: 2 / 5
+    - c: 3 / 5
+    - d: 4 / 5
+    - e: 5 / 5
+    "###);
+
+    let rv = render!(
+        "{% for item in iter %}- {{ item }}: {{ loop.index }} / {{ loop.length|default('?') }}\n{% endfor %}",
+     iter => Value::from_iterator((0..10).filter(|x| x % 2 == 0))
+    );
+    assert_snapshot!(rv, @r###"
+    - 0: 1 / ?
+    - 2: 2 / ?
+    - 4: 3 / ?
+    - 6: 4 / ?
+    - 8: 5 / ?
+    "###);
+}
 
 #[test]
 fn test_complex_key() {
@@ -517,7 +517,6 @@ fn test_complex_key() {
         (Value::from_iter([0u32, 0u32]), "origin"),
         (Value::from_iter([0u32, 1u32]), "right"),
     ]);
-
     assert_eq!(
         value.get_item(&Value::from_iter([0, 0])).ok(),
         Some(Value::from("origin"))
@@ -570,3 +569,93 @@ fn test_via_deserialize() {
     let rv = state.apply_filter("foo", args![point_value]).unwrap();
     assert_eq!(rv.to_string(), "42, -23");
 }
+
+#[test]
+fn test_seq_custom_iter() {
+    #[derive(Debug)]
+    struct WeirdSeq;
+
+    impl Object for WeirdSeq {
+        fn repr(self: &Arc<Self>) -> ObjectRepr {
+            ObjectRepr::Seq
+        }
+
+        fn enumeration(self: &Arc<Self>) -> Enumeration {
+            Enumeration::NonEnumerable
+        }
+
+        fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
+            match key.as_usize() {
+                Some(0) => Some(Value::from(true)),
+                Some(1) => Some(Value::from(false)),
+                _ => None,
+            }
+        }
+
+        fn custom_iter(self: &Arc<Self>) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+            Some(Box::new(('a'..='b').map(Value::from)))
+        }
+    }
+
+    let v = Value::from_object(WeirdSeq);
+    assert_eq!(v.get_item_by_index(0).unwrap(), Value::from(true));
+    assert_eq!(v.get_item_by_index(1).unwrap(), Value::from(false));
+
+    let vec = v.try_iter().unwrap().collect::<Vec<_>>();
+    assert_eq!(vec, vec![Value::from('a'), Value::from('b')]);
+
+    let obj = v.as_object().unwrap();
+    let vec = obj.try_iter_pairs().unwrap().collect::<Vec<_>>();
+    assert_eq!(
+        vec,
+        vec![
+            (Value::from(0), Value::from('a')),
+            (Value::from(1), Value::from('b'))
+        ]
+    );
+}
+
+#[test]
+fn test_map_custom_iter() {
+    #[derive(Debug)]
+    struct WeirdMap;
+
+    impl Object for WeirdMap {
+        fn repr(self: &Arc<Self>) -> ObjectRepr {
+            ObjectRepr::Map
+        }
+
+        fn enumeration(self: &Arc<Self>) -> Enumeration {
+            Enumeration::NonEnumerable
+        }
+
+        fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
+            match key.as_str() {
+                Some("a") => Some(Value::from(true)),
+                Some("b") => Some(Value::from(false)),
+                _ => None,
+            }
+        }
+
+        fn custom_iter(self: &Arc<Self>) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+            Some(Box::new(('a'..='b').map(Value::from)))
+        }
+    }
+
+    let v = Value::from_object(WeirdMap);
+    assert_eq!(v.get_attr("a").unwrap(), Value::from(true));
+    assert_eq!(v.get_attr("b").unwrap(), Value::from(false));
+
+    let vec = v.try_iter().unwrap().collect::<Vec<_>>();
+    assert_eq!(vec, vec![Value::from('a'), Value::from('b')]);
+
+    let obj = v.as_object().unwrap();
+    let vec = obj.try_iter_pairs().unwrap().collect::<Vec<_>>();
+    assert_eq!(
+        vec,
+        vec![
+            (Value::from("a"), Value::from(true)),
+            (Value::from("b"), Value::from(false))
+        ]
+    );
+}